Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create offlinePrecomputedInit #117

Merged

Conversation

sameerank
Copy link
Contributor

@sameerank sameerank commented Dec 5, 2024


labels: mergeable

Fixes: #issue

Motivation and Context

Description

Adds a method for offlinePrecomputedInit

Depends on Eppo-exp/js-sdk-common#163 because we need a way to persist the subject key and attributes without relying on request parameters, since the offline init doesn't make requests

How has this been tested?

package.json Outdated
@@ -59,7 +59,7 @@
"webpack-cli": "^4.10.0"
},
"dependencies": {
"@eppo/js-client-sdk-common": "^4.5.4"
"@eppo/js-client-sdk-common": "^4.6.1"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Depends on this to be published Eppo-exp/js-sdk-common#163

* Configuration for Eppo precomputed client initialization
* @public
*/
export interface IPrecomputedClientConfig extends IBaseRequestConfig {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Breaking change to the interface of precomputedInit but assuming it is safe because we are currently the only users of this function

@sameerank
Copy link
Contributor Author

Marking as ready for review because I expect the tests to pass once the js common dependency is published

@sameerank sameerank marked this pull request as ready for review December 9, 2024 08:57
Copy link
Contributor

@aarsilv aarsilv left a comment

Choose a reason for hiding this comment

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

Pieces coming together 💪

src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
Copy link
Member

@leoromanovsky leoromanovsky left a comment

Choose a reason for hiding this comment

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

I haven't reviewed this yet but since it has a ✅ please remember to test this build by compiled the production package.

Makefile Outdated
@@ -27,14 +27,15 @@ help: Makefile
testDataDir := test/data/
tempDir := ${testDataDir}temp/
gitDataDir := ${tempDir}sdk-test-data/
branchName := main
branchName := sameeran/ff-3687-obfuscated-precomputed-json
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Temporarily using this branch for testing until Eppo-exp/sdk-test-data#89 gets an approval 🙏

let precomputedConfigurationWire: string;

beforeAll(() => {
precomputedConfigurationWire = readMockPrecomputedResponse(MOCK_PRECOMPUTED_RESPONSE_FILE);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, tests use the unobfuscated, unstringified ConfigurationWire format https://github.com/Eppo-exp/sdk-test-data/blob/8ee55117b7c7f5d02b2997629af0f0fa93e4e97c/configuration-wire/precomputed-v1.json

In a follow up PR I will add support for parsing json strings and handling obfuscated fields

src/index.ts Outdated
Comment on lines 624 to 638
export interface IConfigurationWire {
version: number;
precomputed: {
subjectKey: string;
subjectAttributes: Record<string, AttributeType>;
fetchedAt: string;
response: {
createdAt: string;
format: FormatEnum;
obfuscated: boolean;
environment: Environment;
flags: Record<string, PrecomputedFlag>;
};
};
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will replace with an exported interface from Eppo-exp/js-sdk-common#160

@sameerank sameerank requested a review from typotter December 11, 2024 07:06
src/index.ts Outdated Show resolved Hide resolved
@@ -1205,7 +1214,7 @@ describe('EppoClient config', () => {
const retryManager = eventDispatcher['retryManager'];
const batchProcessor = eventDispatcher['batchProcessor'];
expect(eventDispatcher['deliveryIntervalMs']).toEqual(1);
expect(batchProcessor['batchSize']).toEqual(5);
expect(batchProcessor['batchSize']).toEqual(100);
Copy link
Contributor Author

@sameerank sameerank Dec 16, 2024

Choose a reason for hiding this comment

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

cc @felipecsl I had to change this because 5 is below the minimum allowed batch size

https://github.com/Eppo-exp/js-sdk-common/blob/b1048a32587a809bed351af07185592c9f774150/src/events/batch-event-processor.ts#L4-L13

const MIN_BATCH_SIZE = 100;
const MAX_BATCH_SIZE = 10_000;

export default class BatchEventProcessor {
  private readonly batchSize: number;

  constructor(private readonly eventQueue: NamedEventQueue<Event>, batchSize: number) {
    // clamp batch size between min and max
    this.batchSize = Math.max(MIN_BATCH_SIZE, Math.min(MAX_BATCH_SIZE, batchSize));
  }

package.json Outdated
@@ -1,6 +1,6 @@
{
"name": "@eppo/js-client-sdk",
"version": "3.8.3",
"version": "3.9.0-alpha.0",
Copy link
Contributor Author

@sameerank sameerank Dec 16, 2024

Choose a reason for hiding this comment

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

⚠️ Do not publish this version for dogfooding until

  1. The edge compute endpoint is published with updated hashing
  2. The corresponding md5 hasher in js-client-sdk-common is updated https://github.com/Eppo-exp/js-sdk-common/pull/164/files#r1887291803 (.appendBinary --> .append)
  3. And 4.7.0-alpha.1 version of js-client-sdk-common has been released and the dependency in this file has been updated to match (also see: https://github.com/Eppo-exp/js-sdk-common/pull/164/files#r1887650834)

@sameerank sameerank assigned typotter and unassigned leoromanovsky Dec 16, 2024
@rasendubi rasendubi self-requested a review December 17, 2024 19:59
src/index.ts Outdated
contextAttributes ?? { numericAttributes: {}, categoricalAttributes: {} },
);

EppoPrecomputedJSClient.instance.setSubjectSaltAndPrecomputedFlagStore(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on these PR comments

Eppo-exp/js-sdk-common#164 (comment)
Eppo-exp/js-sdk-common#164 (comment)

I'll see if I can make this method private/not use it here and have these set through the constructor

src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated
Comment on lines 643 to 649
const configurationWire: IConfigurationWire = JSON.parse(config.precomputedConfigurationWire);
if (!configurationWire.precomputed) {
applicationLogger.error('Invalid precomputed configuration wire');
return EppoPrecomputedJSClient.instance;
}
const { subjectKey, subjectAttributes, response } = configurationWire.precomputed;
const parsedResponse: IPrecomputedConfigurationResponse = JSON.parse(response);

Choose a reason for hiding this comment

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

Is there any reason all this parsing is done in client library and not common?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's due to the approach taken. The same EppoPrecomputedClient is used for online (via precomputedInit) vs offline mode (via offlinePrecomputedInit)

The EppoPrecomputedClient's constructor knows which mode we're using based on the provided initialization options for the client instance. Both precomputedFlagStore and subject need to be accessible for initializing in the online mode: the user may want to select a specific storage option (e.g. chrome.storage) for their use case and subject is required to know what to precompute. We're reusing these same inputs for offline mode, hence the parsing.

I could add an option to pass in the configuration wire string to be parsed by the common library, which would overwrite the contents of precomputedFlagStore, but making subject optional or to overwrite it seems like doing a lot in EppoPrecomputedClient's constructor.

src/index.ts Outdated Show resolved Hide resolved
subjectAttributes: subjectAttributes ?? ({} as Attributes),
};

EppoPrecomputedJSClient.instance = new EppoPrecomputedJSClient({

Choose a reason for hiding this comment

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

major: before potentially overwriting previous instance, we should shut it down. (and warn of re-initialization)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'm unclear about what "shut it down" means here. The client is offline so polling/making requests isn't at play. Also the two instance variables being set by the constructor (subject and precomputedFlagStore) are private.

I could make these public so I can clear them here. Or I can add a public method to EppoPrecomputedClient in the common sdk to clear these. I can't do it in the constructor because that only has access to the new instance, not the one being replaced.

Copy link
Contributor Author

@sameerank sameerank Jan 10, 2025

Choose a reason for hiding this comment

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

My attempt at a private shutdown function

function shutdownEppoPrecomputedClient() {
  if (EppoPrecomputedJSClient.instance) {
    EppoPrecomputedJSClient.instance.stopPolling();
    EppoPrecomputedJSClient.initialized = false;
    applicationLogger.warn(`${loggerPrefix} Precomputed client is being re-initialized.`);
  }
}

The configuration store is a private variable in EppoPrecomputedClient so I can't clear it here, nor does it come with any helpers to clear its contents. Also the store we use at the moment is memory-only, so I don't know if persisting across sessions is possible, e.g. if a page refresh is the reason for re-initializing

Copy link

@rasendubi rasendubi Jan 16, 2025

Choose a reason for hiding this comment

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

I think I'm unclear about what "shut it down" means here.

I mostly meant stopping polling. Most SDKs have a method for stopping all background activities.

The client is offline so polling/making requests isn't at play.

The fact that we're initializing offline doesn't mean that the previous client was in offline mode too

package.json Outdated Show resolved Hide resolved
src/index.spec.ts Outdated Show resolved Hide resolved
src/index.spec.ts Show resolved Hide resolved
@sameerank sameerank merged commit b55bfe2 into main Jan 11, 2025
8 checks passed
@sameerank sameerank deleted the sameeran/ff-3649-offline-init-method-for-precomputed-client branch January 11, 2025 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants