Skip to content

Commit 9cf05dc

Browse files
authored
feat: impact metric flag context (#749)
1 parent 864626e commit 9cf05dc

File tree

4 files changed

+70
-19
lines changed

4 files changed

+70
-19
lines changed

src/impact-metrics/metric-client.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { EventEmitter } from 'stream';
22
import { StaticContext, Unleash, UnleashEvents } from '../unleash';
3-
import { ImpactMetricRegistry } from './metric-types';
3+
import { ImpactMetricRegistry, MetricFlagContext, MetricLabels } from './metric-types';
44
import { extractEnvironmentFromCustomHeaders } from './environment-resolver';
5+
import Client from '../client';
56

67
export class MetricsAPI extends EventEmitter {
78
constructor(
89
private metricRegistry: ImpactMetricRegistry,
10+
private variantResolver: Pick<Client, 'forceGetVariant'>,
911
private staticContext: StaticContext,
1012
) {
1113
super();
@@ -29,7 +31,25 @@ export class MetricsAPI extends EventEmitter {
2931
this.metricRegistry.gauge({ name, help, labelNames });
3032
}
3133

32-
incrementCounter(name: string, value?: number, featureName?: string): void {
34+
private getFlagLabels(flagContext?: MetricFlagContext): MetricLabels {
35+
let flagLabels: MetricLabels = {};
36+
if (flagContext) {
37+
for (const flag of flagContext.flagNames) {
38+
const variant = this.variantResolver.forceGetVariant(flag, flagContext.context);
39+
40+
if (variant.enabled) {
41+
flagLabels[flag] = variant.name;
42+
} else if (variant.feature_enabled) {
43+
flagLabels[flag] = 'enabled';
44+
} else {
45+
flagLabels[flag] = 'disabled';
46+
}
47+
}
48+
}
49+
return flagLabels;
50+
}
51+
52+
incrementCounter(name: string, value?: number, flagContext?: MetricFlagContext): void {
3353
const counter = this.metricRegistry.getCounter(name);
3454
if (!counter) {
3555
this.emit(
@@ -39,23 +59,27 @@ export class MetricsAPI extends EventEmitter {
3959
return;
4060
}
4161

62+
const flagLabels = this.getFlagLabels(flagContext);
63+
4264
const labels = {
43-
...(featureName ? { featureName } : {}),
65+
...flagLabels,
4466
...this.staticContext,
4567
};
4668

4769
counter.inc(value, labels);
4870
}
4971

50-
updateGauge(name: string, value: number, featureName?: string): void {
72+
updateGauge(name: string, value: number, flagContext?: MetricFlagContext): void {
5173
const gauge = this.metricRegistry.getGauge(name);
5274
if (!gauge) {
5375
this.emit(UnleashEvents.Warn, `Gauge ${name} not defined, this gauge will not be updated.`);
5476
return;
5577
}
5678

79+
const flagLabels = this.getFlagLabels(flagContext);
80+
5781
const labels = {
58-
...(featureName ? { featureName } : {}),
82+
...flagLabels,
5983
...this.staticContext,
6084
};
6185

@@ -79,6 +103,6 @@ export class UnleashMetricClient extends Unleash {
79103
}
80104
}
81105

82-
this.impactMetrics = new MetricsAPI(this.metricRegistry, metricsContext);
106+
this.impactMetrics = new MetricsAPI(this.metricRegistry, this.client, metricsContext);
83107
}
84108
}

src/impact-metrics/metric-types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Context } from '../context';
2+
13
type MetricType = 'counter' | 'gauge';
24
type LabelValuesKey = string;
35

@@ -195,3 +197,8 @@ export interface MetricOptions {
195197
help: string;
196198
labelNames?: string[];
197199
}
200+
201+
export interface MetricFlagContext {
202+
flagNames: string[];
203+
context: Context;
204+
}

src/test/impact-metrics/metric-client.test.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
import { MetricsAPI } from '../../impact-metrics/metric-client';
22

33
import test from 'ava';
4+
import Client from '../../client';
5+
import { MetricLabels } from '../../impact-metrics/metric-types';
6+
7+
const fakeVariantResolver = (
8+
variantName = 'disabled',
9+
feature_enabled = true,
10+
): Pick<Client, 'forceGetVariant'> => ({
11+
forceGetVariant: () => ({
12+
name: variantName,
13+
feature_enabled,
14+
enabled: variantName !== 'disabled',
15+
featureEnabled: feature_enabled,
16+
}),
17+
});
418

519
test('should not register a counter with empty name or help', (t) => {
620
let counterRegistered = false;
@@ -12,7 +26,7 @@ test('should not register a counter with empty name or help', (t) => {
1226
};
1327

1428
const staticContext = { appName: 'my-app', environment: 'dev' };
15-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
29+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
1630

1731
api.defineCounter('some_name', '');
1832
t.false(counterRegistered, 'Counter should not be registered with empty help');
@@ -31,7 +45,7 @@ test('should register a counter with valid name and help', (t) => {
3145
};
3246

3347
const staticContext = { appName: 'my-app', environment: 'dev' };
34-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
48+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
3549

3650
api.defineCounter('valid_name', 'Valid help text');
3751
t.true(counterRegistered, 'Counter should be registered with valid name and help');
@@ -47,7 +61,7 @@ test('should not register a gauge with empty name or help', (t) => {
4761
};
4862

4963
const staticContext = { appName: 'my-app', environment: 'dev' };
50-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
64+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
5165

5266
api.defineGauge('some_name', '');
5367
t.false(gaugeRegistered, 'Gauge should not be registered with empty help');
@@ -66,18 +80,20 @@ test('should register a gauge with valid name and help', (t) => {
6680
};
6781

6882
const staticContext = { appName: 'my-app', environment: 'dev' };
69-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
83+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
7084

7185
api.defineGauge('valid_name', 'Valid help text');
7286
t.true(gaugeRegistered, 'Gauge should be registered with valid name and help');
7387
});
7488

7589
test('should increment counter with valid parameters', (t) => {
7690
let counterIncremented = false;
91+
let recordedLabels: MetricLabels = {};
7792

7893
const fakeCounter = {
79-
inc: () => {
94+
inc: (_value: number, labels: MetricLabels) => {
8095
counterIncremented = true;
96+
recordedLabels = labels;
8197
},
8298
};
8399

@@ -86,18 +102,21 @@ test('should increment counter with valid parameters', (t) => {
86102
};
87103

88104
const staticContext = { appName: 'my-app', environment: 'dev' };
89-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
105+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
90106

91-
api.incrementCounter('valid_counter', 5, 'featureX');
107+
api.incrementCounter('valid_counter', 5, { flagNames: ['featureX'], context: staticContext });
92108
t.true(counterIncremented, 'Counter should be incremented with valid parameters');
109+
t.deepEqual(recordedLabels, { appName: 'my-app', environment: 'dev', featureX: 'enabled' });
93110
});
94111

95112
test('should set gauge with valid parameters', (t) => {
96113
let gaugeSet = false;
114+
let recordedLabels: MetricLabels = {};
97115

98116
const fakeGauge = {
99-
set: () => {
117+
set: (_value: number, labels: MetricLabels) => {
100118
gaugeSet = true;
119+
recordedLabels = labels;
101120
},
102121
};
103122

@@ -106,10 +125,11 @@ test('should set gauge with valid parameters', (t) => {
106125
};
107126

108127
const staticContext = { appName: 'my-app', environment: 'dev' };
109-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
128+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver('variantY'), staticContext);
110129

111-
api.updateGauge('valid_gauge', 10, 'featureY');
130+
api.updateGauge('valid_gauge', 10, { flagNames: ['featureY'], context: staticContext });
112131
t.true(gaugeSet, 'Gauge should be set with valid parameters');
132+
t.deepEqual(recordedLabels, { appName: 'my-app', environment: 'dev', featureY: 'variantY' });
113133
});
114134

115135
test('defining a counter automatically sets label names', (t) => {
@@ -127,7 +147,7 @@ test('defining a counter automatically sets label names', (t) => {
127147
};
128148

129149
const staticContext = { appName: 'my-app', environment: 'dev' };
130-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
150+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver(), staticContext);
131151

132152
api.defineCounter('test_counter', 'Test help text');
133153
t.true(counterRegistered, 'Counter should be registered');
@@ -148,7 +168,7 @@ test('defining a gauge automatically sets label names', (t) => {
148168
};
149169

150170
const staticContext = { appName: 'my-app', environment: 'dev' };
151-
const api = new MetricsAPI(fakeRegistry as any, staticContext);
171+
const api = new MetricsAPI(fakeRegistry as any, fakeVariantResolver('variantX'), staticContext);
152172

153173
api.defineGauge('test_gauge', 'Test help text');
154174
t.true(gaugeRegistered, 'Gauge should be registered');

src/unleash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class Unleash extends EventEmitter {
4141

4242
private repository: RepositoryInterface;
4343

44-
private client: Client;
44+
protected client: Client;
4545

4646
private metrics: Metrics;
4747

0 commit comments

Comments
 (0)