Skip to content

Commit d8b4ff7

Browse files
chore(root): Feature flag clean ups (#7723)
1 parent fe007f4 commit d8b4ff7

File tree

52 files changed

+737
-1589
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+737
-1589
lines changed

.source

apps/api/e2e/setup.ts

+25-39
Original file line numberDiff line numberDiff line change
@@ -47,47 +47,33 @@ after(async () => {
4747
}
4848
});
4949

50-
afterEach(async function () {
51-
sinon.restore();
52-
});
50+
async function cleanup() {
51+
const jobsService = new JobsService();
52+
await jobsService.runAllDelayedJobsImmediately();
53+
await jobsService.awaitAllJobs();
5354

54-
/*
55-
* async function cleanup() {
56-
* const jobsService = new JobsService();
57-
* await jobsService.runAllDelayedJobsImmediately();
58-
* await jobsService.awaitAllJobs();
59-
*/
55+
await Promise.all([workflowQueue.drain(), standardQueue.drain(), subscriberProcessQueue.drain()]);
6056

61-
// await Promise.all([workflowQueue.drain(), standardQueue.drain(), subscriberProcessQueue.drain()]);
62-
63-
/*
64-
* await jobRepository._model.deleteMany({});
65-
* }
66-
*/
57+
await jobRepository._model.deleteMany({});
58+
}
6759

68-
/*
69-
* function timeoutPromise(ms: number) {
70-
* // eslint-disable-next-line no-promise-executor-return
71-
* return new Promise((resolve) => setTimeout(resolve, ms));
72-
* }
73-
*/
60+
function timeoutPromise(ms: number) {
61+
// eslint-disable-next-line no-promise-executor-return
62+
return new Promise((resolve) => setTimeout(resolve, ms));
63+
}
7464

75-
/*
76-
* afterEach(async function () {
77-
* const TIMEOUT = 4500;
78-
* sinon.restore();
79-
*/
65+
afterEach(async function () {
66+
const TIMEOUT = 4500;
67+
sinon.restore();
8068

81-
/*
82-
* try {
83-
* await Promise.race([
84-
* cleanup(),
85-
* timeoutPromise(TIMEOUT).then(() => {
86-
* console.warn('Cleanup operation timed out after 5000ms - continuing with tests');
87-
* }),
88-
* ]);
89-
* } catch (error) {
90-
* console.error('Error during cleanup:', error);
91-
* }
92-
* });
93-
*/
69+
try {
70+
await Promise.race([
71+
cleanup(),
72+
timeoutPromise(TIMEOUT).then(() => {
73+
console.warn('Cleanup operation timed out after 5000ms - continuing with tests');
74+
}),
75+
]);
76+
} catch (error) {
77+
console.error('Error during cleanup:', error);
78+
}
79+
});

apps/api/src/app/billing/e2e/create-checkout-session.e2e-ee.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,19 @@ describe('Create checkout session #novu-v2', async () => {
5757
},
5858
};
5959
let checkoutCreateStub: sinon.SinonStub;
60-
let getFeatureFlagStub: { execute: sinon.SinonStub };
60+
let featureFlagsServiceStub: { getFlag: sinon.SinonStub };
6161
const IS_2025_Q1_TIERING_ENABLED = true;
6262

