Skip to content

Commit d9ffcec

Browse files
feat(goff web): Support tracking events (#1268)
Signed-off-by: Thomas Poignant <[email protected]>
1 parent 136b1c9 commit d9ffcec

File tree

6 files changed

+338
-209
lines changed

6 files changed

+338
-209
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Logger } from '@openfeature/web-sdk';
2+
import { ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions, TrackingEvent } from './model';
3+
import { GoffApiController } from './controller/goff-api';
4+
import { CollectorError } from './errors/collector-error';
5+
import { copy } from 'copy-anything';
6+
7+
type Timer = ReturnType<typeof setInterval>;
8+
9+
export class CollectorManager {
10+
// bgSchedulerId contains the id of the setInterval that is running.
11+
private bgScheduler?: Timer;
12+
// dataCollectorBuffer contains all the FeatureEvents that we need to send to the relay-proxy for data collection.
13+
private dataCollectorBuffer?: Array<FeatureEvent<any> | TrackingEvent>;
14+
// dataFlushInterval interval time (in millisecond) we use to call the relay proxy to collect data.
15+
private readonly dataFlushInterval: number;
16+
// logger is the Open Feature logger to use
17+
private logger?: Logger;
18+
// dataCollectorMetadata are the metadata used when calling the data collector endpoint
19+
private readonly dataCollectorMetadata: Record<string, ExporterMetadataValue>;
20+
21+
private readonly goffApiController: GoffApiController;
22+
23+
constructor(options: GoFeatureFlagWebProviderOptions, logger?: Logger) {
24+
this.dataFlushInterval = options.dataFlushInterval || 1000 * 60;
25+
this.logger = logger;
26+
this.goffApiController = new GoffApiController(options);
27+
28+
this.dataCollectorMetadata = {
29+
provider: 'web',
30+
openfeature: true,
31+
...options.exporterMetadata,
32+
};
33+
}
34+
35+
init() {
36+
this.bgScheduler = setInterval(async () => await this.callGoffDataCollection(), this.dataFlushInterval);
37+
this.dataCollectorBuffer = [];
38+
}
39+
40+
async close() {
41+
clearInterval(this.bgScheduler);
42+
// We call the data collector with what is still in the buffer.
43+
await this.callGoffDataCollection();
44+
}
45+
46+
add(event: FeatureEvent<any> | TrackingEvent) {
47+
if (this.dataCollectorBuffer) {
48+
this.dataCollectorBuffer.push(event);
49+
}
50+
}
51+
52+
/**
53+
* callGoffDataCollection is a function called periodically to send the usage of the flag to the
54+
* central service in charge of collecting the data.
55+
*/
56+
async callGoffDataCollection() {
57+
const dataToSend = copy(this.dataCollectorBuffer) || [];
58+
this.dataCollectorBuffer = [];
59+
try {
60+
await this.goffApiController.collectData(dataToSend, this.dataCollectorMetadata);
61+
} catch (e) {
62+
if (!(e instanceof CollectorError)) {
63+
throw e;
64+
}
65+
this.logger?.error(e);
66+
// if we have an issue calling the collector, we put the data back in the buffer
67+
this.dataCollectorBuffer = [...this.dataCollectorBuffer, ...dataToSend];
68+
return;
69+
}
70+
}
71+
}

libs/providers/go-feature-flag-web/src/lib/controller/goff-api.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { DataCollectorRequest, ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions } from '../model';
1+
import {
2+
DataCollectorRequest,
3+
ExporterMetadataValue,
4+
FeatureEvent,
5+
GoFeatureFlagWebProviderOptions,
6+
TrackingEvent,
7+
} from '../model';
28
import { CollectorError } from '../errors/collector-error';
39

