Skip to content

Commit 265dde4

Browse files
committed
Merge extensible feature flags from telemetry-5-export
2 parents 86d7b8d + c3494c1 commit 265dde4

File tree

1 file changed

+46
-32
lines changed

1 file changed

+46
-32
lines changed

lib/telemetry/FeatureFlagCache.ts

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import { buildUrl } from './urlUtils';
2222

2323
/**
2424
* Context holding feature flag state for a specific host.
25+
* Stores all feature flags from the server for extensibility.
2526
*/
2627
export interface FeatureFlagContext {
27-
telemetryEnabled?: boolean;
28+
flags: Map<string, string>; // All feature flags from server (extensible for future flags)
2829
lastFetched?: Date;
2930
refCount: number;
3031
cacheDuration: number; // 15 minutes in ms
@@ -54,6 +55,7 @@ export default class FeatureFlagCache {
5455
let ctx = this.contexts.get(host);
5556
if (!ctx) {
5657
ctx = {
58+
flags: new Map<string, string>(),
5759
refCount: 0,
5860
cacheDuration: this.CACHE_DURATION_MS,
5961
};
@@ -78,10 +80,14 @@ export default class FeatureFlagCache {
7880
}
7981

8082
/**
81-
* Checks if telemetry is enabled for the host.
83+
* Generic method to check if a feature flag is enabled.
8284
* Uses cached value if available and not expired.
85+
*
86+
* @param host The host to check
87+
* @param flagName The feature flag name to query
88+
* @returns true if flag is enabled (value is "true"), false otherwise
8389
*/
84-
async isTelemetryEnabled(host: string): Promise<boolean> {
90+
async isFeatureEnabled(host: string, flagName: string): Promise<boolean> {
8591
const logger = this.context.getLogger();
8692
const ctx = this.contexts.get(host);
8793

@@ -93,26 +99,36 @@ export default class FeatureFlagCache {
9399

94100
if (isExpired) {
95101
try {
96-
// Fetch feature flag from server
97-
ctx.telemetryEnabled = await this.fetchFeatureFlag(host);
102+
// Fetch all feature flags from server
103+
await this.fetchFeatureFlags(host);
98104
ctx.lastFetched = new Date();
99105
} catch (error: any) {
100106
// Log at debug level only, never propagate exceptions
101-
logger.log(LogLevel.debug, `Error fetching feature flag: ${error.message}`);
107+
logger.log(LogLevel.debug, `Error fetching feature flags: ${error.message}`);
102108
}
103109
}
104110

105-
return ctx.telemetryEnabled ?? false;
111+
// Get flag value and parse as boolean
112+
const value = ctx.flags.get(flagName);
113+
return value?.toLowerCase() === 'true';
114+
}
115+
116+
/**
117+
* Convenience method to check if telemetry is enabled for the host.
118+
* Uses cached value if available and not expired.
119+
*/
120+
async isTelemetryEnabled(host: string): Promise<boolean> {
121+
return this.isFeatureEnabled(host, this.FEATURE_FLAG_NAME);
106122
}
107123

108124
/**
109-
* Fetches feature flag from server using connector-service API.
110-
* Calls GET /api/2.0/connector-service/feature-flags/OSS_NODEJS/{version}
125+
* Fetches all feature flags from server using connector-service API.
126+
* Calls GET /api/2.0/connector-service/feature-flags/NODEJS/{version}
127+
* Stores all flags in the context for extensibility.
111128
*
112-
* @param host The host to fetch feature flag for
113-
* @returns true if feature flag is enabled, false otherwise
129+
* @param host The host to fetch feature flags for
114130
*/
115-
private async fetchFeatureFlag(host: string): Promise<boolean> {
131+
private async fetchFeatureFlags(host: string): Promise<void> {
116132
const logger = this.context.getLogger();
117133

118134
try {
@@ -143,41 +159,39 @@ export default class FeatureFlagCache {
143159
});
144160

145161
if (!response.ok) {
146-
logger.log(LogLevel.debug, `Feature flag fetch failed: ${response.status} ${response.statusText}`);
147-
return false;
162+
logger.log(LogLevel.debug, `Feature flags fetch failed: ${response.status} ${response.statusText}`);
163+
return;
148164
}
149165

150166
// Parse response JSON
151167
const data: any = await response.json();
152168

153169
// Response format: { flags: [{ name: string, value: string }], ttl_seconds?: number }
154170
if (data && data.flags && Array.isArray(data.flags)) {
155-
// Update cache duration if TTL provided
156171
const ctx = this.contexts.get(host);
157-
if (ctx && data.ttl_seconds) {
158-
ctx.cacheDuration = data.ttl_seconds * 1000; // Convert to milliseconds
159-
logger.log(LogLevel.debug, `Updated cache duration to ${data.ttl_seconds} seconds`);
172+
if (!ctx) {
173+
return;
174+
}
175+
176+
// Clear existing flags and store all flags from response
177+
ctx.flags.clear();
178+
for (const flag of data.flags) {
179+
if (flag.name && flag.value !== undefined) {
180+
ctx.flags.set(flag.name, String(flag.value));
181+
}
160182
}
161183

162-
// Look for our specific feature flag
163-
const flag = data.flags.find((f: any) => f.name === this.FEATURE_FLAG_NAME);
184+
logger.log(LogLevel.debug, `Stored ${ctx.flags.size} feature flags from server`);
164185

165-
if (flag) {
166-
// Parse boolean value (can be string "true"/"false")
167-
const value = String(flag.value).toLowerCase();
168-
const enabled = value === 'true';
169-
logger.log(LogLevel.debug, `Feature flag ${this.FEATURE_FLAG_NAME}: ${enabled}`);
170-
return enabled;
186+
// Update cache duration if TTL provided
187+
if (data.ttl_seconds) {
188+
ctx.cacheDuration = data.ttl_seconds * 1000; // Convert to milliseconds
189+
logger.log(LogLevel.debug, `Updated cache duration to ${data.ttl_seconds} seconds`);
171190
}
172191
}
173-
174-
// Feature flag not found in response, default to false
175-
logger.log(LogLevel.debug, `Feature flag ${this.FEATURE_FLAG_NAME} not found in response`);
176-
return false;
177192
} catch (error: any) {
178193
// Log at debug level only, never propagate exceptions
179-
logger.log(LogLevel.debug, `Error fetching feature flag from ${host}: ${error.message}`);
180-
return false;
194+
logger.log(LogLevel.debug, `Error fetching feature flags from ${host}: ${error.message}`);
181195
}
182196
}
183197

0 commit comments

Comments
 (0)