Skip to content

Commit

Permalink
feat(api, web, shared, app-gen): Refactor feature flags for key enum (#…
Browse files Browse the repository at this point in the history
…5100)

* feat(api, web, shared, app-gen): Refactor feature flags to allow provision of key enum

* feat(api): Remove FF_IS_TOPIC_NOTIFICATION_ENABLED flag

* Revert "feat(api): Remove FF_IS_TOPIC_NOTIFICATION_ENABLED flag"

This reverts commit 62a054c.

* revert: Add back IS_TOPIC_NOTIFICATION_ENABLED FF

* test(shared): Add jest

* feat(shared): Add FF preparation util

* test(shared): Fix ts error test

* fix(shared): Pluralize name

* feat(app-gen, web): Use the FF preparation util

* Merge branch 'next' into ent-19-billing-launchdarkly-flag

* fix: FF naming convention
  • Loading branch information
rifont authored Jan 31, 2024
1 parent 28dd9e8 commit 314df4d
Show file tree
Hide file tree
Showing 50 changed files with 192 additions and 566 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
'plugin:prettier/recommended',
'plugin:promise/recommended',
],
ignorePatterns: ['.eslintrc.js', '*.json', 'jest.config.js'],
ignorePatterns: ['.eslintrc.js', '*.json', 'jest.config.js', 'jest.setup.js'],
plugins: ['import', 'promise', '@typescript-eslint', 'prettier'],
parser: '@typescript-eslint/parser',
settings: {
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ LOGGING_LEVEL=info
VERCEL_REDIRECT_URI=https://dev.web.novu.co/auth/login
VERCEL_BASE_URL=https://api.vercel.com

FF_IS_TOPIC_NOTIFICATION_ENABLED=true
IS_TOPIC_NOTIFICATION_ENABLED=true
FF_IS_DISTRIBUTED_LOCK_LOGGING_ENABLED=false
IS_TRANSLATION_MANAGER_ENABLED=false

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ LOGGING_LEVEL=info
VERCEL_REDIRECT_URI=https://web.novu.co/auth/login
VERCEL_BASE_URL=https://api.vercel.com

FF_IS_TOPIC_NOTIFICATION_ENABLED=true
IS_TOPIC_NOTIFICATION_ENABLED=true
FF_IS_DISTRIBUTED_LOCK_LOGGING_ENABLED=false
IS_TRANSLATION_MANAGER_ENABLED=false

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ VERCEL_CLIENT_SECRET=
VERCEL_REDIRECT_URI=http://127.0.0.1:4200/auth/login
VERCEL_BASE_URL=https://api.vercel.com

FF_IS_TOPIC_NOTIFICATION_ENABLED=true
IS_TOPIC_NOTIFICATION_ENABLED=true

STORE_NOTIFICATION_CONTENT=true

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ VERCEL_CLIENT_SECRET=
VERCEL_REDIRECT_URI=http://127.0.0.1:4200/auth/login
VERCEL_BASE_URL=https://api.vercel.com

FF_IS_TOPIC_NOTIFICATION_ENABLED=true
IS_TOPIC_NOTIFICATION_ENABLED=true
IS_TRANSLATION_MANAGER_ENABLED=false

STORE_NOTIFICATION_CONTENT=true
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/events/e2e/map-trigger-recipients.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('MapTriggerRecipientsUseCase', () => {
await featureFlagsService.initialize();

process.env.LAUNCH_DARKLY_SDK_KEY = '';
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'false';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'false';

const moduleRef = await Test.createTestingModule({
imports: [SharedModule, EventsModule],
Expand Down Expand Up @@ -196,7 +196,7 @@ describe('MapTriggerRecipientsUseCase', () => {
describe('When feature enabled', () => {
before(async () => {
process.env.LAUNCH_DARKLY_SDK_KEY = '';
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'true';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'true';

const moduleRef = await Test.createTestingModule({
imports: [SharedModule, EventsModule],
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/app/events/e2e/trigger-event-topic.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('Topic Trigger Event', () => {

beforeEach(async () => {
process.env.LAUNCH_DARKLY_SDK_KEY = '';
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'true';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'true';
session = new UserSession();
await session.initialize();

Expand All @@ -70,7 +70,7 @@ describe('Topic Trigger Event', () => {

afterEach(() => {
process.env.LAUNCH_DARKLY_SDK_KEY = originalLaunchDarklySdkKey;
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'false';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'false';
});

it('should trigger an event successfully', async () => {
Expand Down Expand Up @@ -323,7 +323,7 @@ describe('Topic Trigger Event', () => {

beforeEach(async () => {
process.env.LAUNCH_DARKLY_SDK_KEY = '';
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'true';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'true';
session = new UserSession();
await session.initialize();

Expand Down Expand Up @@ -377,7 +377,7 @@ describe('Topic Trigger Event', () => {

afterEach(() => {
process.env.LAUNCH_DARKLY_SDK_KEY = originalLaunchDarklySdkKey;
process.env.FF_IS_TOPIC_NOTIFICATION_ENABLED = 'false';
process.env.IS_TOPIC_NOTIFICATION_ENABLED = 'false';
});

it('should trigger an event successfully', async () => {
Expand Down
18 changes: 12 additions & 6 deletions apps/api/src/app/rate-limiting/guards/throttler.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import {
ThrottlerStorage,
} from '@nestjs/throttler';
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { EvaluateApiRateLimit, EvaluateApiRateLimitCommand } from '../usecases/evaluate-api-rate-limit';
import { Reflector } from '@nestjs/core';
import { FeatureFlagCommand, GetIsApiRateLimitingEnabled, Instrument } from '@novu/application-generic';
import { ApiRateLimitCategoryEnum, ApiRateLimitCostEnum, ApiAuthSchemeEnum, IJwtPayload } from '@novu/shared';
import { GetFeatureFlag, GetFeatureFlagCommand, Instrument } from '@novu/application-generic';
import {
ApiRateLimitCategoryEnum,
ApiRateLimitCostEnum,
ApiAuthSchemeEnum,
IJwtPayload,
FeatureFlagsKeysEnum,
} from '@novu/shared';
import { ThrottlerCost, ThrottlerCategory } from './throttler.decorator';
import { HttpRequestHeaderKeysEnum, HttpResponseHeaderKeysEnum } from '../../shared/framework/types';

Expand All @@ -34,7 +39,7 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
@InjectThrottlerStorage() protected readonly storageService: ThrottlerStorage,
reflector: Reflector,
private evaluateApiRateLimit: EvaluateApiRateLimit,
private getIsApiRateLimitingEnabled: GetIsApiRateLimitingEnabled
private getFeatureFlag: GetFeatureFlag
) {
super(options, storageService, reflector);
}
Expand Down Expand Up @@ -66,11 +71,12 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
const user = this.getReqUser(context);
const { organizationId, environmentId, _id } = user;

const isEnabled = await this.getIsApiRateLimitingEnabled.execute(
FeatureFlagCommand.create({
const isEnabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
environmentId,
organizationId,
userId: _id,
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_ENABLED,
})
);

Expand Down
14 changes: 6 additions & 8 deletions apps/api/src/app/shared/framework/idempotency.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
BadRequestException,
ConflictException,
} from '@nestjs/common';
import { CacheService, FeatureFlagCommand, GetIsApiIdempotencyEnabled, Instrument } from '@novu/application-generic';
import { CacheService, GetFeatureFlagCommand, GetFeatureFlag, Instrument } from '@novu/application-generic';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { createHash } from 'crypto';
import { ApiAuthSchemeEnum, IJwtPayload } from '@novu/shared';
import { ApiAuthSchemeEnum, FeatureFlagsKeysEnum, IJwtPayload } from '@novu/shared';
import { HttpResponseHeaderKeysEnum } from './types';

const LOG_CONTEXT = 'IdempotencyInterceptor';
Expand All @@ -34,10 +34,7 @@ const ALLOWED_METHODS = ['post', 'patch'];

@Injectable()
export class IdempotencyInterceptor implements NestInterceptor {
constructor(
private readonly cacheService: CacheService,
private getIsApiIdempotencyEnabled: GetIsApiIdempotencyEnabled
) {}
constructor(private readonly cacheService: CacheService, private getFeatureFlag: GetFeatureFlag) {}

protected async isEnabled(context: ExecutionContext): Promise<boolean> {
const isAllowedAuthScheme = this.isAllowedAuthScheme(context);
Expand All @@ -48,8 +45,9 @@ export class IdempotencyInterceptor implements NestInterceptor {
const user = this.getReqUser(context);
const { organizationId, environmentId, _id } = user;

const isEnabled = await this.getIsApiIdempotencyEnabled.execute(
FeatureFlagCommand.create({
const isEnabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
key: FeatureFlagsKeysEnum.IS_API_IDEMPOTENCY_ENABLED,
environmentId,
organizationId,
userId: _id,
Expand Down
10 changes: 2 additions & 8 deletions apps/api/src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,13 @@ import {
DalServiceHealthIndicator,
distributedLockService,
featureFlagsService,
getIsTopicNotificationEnabled,
getIsApiRateLimitingEnabled,
getFeatureFlag,
InvalidateCacheService,
LoggerModule,
QueuesModule,
storageService,
getIsApiIdempotencyEnabled,
ExecutionLogRoute,
CreateExecutionDetails,
getIsExecutionLogQueueEnabled,
} from '@novu/application-generic';

import * as packageJson from '../../../package.json';
Expand Down Expand Up @@ -92,15 +89,12 @@ const PROVIDERS = [
DalServiceHealthIndicator,
distributedLockService,
featureFlagsService,
getIsTopicNotificationEnabled,
getIsApiRateLimitingEnabled,
getIsApiIdempotencyEnabled,
InvalidateCacheService,
storageService,
...DAL_MODELS,
ExecutionLogRoute,
CreateExecutionDetails,
getIsExecutionLogQueueEnabled,
getFeatureFlag,
];

@Module({
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/config/env-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const validators: { [K in keyof any]: ValidatorSpec<any[K]> } = {
NEW_RELIC_LICENSE_KEY: str({
default: '',
}),
FF_IS_TOPIC_NOTIFICATION_ENABLED: bool({
IS_TOPIC_NOTIFICATION_ENABLED: bool({
desc: 'This is the environment variable used to enable the feature to send notifications to a topic',
default: true,
choices: [false, true],
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/components/layout/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import {
Settings,
Team,
} from '@novu/design-system';
import { useEnvController, useIsMultiTenancyEnabled, useIsTranslationManagerEnabled } from '../../../hooks';
import { useEnvController, useFeatureFlag } from '../../../hooks';
import { currentOnboardingStep } from '../../../pages/quick-start/components/route/store';
import { useSpotlightContext } from '../../providers/SpotlightProvider';
import { ChangesCountBadge } from './ChangesCountBadge';
import OrganizationSelect from './OrganizationSelect';
import { FeatureFlagsKeysEnum } from '@novu/shared';

const usePopoverStyles = createStyles(({ colorScheme }) => ({
dropdown: {
Expand Down Expand Up @@ -62,8 +63,8 @@ export function SideNav({}: Props) {
const dark = colorScheme === 'dark';
const { addItem, removeItems } = useSpotlightContext();
const { classes } = usePopoverStyles();
const isMultiTenancyEnabled = useIsMultiTenancyEnabled();
const isTranslationManagerEnabled = useIsTranslationManagerEnabled();
const isMultiTenancyEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_MULTI_TENANCY_ENABLED);
const isTranslationManagerEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_TRANSLATION_MANAGER_ENABLED);

useEffect(() => {
removeItems(['toggle-environment']);
Expand Down
8 changes: 2 additions & 6 deletions apps/web/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import {
WEBHOOK_URL,
MAIL_SERVER_DOMAIN,
LAUNCH_DARKLY_CLIENT_SIDE_ID,
IS_TEMPLATE_STORE_ENABLED,
IS_MULTI_TENANCY_ENABLED,
IS_TRANSLATION_MANAGER_ENABLED,
FEATURE_FLAGS,
} from '@novu/shared-web';

export {
Expand All @@ -33,9 +31,7 @@ export {
WEBHOOK_URL,
MAIL_SERVER_DOMAIN,
LAUNCH_DARKLY_CLIENT_SIDE_ID,
IS_TEMPLATE_STORE_ENABLED,
IS_MULTI_TENANCY_ENABLED,
IS_TRANSLATION_MANAGER_ENABLED,
FEATURE_FLAGS,
};

export const IS_EU_ENV = (ENV === 'production' || ENV === 'prod') && API_ROOT.includes('eu.api.novu.co');
9 changes: 1 addition & 8 deletions apps/web/src/hooks/useFeatureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
import {
useFeatureFlags,
useIsTemplateStoreEnabled,
useIsMultiTenancyEnabled,
useIsTranslationManagerEnabled,
} from '@novu/shared-web';

export { useFeatureFlags, useIsTemplateStoreEnabled, useIsMultiTenancyEnabled, useIsTranslationManagerEnabled };
export { useFeatureFlags, useFeatureFlag } from '@novu/shared-web';
5 changes: 3 additions & 2 deletions apps/web/src/pages/templates/WorkflowListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
useTemplates,
useEnvController,
useNotificationGroup,
useIsTemplateStoreEnabled,
useFeatureFlag,
INotificationTemplateExtended,
} from '../../hooks';
import PageHeader from '../../components/layout/components/PageHeader';
Expand Down Expand Up @@ -43,6 +43,7 @@ import { TemplateCreationSourceEnum } from './shared';
import { TemplatesListNoDataOld } from './TemplatesListNoDataOld';
import { useCreateDigestDemoWorkflow } from '../../api/hooks/notification-templates/useCreateDigestDemoWorkflow';
import { When } from '../../components/utils/When';
import { FeatureFlagsKeysEnum } from '@novu/shared';

const columns: IExtendedColumn<INotificationTemplateExtended>[] = [
{
Expand Down Expand Up @@ -174,7 +175,7 @@ function WorkflowListPage() {

const { TemplatesStoreModal, openModal } = useTemplatesStoreModal({ general, popular });
const { createDigestDemoWorkflow, isDisabled: isTryDigestDisabled } = useCreateDigestDemoWorkflow();
const isTemplateStoreEnabled = useIsTemplateStoreEnabled();
const isTemplateStoreEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_TEMPLATE_STORE_ENABLED);

function handleTableChange(pageIndex) {
setPage(pageIndex);
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/pages/templates/hooks/useTemplatesStoreModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useDisclosure } from '@mantine/hooks';
import { FeatureFlagsKeysEnum } from '@novu/shared';

import { IBlueprintsGrouped } from '../../../api/hooks';
import { useInlineComponent, useIsTemplateStoreEnabled } from '../../../hooks';
import { useInlineComponent, useFeatureFlag } from '../../../hooks';
import { ITemplatesStoreModalProps, TemplatesStoreModal } from '../components/templates-store';

const NULL_COMPONENT = () => null;
Expand All @@ -14,7 +15,7 @@ export const useTemplatesStoreModal = ({
popular?: IBlueprintsGrouped;
}) => {
const [opened, { open, close }] = useDisclosure(false);
const isTemplateStoreEnabled = useIsTemplateStoreEnabled();
const isTemplateStoreEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_TEMPLATE_STORE_ENABLED);
const hasGroups = general && general.length > 0;
const hasPopular = !!(popular && popular?.blueprints.length > 0);
const hasGroupsOrPopular = hasGroups || hasPopular;
Expand Down
2 changes: 0 additions & 2 deletions apps/worker/src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
EventsDistributedLockService,
featureFlagsService,
GetTenant,
getUseMergedDigestId,
InvalidateCacheService,
LoggerModule,
MetricsModule,
Expand Down Expand Up @@ -110,7 +109,6 @@ const PROVIDERS = [
distributedLockService,
EventsDistributedLockService,
featureFlagsService,
getUseMergedDigestId,
InvalidateCacheService,
ProcessSubscriber,
StorageHelperService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
ExecutionDetailsStatusEnum,
DigestTypeEnum,
IDigestRegularMetadata,
FeatureFlagsKeysEnum,
} from '@novu/shared';
import {
DetailEnum,
GetUseMergedDigestId,
FeatureFlagCommand,
GetFeatureFlag,
GetFeatureFlagCommand,
ExecutionLogRoute,
ExecutionLogRouteCommand,
} from '@novu/application-generic';
Expand All @@ -36,16 +37,17 @@ export class Digest extends SendMessageType {
protected jobRepository: JobRepository,
private getDigestEventsRegular: GetDigestEventsRegular,
private getDigestEventsBackoff: GetDigestEventsBackoff,
private getUseMergedDigestId: GetUseMergedDigestId
private getFeatureFlag: GetFeatureFlag
) {
super(messageRepository, createLogUsecase, executionLogRoute);
}

public async execute(command: SendMessageCommand) {
const currentJob = await this.getCurrentJob(command);

const useMergedDigestId = await this.getUseMergedDigestId.execute(
FeatureFlagCommand.create({
const useMergedDigestId = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
key: FeatureFlagsKeysEnum.IS_USE_MERGED_DIGEST_ID_ENABLED,
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
Expand Down
Loading

0 comments on commit 314df4d

Please sign in to comment.