diff --git a/packages/backend-core/src/events/processors/posthog/rateLimiting.ts b/packages/backend-core/src/events/processors/posthog/rateLimiting.ts index f61d34d0a4..e2a02eb28e 100644 --- a/packages/backend-core/src/events/processors/posthog/rateLimiting.ts +++ b/packages/backend-core/src/events/processors/posthog/rateLimiting.ts @@ -1,6 +1,7 @@ import { Event } from "@budibase/types" import { CacheKeys, TTL } from "../../../cache/generic" import * as cache from "../../../cache/generic" +import * as context from "../../../context" type RateLimitedEvent = | Event.SERVED_BUILDER @@ -15,6 +16,10 @@ const isRateLimited = (event: Event): event is RateLimitedEvent => { ) } +const isPerApp = (event: RateLimitedEvent) => { + return event === Event.SERVED_APP_PREVIEW || event === Event.SERVED_APP +} + interface EventProperties { timestamp: number } @@ -69,7 +74,11 @@ export const limited = async (event: Event): Promise => { } const eventKey = (event: RateLimitedEvent) => { - return `${CacheKeys.EVENTS_RATE_LIMIT}:${event}` + let key = `${CacheKeys.EVENTS_RATE_LIMIT}:${event}` + if (isPerApp(event)) { + key = key + context.getAppId() + } + return key } const readEvent = async (event: RateLimitedEvent) => { @@ -81,8 +90,7 @@ const recordEvent = async ( event: RateLimitedEvent, properties: EventProperties ) => { - const key = `${CacheKeys.EVENTS_RATE_LIMIT}:${event}` - + const key = eventKey(event) const limit = RATE_LIMITS[event] let ttl switch (limit) { diff --git a/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts b/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts index 7ddee7279b..d14b697966 100644 --- a/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +++ b/packages/backend-core/src/events/processors/posthog/tests/PosthogProcessor.spec.ts @@ -1,7 +1,10 @@ +import "../../../../../tests/utilities/TestConfiguration" import PosthogProcessor from "../PosthogProcessor" import { Event, IdentityType, Hosting } from "@budibase/types" const tk = require("timekeeper") -import * as Cache from "../../../../cache/generic" +import * as cache from "../../../../cache/generic" +import { CacheKeys } from "../../../../cache/generic" +import * as context from "../../../../context" const newIdentity = () => { return { @@ -13,8 +16,11 @@ const newIdentity = () => { } describe("PosthogProcessor", () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks() + await cache.bustCache( + `${CacheKeys.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` + ) }) describe("processEvent", () => { @@ -71,7 +77,7 @@ describe("PosthogProcessor", () => { tk.freeze(new Date(2022, 0, 3, 6, 0)) await processor.processEvent(Event.SERVED_BUILDER, identity, properties) - expect(processor.posthog.capture).toHaveBeenCalledTimes(4) + expect(processor.posthog.capture).toHaveBeenCalledTimes(3) }) it("sends event again after cache expires", async () => { @@ -82,8 +88,8 @@ describe("PosthogProcessor", () => { tk.freeze(new Date(2022, 0, 1, 14, 0)) await processor.processEvent(Event.SERVED_BUILDER, identity, properties) - await Cache.bustCache( - `${Cache.CacheKeys.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` + await cache.bustCache( + `${CacheKeys.EVENTS_RATE_LIMIT}:${Event.SERVED_BUILDER}` ) tk.freeze(new Date(2022, 0, 1, 14, 0)) @@ -91,6 +97,49 @@ describe("PosthogProcessor", () => { expect(processor.posthog.capture).toHaveBeenCalledTimes(2) }) + + it("sends per app events once per day per app", async () => { + const processor = new PosthogProcessor("test") + const identity = newIdentity() + const properties = {} + + const runAppEvents = async (appId: string) => { + await context.doInAppContext(appId, async () => { + tk.freeze(new Date(2022, 0, 1, 14, 0)) + await processor.processEvent(Event.SERVED_APP, identity, properties) + await processor.processEvent( + Event.SERVED_APP_PREVIEW, + identity, + properties + ) + + // go forward one hour - should be ignored + tk.freeze(new Date(2022, 0, 1, 15, 0)) + await processor.processEvent(Event.SERVED_APP, identity, properties) + await processor.processEvent( + Event.SERVED_APP_PREVIEW, + identity, + properties + ) + + // go forward into next day + tk.freeze(new Date(2022, 0, 2, 9, 0)) + + await processor.processEvent(Event.SERVED_APP, identity, properties) + await processor.processEvent( + Event.SERVED_APP_PREVIEW, + identity, + properties + ) + }) + } + + await runAppEvents("app_1") + expect(processor.posthog.capture).toHaveBeenCalledTimes(4) + + await runAppEvents("app_2") + expect(processor.posthog.capture).toHaveBeenCalledTimes(8) + }) }) }) })