Skip to content

Commit 0ce1b7a

Browse files
committed
Allow configuring analytics API endpoint separate from flags API
1 parent d52fbac commit 0ce1b7a

File tree

4 files changed

+56
-28
lines changed

4 files changed

+56
-28
lines changed

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export {
22
AnalyticsProcessor,
3+
AnalyticsProcessorOptions,
34
FlagsmithAPIError,
45
FlagsmithClientError,
56
EnvironmentDataPollingManager,

sdk/analytics.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,52 @@
11
import { pino, Logger } from 'pino';
22
import { Fetch } from "./types.js";
3+
import { Flags } from "./models.js";
34

4-
const ANALYTICS_ENDPOINT = 'analytics/flags/';
5+
export const ANALYTICS_ENDPOINT = './analytics/flags/';
56

6-
// Used to control how often we send data(in seconds)
7+
/** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
78
const ANALYTICS_TIMER = 10;
89

10+
const DEFAULT_REQUEST_TIMEOUT_MS = 3000
11+
12+
export interface AnalyticsProcessorOptions {
13+
/** URL of the Flagsmith analytics events API endpoint
14+
* @example https://flagsmith.example.com/api/v1/analytics
15+
*/
16+
analyticsUrl?: string;
17+
/** Client-side key of the environment that analytics will be recorded for. **/
18+
environmentKey: string;
19+
/** Duration in milliseconds to wait for API requests to complete before timing out. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. **/
20+
requestTimeoutMs?: number;
21+
logger?: Logger;
22+
/** Custom {@link fetch} implementation to use for API requests. **/
23+
fetch?: Fetch
24+
25+
/** @deprecated Use {@link analyticsUrl} instead. **/
26+
baseApiUrl?: string;
27+
}
28+
29+
/**
30+
* Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
31+
*
32+
* Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
33+
* passed since the previous analytics API request was made (if any), or by calling {@link flush}.
34+
*
35+
* Data will stay in memory indefinitely until it can be successfully posted to the API.
36+
* @see https://docs.flagsmith.com/advanced-use/flag-analytics.
37+
*/
938
export class AnalyticsProcessor {
10-
private analyticsEndpoint: string;
39+
private analyticsUrl: string;
1140
private environmentKey: string;
1241
private lastFlushed: number;
1342
analyticsData: { [key: string]: any };
14-
private requestTimeoutMs: number = 3000;
43+
private requestTimeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS;
1544
private logger: Logger;
1645
private currentFlush: ReturnType<typeof fetch> | undefined;
1746
private customFetch: Fetch;
1847

19-
/**
20-
* AnalyticsProcessor is used to track how often individual Flags are evaluated within
21-
* the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
22-
*
23-
* @param data.environmentKey environment key obtained from the Flagsmith UI
24-
* @param data.baseApiUrl base api url to override when using self hosted version
25-
* @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
26-
given number of milliseconds
27-
*/
28-
constructor(data: { environmentKey: string; baseApiUrl: string; requestTimeoutMs?: number, logger?: Logger, fetch?: Fetch }) {
29-
this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
48+
constructor(data: AnalyticsProcessorOptions) {
49+
this.analyticsUrl = data.analyticsUrl || data.baseApiUrl + ANALYTICS_ENDPOINT;
3050
this.environmentKey = data.environmentKey;
3151
this.lastFlushed = Date.now();
3252
this.analyticsData = {};
@@ -35,15 +55,15 @@ export class AnalyticsProcessor {
3555
this.customFetch = data.fetch ?? fetch;
3656
}
3757
/**
38-
* Sends all the collected data to the api asynchronously and resets the timer
58+
* Try to flush pending collected data to the Flagsmith analytics API.
3959
*/
4060
async flush() {
4161
if (this.currentFlush || !Object.keys(this.analyticsData).length) {
4262
return;
4363
}
4464

4565
try {
46-
this.currentFlush = this.customFetch(this.analyticsEndpoint, {
66+
this.currentFlush = this.customFetch(this.analyticsUrl, {
4767
method: 'POST',
4868
body: JSON.stringify(this.analyticsData),
4969
signal: AbortSignal.timeout(this.requestTimeoutMs),
@@ -66,6 +86,11 @@ export class AnalyticsProcessor {
6686
this.lastFlushed = Date.now();
6787
}
6888

89+
/**
90+
* Track a single evaluation event for a feature.
91+
*
92+
* This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
93+
*/
6994
trackFeature(featureName: string) {
7095
this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
7196
if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {

sdk/index.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js'
55
import { IdentityModel } from '../flagsmith-engine/index.js';
66
import { TraitModel } from '../flagsmith-engine/index.js';
77

8-
import { AnalyticsProcessor } from './analytics.js';
8+
import {ANALYTICS_ENDPOINT, AnalyticsProcessor} from './analytics.js';
99
import { BaseOfflineHandler } from './offline_handlers.js';
1010
import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
1111

@@ -17,7 +17,7 @@ import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js'
1717
import { Fetch, FlagsmithCache, FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
1818
import { pino, Logger } from 'pino';
1919

20-
export { AnalyticsProcessor } from './analytics.js';
20+
export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
2121
export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
2222

2323
export { DefaultFlag, Flags } from './models.js';
@@ -30,6 +30,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
3030
export class Flagsmith {
3131
environmentKey?: string = undefined;
3232
apiUrl?: string = undefined;
33+
analyticsUrl?: string = undefined;
3334
customHeaders?: { [key: string]: any };
3435
agent?: Dispatcher;
3536
requestTimeoutMs?: number;
@@ -138,6 +139,7 @@ export class Flagsmith {
138139

139140
const apiUrl = data.apiUrl || DEFAULT_API_URL;
140141
this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
142+
this.analyticsUrl = this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href
141143
this.environmentFlagsUrl = `${this.apiUrl}flags/`;
142144
this.identitiesUrl = `${this.apiUrl}identities/`;
143145
this.environmentUrl = `${this.apiUrl}environment-document/`;
@@ -156,14 +158,14 @@ export class Flagsmith {
156158
this.updateEnvironment();
157159
}
158160

159-
this.analyticsProcessor = data.enableAnalytics
160-
? new AnalyticsProcessor({
161-
environmentKey: this.environmentKey,
162-
baseApiUrl: this.apiUrl,
163-
requestTimeoutMs: this.requestTimeoutMs,
164-
logger: this.logger
165-
})
166-
: undefined;
161+
if (data.enableAnalytics) {
162+
this.analyticsProcessor = new AnalyticsProcessor({
163+
environmentKey: this.environmentKey,
164+
analyticsUrl: this.analyticsUrl,
165+
requestTimeoutMs: this.requestTimeoutMs,
166+
logger: this.logger,
167+
})
168+
}
167169
}
168170
}
169171
/**

tests/sdk/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const fetch = vi.fn(global.fetch)
2424
export function analyticsProcessor() {
2525
return new AnalyticsProcessor({
2626
environmentKey: 'test-key',
27-
baseApiUrl: 'http://testUrl',
27+
analyticsUrl: 'http://testUrl/analytics/flags/',
2828
fetch,
2929
});
3030
}

0 commit comments

Comments
 (0)