410
export class GoffApiController {
@@ -15,7 +21,10 @@ export class GoffApiController {
1521
this.options = options;
1622
}
1723

18-
async collectData(events: FeatureEvent<any>[], dataCollectorMetadata: Record<string, ExporterMetadataValue>) {
24+
async collectData(
25+
events: Array<FeatureEvent<any> | TrackingEvent>,
26+
dataCollectorMetadata: Record<string, ExporterMetadataValue>,
27+
) {
1928
if (events?.length === 0) {
2029
return;
2130
}
Lines changed: 7 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,14 @@
1-
import { EvaluationDetails, FlagValue, Hook, HookContext, Logger } from '@openfeature/web-sdk';
2-
import { ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions } from './model';
3-
import { copy } from 'copy-anything';
4-
import { CollectorError } from './errors/collector-error';
5-
import { GoffApiController } from './controller/goff-api';
1+
import { EvaluationDetails, FlagValue, Hook, HookContext } from '@openfeature/web-sdk';
2+
import { CollectorManager } from './collector-manager';
63

74
const defaultTargetingKey = 'undefined-targetingKey';
85
type Timer = ReturnType<typeof setInterval>;
96

107
export class GoFeatureFlagDataCollectorHook implements Hook {
11-
// bgSchedulerId contains the id of the setInterval that is running.
12-
private bgScheduler?: Timer;
13-
// dataCollectorBuffer contains all the FeatureEvents that we need to send to the relay-proxy for data collection.
14-
private dataCollectorBuffer?: FeatureEvent<any>[];
15-
// dataFlushInterval interval time (in millisecond) we use to call the relay proxy to collect data.
16-
private readonly dataFlushInterval: number;
17-
// dataCollectorMetadata are the metadata used when calling the data collector endpoint
18-
private readonly dataCollectorMetadata: Record<string, ExporterMetadataValue>;
19-
private readonly goffApiController: GoffApiController;
20-
// logger is the Open Feature logger to use
21-
private logger?: Logger;
8+
private collectorManagger?: CollectorManager;
229

23-
constructor(options: GoFeatureFlagWebProviderOptions, logger?: Logger) {
24-
this.dataFlushInterval = options.dataFlushInterval || 1000 * 60;
25-
this.logger = logger;
26-
this.goffApiController = new GoffApiController(options);
27-
this.dataCollectorMetadata = {
28-
provider: 'web',
29-
openfeature: true,
30-
...options.exporterMetadata,
31-
};
32-
}
33-
34-
init() {
35-
this.bgScheduler = setInterval(async () => await this.callGoffDataCollection(), this.dataFlushInterval);
36-
this.dataCollectorBuffer = [];
37-
}
38-
39-
async close() {
40-
clearInterval(this.bgScheduler);
41-
// We call the data collector with what is still in the buffer.
42-
await this.callGoffDataCollection();
43-
}
44-
45-
/**
46-
* callGoffDataCollection is a function called periodically to send the usage of the flag to the
47-
* central service in charge of collecting the data.
48-
*/
49-
async callGoffDataCollection() {
50-
const dataToSend = copy(this.dataCollectorBuffer) || [];
51-
this.dataCollectorBuffer = [];
52-
try {
53-
await this.goffApiController.collectData(dataToSend, this.dataCollectorMetadata);
54-
} catch (e) {
55-
if (!(e instanceof CollectorError)) {
56-
throw e;
57-
}
58-
this.logger?.error(e);
59-
// if we have an issue calling the collector, we put the data back in the buffer
60-
this.dataCollectorBuffer = [...this.dataCollectorBuffer, ...dataToSend];
61-
return;
62-
}
10+
constructor(collectorManager: CollectorManager) {
11+
this.collectorManagger = collectorManager;
6312
}
6413

6514
after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>) {
@@ -74,7 +23,7 @@ export class GoFeatureFlagDataCollectorHook implements Hook {
7423
userKey: hookContext.context.targetingKey || defaultTargetingKey,
7524
source: 'PROVIDER_CACHE',
7625
};
77-
this.dataCollectorBuffer?.push(event);
26+
this.collectorManagger?.add(event);
7827
}
7928

8029
error(hookContext: HookContext) {
@@ -89,6 +38,6 @@ export class GoFeatureFlagDataCollectorHook implements Hook {
8938
userKey: hookContext.context.targetingKey || defaultTargetingKey,
9039
source: 'PROVIDER_CACHE',
9140
};
92-
this.dataCollectorBuffer?.push(event);
41+
this.collectorManagger?.add(event);
9342
}
9443
}

0 commit comments

Comments
 (0)