Feature flags seeds, queries and hooks (#2769)

* seed is working

* allow graphql to retrieve feature flag data

* create useIsFeatureEnabled hook

* hook is working

* Update icons.ts
This commit is contained in:
bosiraphael
2023-11-29 16:40:44 +01:00
committed by GitHub
parent d855a42eca
commit 04c7c1a334
12 changed files with 174 additions and 17 deletions

View File

@ -88,6 +88,30 @@ export type CursorPaging = {
last?: InputMaybe<Scalars['Int']>;
};
export type FeatureFlag = {
__typename?: 'FeatureFlag';
id: Scalars['ID'];
key: Scalars['String'];
value: Scalars['Boolean'];
workspaceId: Scalars['String'];
};
export type FeatureFlagFilter = {
and?: InputMaybe<Array<FeatureFlagFilter>>;
id?: InputMaybe<IdFilterComparison>;
or?: InputMaybe<Array<FeatureFlagFilter>>;
};
export type FeatureFlagSort = {
direction: SortDirection;
field: FeatureFlagSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum FeatureFlagSortFields {
Id = 'id'
}
export type FieldConnection = {
__typename?: 'FieldConnection';
/** Array of edges. */
@ -387,6 +411,18 @@ export enum RelationMetadataType {
OneToOne = 'ONE_TO_ONE'
}
/** Sort Directions */
export enum SortDirection {
Asc = 'ASC',
Desc = 'DESC'
}
/** Sort Nulls Options */
export enum SortNulls {
NullsFirst = 'NULLS_FIRST',
NullsLast = 'NULLS_LAST'
}
export type Support = {
__typename?: 'Support';
supportDriver: Scalars['String'];
@ -466,12 +502,19 @@ export type Workspace = {
deletedAt?: Maybe<Scalars['DateTime']>;
displayName?: Maybe<Scalars['String']>;
domainName?: Maybe<Scalars['String']>;
featureFlags?: Maybe<Array<FeatureFlag>>;
id: Scalars['ID'];
inviteHash?: Maybe<Scalars['String']>;
logo?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
};
export type WorkspaceFeatureFlagsArgs = {
filter?: FeatureFlagFilter;
sorting?: Array<FeatureFlagSort>;
};
export type WorkspaceEdge = {
__typename?: 'WorkspaceEdge';
/** Cursor for this node. */
@ -671,7 +714,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean } } };
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } } };
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
@ -1204,6 +1247,12 @@ export const GetCurrentUserDocument = gql`
domainName
inviteHash
allowImpersonation
featureFlags {
id
key
value
workspaceId
}
}
}
}

View File

@ -4,7 +4,12 @@ import { Workspace } from '~/generated/graphql';
export type CurrentWorkspace = Pick<
Workspace,
'id' | 'inviteHash' | 'logo' | 'displayName' | 'allowImpersonation'
| 'id'
| 'inviteHash'
| 'logo'
| 'displayName'
| 'allowImpersonation'
| 'featureFlags'
>;
export const currentWorkspaceState = atom<CurrentWorkspace | null>({

View File

@ -26,6 +26,12 @@ export const GET_CURRENT_USER = gql`
domainName
inviteHash
allowImpersonation
featureFlags {
id
key
value
workspaceId
}
}
}
}

View File

@ -0,0 +1,17 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
export const useIsFeatureEnabled = (featureKey: string): boolean => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const featureFlag = currentWorkspace?.featureFlags?.find(
(flag) => flag.key === featureKey,
);
if (!featureFlag) {
return false;
}
return featureFlag.value;
};

View File

