Skip to content

Commit

Permalink
feat: make inital metrics interval configurable. (#203)
Browse files Browse the repository at this point in the history
Co-authored-by: Simon Hornby <liquidwicked64@gmail.com>
  • Loading branch information
ivarconr and sighphyre committed May 7, 2024
1 parent f4299a8 commit 0f95cd1
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ The Unleash SDK takes the following options:
| appName | yes | n/a | The name of the application using this SDK. Will be used as part of the metrics sent to Unleash Proxy. Will also be part of the Unleash Context. |
| refreshInterval | no | `30` | How often, in seconds, the SDK should check for updated toggle configuration. If set to 0 will disable checking for updates |
| disableRefresh | no | `false` | If set to true, the client will not check for updated toggle configuration |
| metricsInterval | no | `60` | How often, in seconds, the SDK should send usage metrics back to Unleash Proxy |
| metricsInterval | no | `60` | How often, in seconds, the SDK should send usage metrics back to Unleash Proxy. It will be started after the initial metrics report, which is sent after the configured `metricsIntervalInitial` |
| metricsIntervalInitial | no | `2` | How long the SDK should wait for the first metrics report back to the Unleash API. If you want to disable the initial metrics call you can set it to 0. |
| disableMetrics | no | `false` | Set this option to `true` if you want to disable usage metrics |
| storageProvider | no | `LocalStorageProvider` in browser, `InMemoryStorageProvider` otherwise | Allows you to inject a custom storeProvider |
| fetch | no | `window.fetch` or global `fetch` | Allows you to override the fetch implementation to use. Useful in Node.js environments where you can inject `node-fetch` |
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface IConfig extends IStaticContext {
disableRefresh?: boolean;
refreshInterval?: number;
metricsInterval?: number;
metricsIntervalInitial?: number;
disableMetrics?: boolean;
storageProvider?: IStorageProvider;
context?: IMutableContext;
Expand Down Expand Up @@ -153,6 +154,7 @@ export class UnleashClient extends TinyEmitter {
disableRefresh = false,
refreshInterval = 30,
metricsInterval = 30,
metricsIntervalInitial = 2,
disableMetrics = false,
appName,
environment = 'default',
Expand Down Expand Up @@ -232,6 +234,7 @@ export class UnleashClient extends TinyEmitter {
fetch,
headerName,
customHeaders,
metricsIntervalInitial,
});
}

Expand Down
81 changes: 81 additions & 0 deletions src/metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ test('should be disabled by flag disableMetrics', async () => {
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 0,
});

metrics.count('foo', true);
Expand All @@ -40,6 +41,7 @@ test('should send metrics', async () => {
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 0,
});

metrics.count('foo', true);
Expand Down Expand Up @@ -76,6 +78,7 @@ test('should send metrics with custom auth header', async () => {
clientKey: '123',
fetch: fetchMock,
headerName: 'NotAuthorization',
metricsIntervalInitial: 0,
});

metrics.count('foo', true);
Expand All @@ -99,6 +102,7 @@ test('Should send initial metrics after 2 seconds', () => {
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 2,
});

metrics.start();
Expand All @@ -112,6 +116,81 @@ test('Should send initial metrics after 2 seconds', () => {
expect(fetchMock.mock.calls.length).toEqual(1);
});

test('Should send initial metrics after 20 seconds, when metricsIntervalInitial is higher than metricsInterval', () => {
const metrics = new Metrics({
onError: console.error,
appName: 'test',
metricsInterval: 5,
disableMetrics: false,
url: 'http://localhost:3000',
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 20,
});

metrics.start();

metrics.count('foo', true);
metrics.count('foo', true);
metrics.count('foo', false);
metrics.count('bar', false);
// Account for 20 second timeout before the set interval starts
jest.advanceTimersByTime(20000);
expect(fetchMock.mock.calls.length).toEqual(1);
});

test('Should send metrics for initial and after metrics interval', () => {
const metrics = new Metrics({
onError: console.error,
appName: 'test',
metricsInterval: 5,
disableMetrics: false,
url: 'http://localhost:3000',
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 2,
});

metrics.start();

metrics.count('foo', true);
metrics.count('foo', true);
metrics.count('foo', false);
metrics.count('bar', false);
// Account for 2 second timeout before the set interval starts
jest.advanceTimersByTime(2000);
metrics.count('foo', false);
metrics.count('bar', false);
jest.advanceTimersByTime(5000);
expect(fetchMock.mock.calls.length).toEqual(2);
});

test('Should not send initial metrics if disabled', () => {
const metrics = new Metrics({
onError: console.error,
appName: 'test',
metricsInterval: 5,
disableMetrics: false,
url: 'http://localhost:3000',
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 0,
});

metrics.start();

metrics.count('foo', true);
metrics.count('foo', true);
metrics.count('foo', false);
metrics.count('bar', false);
// Account for 2 second timeout before the set interval starts
jest.advanceTimersByTime(2000);
expect(fetchMock.mock.calls.length).toEqual(0);
});

test('should send metrics based on timer interval', async () => {
const metrics = new Metrics({
onError: console.error,
Expand All @@ -122,6 +201,7 @@ test('should send metrics based on timer interval', async () => {
clientKey: '123',
fetch: fetchMock,
headerName: 'Authorization',
metricsIntervalInitial: 2,
});

metrics.start();
Expand Down Expand Up @@ -162,6 +242,7 @@ describe('Custom headers for metrics', () => {
fetch: fetchMock,
headerName: 'Authorization',
customHeaders,
metricsIntervalInitial: 2,
});

metrics.count('foo', true);
Expand Down
15 changes: 11 additions & 4 deletions src/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface MetricsOptions {
fetch: any;
headerName: string;
customHeaders?: Record<string, string>;
metricsIntervalInitial: number;
}

interface VariantBucket {
Expand Down Expand Up @@ -51,6 +52,7 @@ export default class Metrics {
private fetch: any;
private headerName: string;
private customHeaders: Record<string, string>;
private metricsIntervalInitial: number;

constructor({
onError,
Expand All @@ -63,11 +65,13 @@ export default class Metrics {
fetch,
headerName,
customHeaders = {},
metricsIntervalInitial,
}: MetricsOptions) {
this.onError = onError;
this.onSent = onSent || doNothing;
this.disabled = disableMetrics;
this.metricsInterval = metricsInterval * 1000;
this.metricsIntervalInitial = metricsIntervalInitial * 1000;
this.appName = appName;
this.url = url instanceof URL ? url : new URL(url);
this.clientKey = clientKey;
Expand All @@ -86,11 +90,14 @@ export default class Metrics {
typeof this.metricsInterval === 'number' &&
this.metricsInterval > 0
) {
// send first metrics after two seconds.
setTimeout(() => {
if (this.metricsIntervalInitial > 0) {
setTimeout(() => {
this.startTimer();
this.sendMetrics();
}, this.metricsIntervalInitial);
} else {
this.startTimer();
this.sendMetrics();
}, 2000);
}
}
}

Expand Down

0 comments on commit 0f95cd1

Please sign in to comment.