Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ We encourage pull requests and other contributions from the community. Before su

### Prerequisites

The project should be built and tested against the lowest compatible version, Node 16. It uses `npm`, which is bundled in all supported versions of Node.
The project should be built and tested against the lowest compatible version, Node 18. It uses `npm`, which is bundled in all supported versions of Node.

### Setup

Expand Down
50 changes: 50 additions & 0 deletions __tests__/LaunchDarklyProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,4 +326,54 @@ describe('given a mock LaunchDarkly client', () => {
expect(logger.logs[0]).toEqual("The EvaluationContext contained both a 'targetingKey' and a"
+ " 'key' attribute. The 'key' attribute will be discarded.");
});

it('handles tracking with invalid context', () => {
ofClient.track('test-event', {});
expect(logger.logs[0]).toEqual("The EvaluationContext must contain either a 'targetingKey' "
+ "or a 'key' and the type must be a string.");
});

it('handles tracking with no data or metricValue', () => {
ldClient.track = jest.fn();
ofClient.track('test-event', basicContext);
expect(ldClient.track).toHaveBeenCalledWith(
'test-event',
translateContext(logger, basicContext),
undefined,
undefined,
);
});

it('handles tracking with only metricValue', () => {
ldClient.track = jest.fn();
ofClient.track('test-event', basicContext, { value: 12345 });
expect(ldClient.track).toHaveBeenCalledWith(
'test-event',
translateContext(logger, basicContext),
undefined,
12345,
);
});

it('handles tracking with data but no metricValue', () => {
ldClient.track = jest.fn();
ofClient.track('test-event', basicContext, { key1: 'val1' });
expect(ldClient.track).toHaveBeenCalledWith(
'test-event',
translateContext(logger, basicContext),
{ key1: 'val1' },
undefined,
);
});

it('handles tracking with data and metricValue', () => {
ldClient.track = jest.fn();
ofClient.track('test-event', basicContext, { value: 12345, key1: 'val1' });
expect(ldClient.track).toHaveBeenCalledWith(
'test-event',
translateContext(logger, basicContext),
{ key1: 'val1' },
12345,
);
});
});
20 changes: 20 additions & 0 deletions __tests__/translateTrackingEventDetails.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import translateTrackingEventDetails from '../src/translateTrackingEventDetails';

it('returns undefined if details are empty', () => {
expect(translateTrackingEventDetails({})).toBeUndefined();
});

it('returns undefined if details only contains value', () => {
expect(translateTrackingEventDetails({ value: 12345 })).toBeUndefined();
});

it('returns object without value attribute', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd read back through the test names as sentences to make sure they read well that way.

For example "It returns object without value attribute." vs "It returns an object without the value attribute."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

expect(translateTrackingEventDetails({
value: 12345,
key1: 'val1',
key2: 'val2',
})).toEqual({
key1: 'val1',
key2: 'val2',
});
});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
"license": "Apache-2.0",
"peerDependencies": {
"@launchdarkly/node-server-sdk": "9.x",
"@openfeature/server-sdk": "^1.14.0"
"@openfeature/server-sdk": "^1.16.0"
},
"devDependencies": {
"@launchdarkly/node-server-sdk": "9.x",
"@openfeature/server-sdk": "^1.14.0",
"@openfeature/server-sdk": "^1.16.0",
"@types/jest": "^29.5.14",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
Expand Down
22 changes: 22 additions & 0 deletions src/LaunchDarklyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
ProviderMetadata,
ResolutionDetails,
StandardResolutionReasons,
TrackingEventDetails,
} from '@openfeature/server-sdk';
import {
basicLogger, init, LDClient, LDLogger, LDOptions,
} from '@launchdarkly/node-server-sdk';
import translateContext from './translateContext';
import translateResult from './translateResult';
import translateTrackingEventDetails from './translateTrackingEventDetails';
import SafeLogger from './SafeLogger';

/**
Expand Down Expand Up @@ -229,4 +231,24 @@ export default class LaunchDarklyProvider implements Provider {
await this.client.flush();
this.client.close();
}

/**
* Track a user action or application state, usually representing a business objective or outcome.
* @param trackingEventName The name of the event, which may correspond to a metric
* in Experimentation.
* @param context The context to track.
* @param trackingEventDetails Optional additional information to associate with the event.
*/
track(
trackingEventName: string,
context: EvaluationContext,
trackingEventDetails: TrackingEventDetails,
): void {
this.client.track(
trackingEventName,
this.translateContext(context),
translateTrackingEventDetails(trackingEventDetails),
trackingEventDetails?.value,
);
}
}
18 changes: 18 additions & 0 deletions src/translateTrackingEventDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TrackingEventDetails, TrackingEventValue } from '@openfeature/server-sdk';

/**
* Translate {@link TrackingEventDetails} to an object suitable for use as the data
* parameter in LDClient.track().
* @param details The {@link TrackingEventDetails} to translate.
* @returns An object suitable use as the data parameter in LDClient.track().
* The value attribute will be removed and if the resulting object is empty,
* returns undefined.
*
* @internal
*/
export default function translateTrackingEventDetails(
details: TrackingEventDetails,
): Record<string, TrackingEventValue> | undefined {
const { value, ...data } = details;
return Object.keys(data).length ? data : undefined;
}