6363
beforeEach(() => {
6464
checkoutCreateStub = sinon.stub(stripeStub.checkout.sessions, 'create').resolves({ url: 'url' });
65-
getFeatureFlagStub = {
66-
execute: sinon.stub().resolves(IS_2025_Q1_TIERING_ENABLED),
65+
featureFlagsServiceStub = {
66+
getFlag: sinon.stub().resolves(IS_2025_Q1_TIERING_ENABLED),
6767
};
6868
});
6969

7070
afterEach(() => {
7171
checkoutCreateStub.reset();
72-
getFeatureFlagStub.execute.reset();
72+
featureFlagsServiceStub.getFlag.reset();
7373
});
7474

7575
it('Create checkout session with 1 subscription containing 1 licensed item and 1 metered item for monthly billing interval', async () => {
@@ -78,7 +78,7 @@ describe('Create checkout session #novu-v2', async () => {
7878
getOrCreateCustomer,
7979
userRepository,
8080
getPrices,
81-
getFeatureFlagStub
81+
featureFlagsServiceStub
8282
);
8383

8484
const result = await usecase.execute({
@@ -106,7 +106,7 @@ describe('Create checkout session #novu-v2', async () => {
106106
getOrCreateCustomer,
107107
userRepository,
108108
getPrices,
109-
getFeatureFlagStub
109+
featureFlagsServiceStub
110110
);
111111

112112
const result = await usecase.execute({

apps/api/src/app/billing/e2e/get-prices.e2e-ee.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ describe('GetPrices #novu-v2', () => {
1919
},
2020
};
2121
let listPricesStub: sinon.SinonStub;
22-
let getFeatureFlagStub: { execute: sinon.SinonStub };
22+
let featureFlagsServiceStub: { getFlag: sinon.SinonStub };
2323
const IS_2025_Q1_TIERING_ENABLED = true;
2424

2525
beforeEach(() => {
26-
getFeatureFlagStub = {
27-
execute: sinon.stub().resolves(IS_2025_Q1_TIERING_ENABLED),
26+
featureFlagsServiceStub = {
27+
getFlag: sinon.stub().resolves(IS_2025_Q1_TIERING_ENABLED),
2828
};
2929

3030
listPricesStub = stripeStub.prices.list;
@@ -38,10 +38,10 @@ describe('GetPrices #novu-v2', () => {
3838

3939
afterEach(() => {
4040
listPricesStub.reset();
41-
getFeatureFlagStub.execute.reset();
41+
featureFlagsServiceStub.getFlag.reset();
4242
});
4343

44-
const createUseCase = () => new GetPrices(stripeStub, getFeatureFlagStub);
44+
const createUseCase = () => new GetPrices(stripeStub, featureFlagsServiceStub);
4545

4646
const freeMeteredPriceLookupKey = IS_2025_Q1_TIERING_ENABLED
4747
? ['free_usage_notifications_10k']

apps/api/src/app/integrations/usecases/create-novu-integrations/create-novu-integrations.usecase.ts

+9-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import { Injectable } from '@nestjs/common';
22
import { EnvironmentEntity, IntegrationRepository, OrganizationEntity, UserEntity } from '@novu/dal';
3-
import {
4-
areNovuEmailCredentialsSet,
5-
areNovuSmsCredentialsSet,
6-
GetFeatureFlagService,
7-
GetFeatureFlagCommand,
8-
} from '@novu/application-generic';
3+
import { areNovuEmailCredentialsSet, areNovuSmsCredentialsSet, FeatureFlagsService } from '@novu/application-generic';
94

105
import {
116
ChannelTypeEnum,
@@ -26,7 +21,7 @@ export class CreateNovuIntegrations {
2621
private createIntegration: CreateIntegration,
2722
private integrationRepository: IntegrationRepository,
2823
private setIntegrationAsPrimary: SetIntegrationAsPrimary,
29-
private getFeatureFlag: GetFeatureFlagService
24+
private featureFlagService: FeatureFlagsService
3025
) {}
3126

3227
private async createEmailIntegration(command: CreateNovuIntegrationsCommand) {
@@ -110,14 +105,13 @@ export class CreateNovuIntegrations {
110105
});
111106

112107
if (inAppIntegrationCount === 0) {
113-
const isV2Enabled = await this.getFeatureFlag.getBoolean(
114-
GetFeatureFlagCommand.create({
115-
user: { _id: command.userId } as UserEntity,
116-
environment: { _id: command.environmentId } as EnvironmentEntity,
117-
organization: { _id: command.organizationId } as OrganizationEntity,
118-
key: FeatureFlagsKeysEnum.IS_V2_ENABLED,
119-
})
120-
);
108+
const isV2Enabled = await this.featureFlagService.getFlag({
109+
user: { _id: command.userId } as UserEntity,
110+
environment: { _id: command.environmentId } as EnvironmentEntity,
111+
organization: { _id: command.organizationId } as OrganizationEntity,
112+
key: FeatureFlagsKeysEnum.IS_V2_ENABLED,
113+
defaultValue: false,
114+
});
121115

122116
const name = isV2Enabled ? 'Novu Inbox' : 'Novu In-App';
123117
await this.createIntegration.execute(

apps/api/src/app/integrations/usecases/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
CalculateLimitNovuIntegration,
55
ConditionsFilter,
66
NormalizeVariables,
7-
getFeatureFlagService,
87
} from '@novu/application-generic';
98

109
import { GetWebhookSupportStatus } from './get-webhook-support-status/get-webhook-support-status.usecase';
@@ -36,5 +35,4 @@ export const USE_CASES = [
3635
SetIntegrationAsPrimary,
3736
CreateNovuIntegrations,
3837
NormalizeVariables,
39-
getFeatureFlagService,
4038
];

apps/api/src/app/integrations/usecases/update-integration/update-integration.usecase.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
AnalyticsService,
1010
buildIntegrationKey,
1111
encryptCredentials,
12-
GetFeatureFlagService,
13-
GetFeatureFlagCommand,
1412
InvalidateCacheService,
13+
FeatureFlagsService,
1514
} from '@novu/application-generic';
1615
import { ApiServiceLevelEnum, CHANNELS_WITH_PRIMARY, FeatureFlagsKeysEnum } from '@novu/shared';
1716

@@ -27,7 +26,7 @@ export class UpdateIntegration {
2726
private invalidateCache: InvalidateCacheService,
2827
private integrationRepository: IntegrationRepository,
2928
private analyticsService: AnalyticsService,
30-
private getFeatureFlag: GetFeatureFlagService,
29+
private featureFlagService: FeatureFlagsService,
3130
private communityOrganizationRepository: CommunityOrganizationRepository
3231
) {}
3332

@@ -153,12 +152,11 @@ export class UpdateIntegration {
153152
active: command.active,
154153
});
155154

156-
const isInvalidationDisabled = await this.getFeatureFlag.getBoolean(
157-
GetFeatureFlagCommand.create({
158-
organization: { _id: command.organizationId } as OrganizationEntity,
159-
key: FeatureFlagsKeysEnum.IS_INTEGRATION_INVALIDATION_DISABLED,
160-
})
161-
);
155+
const isInvalidationDisabled = await this.featureFlagService.getFlag({
156+
key: FeatureFlagsKeysEnum.IS_INTEGRATION_INVALIDATION_DISABLED,
157+
defaultValue: false,
158+
organization: { _id: command.organizationId } as OrganizationEntity,
159+
});
162160

163161
if (!isInvalidationDisabled) {
164162
await this.invalidateCache.invalidateQuery({

apps/api/src/app/messages/usecases/get-messages/get-messages.usecase.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable } from '@nestjs/common';
22
import { MessageEntity, MessageRepository, OrganizationEntity, SubscriberEntity } from '@novu/dal';
33
import { ActorTypeEnum, FeatureFlagsKeysEnum } from '@novu/shared';
44

5-
import { GetFeatureFlagService, GetFeatureFlagCommand } from '@novu/application-generic';
5+
import { FeatureFlagsService } from '@novu/application-generic';
66
import { GetMessagesCommand } from './get-messages.command';
77
import { GetSubscriber, GetSubscriberCommand } from '../../../subscribers/usecases/get-subscriber';
88

@@ -11,7 +11,7 @@ export class GetMessages {
1111
constructor(
1212
private messageRepository: MessageRepository,
1313
private getSubscriberUseCase: GetSubscriber,
14-
private getFeatureFlag: GetFeatureFlagService
14+
private featureFlagService: FeatureFlagsService
1515
) {}
1616

1717
async execute(command: GetMessagesCommand) {
@@ -59,12 +59,11 @@ export class GetMessages {
5959
}
6060
}
6161

62-
const isEnabled = await this.getFeatureFlag.getBoolean(
63-
GetFeatureFlagCommand.create({
64-
key: FeatureFlagsKeysEnum.IS_NEW_MESSAGES_API_RESPONSE_ENABLED,
65-
organization: { _id: command.organizationId } as OrganizationEntity,
66-
})
67-
);
62+
const isEnabled = await this.featureFlagService.getFlag({
63+
key: FeatureFlagsKeysEnum.IS_NEW_MESSAGES_API_RESPONSE_ENABLED,
64+
organization: { _id: command.organizationId } as OrganizationEntity,
65+
defaultValue: false,
66+
});
6867

6968
if (isEnabled) {
7069
return {

apps/api/src/app/rate-limiting/guards/throttler.guard.ts

+16-19
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import {
1010
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
1111
import { Reflector } from '@nestjs/core';
1212
import {
13-
GetFeatureFlagService,
14-
GetFeatureFlagCommand,
1513
Instrument,
1614
HttpRequestHeaderKeysEnum,
1715
HttpResponseHeaderKeysEnum,
16+
FeatureFlagsService,
1817
} from '@novu/application-generic';
1918
import {
2019
ApiAuthSchemeEnum,
@@ -45,7 +44,7 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
4544
@InjectThrottlerStorage() protected readonly storageService: ThrottlerStorage,
4645
reflector: Reflector,
4746
private evaluateApiRateLimit: EvaluateApiRateLimit,
48-
private getFeatureFlag: GetFeatureFlagService
47+
private featureFlagService: FeatureFlagsService
4948
) {
5049
super(options, storageService, reflector);
5150
}
@@ -73,14 +72,13 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
7372
const user = this.getReqUser(context);
7473
const { organizationId, environmentId, _id } = user;
7574

76-
const isEnabled = await this.getFeatureFlag.getBoolean(
77-
GetFeatureFlagCommand.create({
78-
environment: { _id: environmentId } as EnvironmentEntity,
79-
organization: { _id: organizationId } as OrganizationEntity,
80-
user: { _id } as UserEntity,
81-
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_ENABLED,
82-
})
83-
);
75+
const isEnabled = await this.featureFlagService.getFlag({
76+
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_ENABLED,
77+
defaultValue: false,
78+
environment: { _id: environmentId } as EnvironmentEntity,
79+
organization: { _id: organizationId } as OrganizationEntity,
80+
user: { _id } as UserEntity,
81+
});
8482

8583
return !isEnabled;
8684
}
@@ -128,14 +126,13 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
128126
* The purpose of the dry run is to allow us to observe how
129127
* the rate limiting would behave without actually enforcing it.
130128
*/
131-
const isDryRun = await this.getFeatureFlag.getBoolean(
132-
GetFeatureFlagCommand.create({
133-
environment: { _id: environmentId } as EnvironmentEntity,
134-
organization: { _id: organizationId } as OrganizationEntity,
135-
user: { _id } as UserEntity,
136-
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_DRY_RUN_ENABLED,
137-
})
138-
);
129+
const isDryRun = await this.featureFlagService.getFlag({
130+
environment: { _id: environmentId } as EnvironmentEntity,
131+
organization: { _id: organizationId } as OrganizationEntity,
132+
user: { _id } as UserEntity,
133+
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_DRY_RUN_ENABLED,
134+
defaultValue: false,
135+
});
139136

140137
res.header(HttpResponseHeaderKeysEnum.RATELIMIT_REMAINING, remaining);
141138
res.header(HttpResponseHeaderKeysEnum.RATELIMIT_LIMIT, limit);

apps/api/src/app/shared/framework/idempotency.interceptor.ts

+9-16
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ import {
1111
ServiceUnavailableException,
1212
UnprocessableEntityException,
1313
} from '@nestjs/common';
14-
import {
15-
CacheService,
16-
GetFeatureFlagService,
17-
GetFeatureFlagCommand,
18-
HttpResponseHeaderKeysEnum,
19-
Instrument,
20-
} from '@novu/application-generic';
14+
import { CacheService, HttpResponseHeaderKeysEnum, Instrument, FeatureFlagsService } from '@novu/application-generic';
2115
import { Observable, of, throwError } from 'rxjs';
2216
import { catchError, map } from 'rxjs/operators';
2317
import { createHash } from 'crypto';
@@ -42,7 +36,7 @@ const ALLOWED_METHODS = ['post', 'patch'];
4236
export class IdempotencyInterceptor implements NestInterceptor {
4337
constructor(
4438
private readonly cacheService: CacheService,
45-
private getFeatureFlag: GetFeatureFlagService
39+
private featureFlagService: FeatureFlagsService
4640
) {}
4741

4842
protected async isEnabled(context: ExecutionContext): Promise<boolean> {
@@ -54,14 +48,13 @@ export class IdempotencyInterceptor implements NestInterceptor {
5448
const user = this.getReqUser(context);
5549
const { organizationId, environmentId, _id } = user;
5650

57-
return await this.getFeatureFlag.getBoolean(
58-
GetFeatureFlagCommand.create({
59-
key: FeatureFlagsKeysEnum.IS_API_IDEMPOTENCY_ENABLED,
60-
environment: { _id: environmentId } as EnvironmentEntity,
61-
organization: { _id: organizationId } as OrganizationEntity,
62-
user: { _id } as UserEntity,
63-
})
64-
);
51+
return await this.featureFlagService.getFlag({
52+
key: FeatureFlagsKeysEnum.IS_API_IDEMPOTENCY_ENABLED,
53+
defaultValue: false,
54+
environment: { _id: environmentId } as EnvironmentEntity,
55+
organization: { _id: organizationId } as OrganizationEntity,
56+
user: { _id } as UserEntity,
57+
});
6558
}
6659

6760
@Instrument()

0 commit comments

Comments
 (0)