Skip to content

Conversation

@curtisliu
Copy link
Member

Summary

This adds the ability to unset user properties when a variant has no value or is a fallback.

Other changes:

  • Fallback order changed for source = InitialVariants:
    1. InitialFlags
    2. Local Storage
    3. Function fallback
    4. Config fallback
  • Added trackExposure parameter to variant() to allow explicitly not tracking exposure. Defaults to true.

Checklist

  • Does your PR title have the correct title format?
  • Does your PR have a breaking change?: Breaking in the sense that custom ExperimentAnalyticsProviders will need to define an unset method.

@curtisliu curtisliu requested a review from bgiori October 6, 2021 23:55
Copy link
Collaborator

@bgiori bgiori left a comment

Choose a reason for hiding this comment

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

First, thanks a lot for taking on this work. Im sure it can be frustrating to take a project started by someone else.

The fallback code is much cleaner now, thanks for doing that.

public variant(
key: string,
fallback?: string | Variant,
trackExposure = true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there a common use case for this? I'd prefer not to add additional optional params to this function.

IMO a customer generally either wants to use exposure events or not? Not really a mix of both.

Copy link
Member Author

Choose a reason for hiding this comment

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

Main reason for this is to allow them to call variant multiple times, then call it once when they actually want to send the event. Maybe an explicit track exposure call is the right interface, so I'll remove it from this PR

Comment on lines 81 to 95
export enum VariantSource {
LOCAL_STORAGE = 'storage',
INITIAL_VARIANTS = 'initial',
SECONDARY_LOCAL_STORAGE = 'secondary-storage',
SECONDARY_INITIAL_VARIANTS = 'secondary-initial',
FALLBACK_INLINE = 'fallback-inline',
FALLBACK_CONFIG = 'fallback-config',
}

export const isFallback = (source: VariantSource): boolean => {
return (
source === VariantSource.FALLBACK_INLINE ||
source === VariantSource.FALLBACK_CONFIG
);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure this belongs in this file.

Could put this and Source from config in its own file within /types

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

*/
export interface ExperimentAnalyticsProvider {
track(event: ExperimentAnalyticsEvent): void;
unset(event: ExperimentAnalyticsEvent): void;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is going to break the smart recruiters custom implementation. We should technically release a v2...

Maybe we discuss the change with them and work through their implementation together? I'd like to avoid publishing v2 until we overhaul the library. 😕

Alternatively, we could have the logic built into the event in track like we do with set user properties. and unset the property if the variant value is null (unassigned or fallback).

Copy link
Member Author

Choose a reason for hiding this comment

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

I made this function optional, which should make it backwards compatible

* Event for tracking a user's exposure to a variant. This event will not count
* towards your analytics event volume.
*/
export class ExposureEvent implements ExperimentAnalyticsEvent {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this pattern not common in js-land? In other words is it more "native" to use a function to return an interface implementation?

Copy link
Member Author

Choose a reason for hiding this comment

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

for simple data objections, factory functions are preferable to classes

Comment on lines 239 to 259
expect(spyTrack).toBeCalledTimes(1);

expect(spyTrack).lastCalledWith({
name: '[Experiment] Exposure',
properties: {
key: serverKey,
source: 'storage',
variant: serverVariant.value,
},
user: expect.objectContaining({
user_id: 'test_user',
}),
key: serverKey,
variant: serverVariant,
userProperties: {
[`[Experiment] ${serverKey}`]: serverVariant.value,
},
userProperty: `[Experiment] ${serverKey}`,
});

expect(spyUnset).toBeCalledTimes(0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah this is cool, still such a noob with js testing frameworks.

/**
* User properties to identify with the user prior to sending the event.
*/
userProperties?: Record<string, unknown>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since you added userProperty you dont need this anymore?

I am confused that we need both userProperty and userProperties.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this isn't necessary anymore but keeping it for backwards compatibility

@curtisliu
Copy link
Member Author

@bgiori PTAL

Copy link
Collaborator

@bgiori bgiori left a comment

Choose a reason for hiding this comment

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

A few changes need to made which have slipped through the cracks, but looks good otherwise 👍

payload: {},
}
},
analyticsProvider: {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to be updated

variant(
key: string,
fallback?: string | Variant,
trackExposure?: boolean,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This field was removed from the concrete implementation, but not from here.

@curtisliu curtisliu merged commit dbab7e8 into main Oct 18, 2021
@curtisliu curtisliu deleted the unset-properties branch October 18, 2021 18:09
kelsonpw pushed a commit to kelsonpw/experiment-js-client that referenced this pull request Jul 10, 2023
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.

3 participants