Skip to content

Commit ace91db

Browse files
authored
feat(feature-flags): support quota limiting for feature flags (#1758)
* did the damn thing * clean up tests
1 parent 9b8e6af commit ace91db

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

src/__tests__/posthog-core.loaded.test.ts

+54-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('loaded() with flags', () => {
1515
...config,
1616
loaded: (ph) => {
1717
ph.capture = jest.fn()
18-
ph._send_request = jest.fn(({ callback }) => callback?.({ status: 200, json: {} }))
18+
ph._send_request = jest.fn(({ callback }) => callback?.({ statusCode: 200, json: {} }))
1919
ph._start_queue_if_opted_in = jest.fn()
2020

2121
jest.spyOn(ph.featureFlags, 'setGroupPropertiesForFlags')
@@ -106,4 +106,57 @@ describe('loaded() with flags', () => {
106106
expect(instance._send_request).toHaveBeenCalledTimes(1)
107107
})
108108
})
109+
110+
describe('quota limiting', () => {
111+
beforeEach(async () => {
112+
instance = await createPosthog()
113+
})
114+
115+
it.each([
116+
{
117+
name: 'does not process feature flags when quota limited',
118+
response: {
119+
quotaLimited: ['feature_flags'],
120+
featureFlags: { 'test-flag': true },
121+
},
122+
expectedCall: false,
123+
expectedArgs: undefined,
124+
},
125+
{
126+
name: 'processes feature flags when not quota limited',
127+
response: {
128+
featureFlags: { 'test-flag': true },
129+
},
130+
expectedCall: true,
131+
expectedArgs: { featureFlags: { 'test-flag': true } },
132+
},
133+
{
134+
name: 'processes feature flags when other resources are quota limited',
135+
response: {
136+
quotaLimited: ['recordings'],
137+
featureFlags: { 'test-flag': true },
138+
},
139+
expectedCall: true,
140+
expectedArgs: { quotaLimited: ['recordings'], featureFlags: { 'test-flag': true } },
141+
},
142+
])('$name', async ({ response, expectedCall, expectedArgs }) => {
143+
instance._send_request = jest.fn(({ callback }) =>
144+
callback?.({
145+
statusCode: 200,
146+
json: response,
147+
})
148+
)
149+
150+
const receivedFeatureFlagsSpy = jest.spyOn(instance.featureFlags, 'receivedFeatureFlags')
151+
152+
instance.featureFlags._callDecideEndpoint()
153+
jest.runAllTimers()
154+
155+
if (expectedCall) {
156+
expect(receivedFeatureFlagsSpy).toHaveBeenCalledWith(expectedArgs, false)
157+
} else {
158+
expect(receivedFeatureFlagsSpy).not.toHaveBeenCalled()
159+
}
160+
})
161+
})
109162
})

src/posthog-featureflags.ts

+14
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ type OverrideFeatureFlagsOptions =
104104
| FeatureFlagOverrides // set variants directly
105105
| FeatureFlagOverrideOptions
106106

107+
export enum QuotaLimitedResource {
108+
FeatureFlags = 'feature_flags',
109+
Recordings = 'recordings',
110+
}
111+
107112
export class PostHogFeatureFlags {
108113
_override_warning: boolean = false
109114
featureFlagEventHandlers: FeatureFlagsCallback[]
@@ -302,6 +307,15 @@ export class PostHogFeatureFlags {
302307
}
303308

304309
this._flagsLoadedFromRemote = !errorsLoading
310+
311+
if (response.json && response.json.quotaLimited?.includes(QuotaLimitedResource.FeatureFlags)) {
312+
// log a warning and then early return
313+
logger.warn(
314+
'You have hit your feature flags quota limit, and will not be able to load feature flags until the quota is reset. Please visit https://posthog.com/docs/billing/limits-alerts to learn more.'
315+
)
316+
return
317+
}
318+
305319
this.receivedFeatureFlags(response.json ?? {}, errorsLoading)
306320

307321
if (this._additionalReloadRequested) {

0 commit comments

Comments
 (0)