@ -20,6 +20,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const SettingsObjectNewFieldStep2 = () => {
@ -37,6 +38,10 @@ export const SettingsObjectNewFieldStep2 = () => {
findActiveObjectMetadataItemBySlug(objectSlug);
const { createMetadataField } = useFieldMetadataItem();
const isRelationFieldTypeEnabled = useIsFeatureEnabled(
'IS_RELATION_FIELD_TYPE_ENABLED',
);
const {
formValues,
handleFormChange,
@ -177,6 +182,22 @@ export const SettingsObjectNewFieldStep2 = () => {
}
};
const excludedFieldTypes = [
FieldMetadataType.Currency,
FieldMetadataType.Email,
FieldMetadataType.Enum,
FieldMetadataType.Numeric,
FieldMetadataType.FullName,
FieldMetadataType.Link,
FieldMetadataType.Phone,
FieldMetadataType.Probability,
FieldMetadataType.Uuid,
];
if (!isRelationFieldTypeEnabled) {
excludedFieldTypes.push(FieldMetadataType.Relation);
}
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
@ -204,18 +225,7 @@ export const SettingsObjectNewFieldStep2 = () => {
onChange={handleFormChange}
/>
<SettingsObjectFieldTypeSelectSection
excludedFieldTypes={[
FieldMetadataType.Currency,
FieldMetadataType.Email,
FieldMetadataType.Enum,
FieldMetadataType.Numeric,
FieldMetadataType.FullName,
FieldMetadataType.Link,
FieldMetadataType.Phone,
FieldMetadataType.Probability,
FieldMetadataType.Relation,
FieldMetadataType.Uuid,
]}
excludedFieldTypes={excludedFieldTypes}
fieldMetadata={{
icon: formValues.icon,
label: formValues.label || 'Employees',

View File

@ -4,6 +4,7 @@ import { WorkspaceModule } from 'src/core/workspace/workspace.module';
import { UserModule } from 'src/core/user/user.module';
import { RefreshTokenModule } from 'src/core/refresh-token/refresh-token.module';
import { AuthModule } from 'src/core/auth/auth.module';
import { FeatureFlagModule } from 'src/core/feature-flag/feature-flag.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
@ -18,7 +19,14 @@ import { ClientConfigModule } from './client-config/client-config.module';
AnalyticsModule,
FileModule,
ClientConfigModule,
FeatureFlagModule,
],
exports: [
AuthModule,
WorkspaceModule,
UserModule,
AnalyticsModule,
FeatureFlagModule,
],
exports: [AuthModule, WorkspaceModule, UserModule, AnalyticsModule],
})
export class CoreModule {}

View File

@ -1,3 +1,5 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import {
Entity,
Unique,
@ -7,18 +9,23 @@ import {
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { Workspace } from 'src/core/workspace/workspace.entity';
@Entity({ name: 'featureFlag', schema: 'core' })
@ObjectType('FeatureFlag')
@Unique('IndexOnKeyAndWorkspaceIdUnique', ['key', 'workspaceId'])
export class FeatureFlagEntity {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
@Field()
@Column({ nullable: false, type: 'text' })
key: string;
@Field()
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@ -27,6 +34,7 @@ export class FeatureFlagEntity {
})
workspace: Workspace;
@Field()
@Column({ nullable: false })
value: boolean;

View File

@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
@Module({
imports: [
TypeORMModule,
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
],
services: [],
resolvers: [],
}),
],
exports: [],
providers: [],
})
export class FeatureFlagModule {}

View File

@ -1,6 +1,6 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IDField, UnPagedRelation } from '@ptc-org/nestjs-query-graphql';
import {
Column,
CreateDateColumn,
@ -15,6 +15,7 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
@Entity({ name: 'workspace', schema: 'core' })
@ObjectType('Workspace')
@UnPagedRelation('featureFlags', () => FeatureFlagEntity, { nullable: true })
export class Workspace {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')

View File

@ -7,6 +7,7 @@ import { FileModule } from 'src/core/file/file.module';
import { WorkspaceManagerModule } from 'src/workspace/workspace-manager/workspace-manager.module';
import { WorkspaceResolver } from 'src/core/workspace/workspace.resolver';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { Workspace } from './workspace.entity';
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
@ -18,7 +19,10 @@ import { WorkspaceService } from './services/workspace.service';
TypeORMModule,
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature([Workspace], 'core'),
NestjsQueryTypeOrmModule.forFeature(
[Workspace, FeatureFlagEntity],
'core',
),
WorkspaceManagerModule,
FileModule,
],

View File

@ -0,0 +1,24 @@
import { DataSource } from 'typeorm';
const tableName = 'featureFlag';
import { SeedWorkspaceId } from 'src/database/typeorm-seeds/core/workspaces';
export const seedFeatureFlags = async (
workspaceDataSource: DataSource,
schemaName: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
.orIgnore()
.values([
{
key: 'IS_RELATION_FIELD_TYPE_ENABLED',
workspaceId: SeedWorkspaceId,
value: true,
},
])
.execute();
};

View File

@ -2,9 +2,11 @@ import { DataSource } from 'typeorm';
import { seedUsers } from 'src/database/typeorm-seeds/core/users';
import { seedWorkspaces } from 'src/database/typeorm-seeds/core/workspaces';
import { seedFeatureFlags } from 'src/database/typeorm-seeds/core/feature-flags';
export const seedCoreSchema = async (workspaceDataSource: DataSource) => {
const schemaName = 'core';
await seedWorkspaces(workspaceDataSource, schemaName);
await seedUsers(workspaceDataSource, schemaName);
await seedFeatureFlags(workspaceDataSource, schemaName);
};