-
Notifications
You must be signed in to change notification settings - Fork 2k
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
WIP ExPlat: Self-Contained Client #48475
Conversation
This is great, @jessie-ross! I only skimmed the code, but I like the idea of a client with a simple interface and no dependencies. Not being a Calypso/JS dev, I'm not in a good place to judge it from an experimenter's perspective, but your plan of getting experimenters to review it should cover that. After @withinboredom and @aaronyan have had a look, a P2 post with the contents you put in the PR description, some context, and plenty of mentions would probably get it some attention.
To simplify things, If this stays in this repo, this PR should also update CODEOWNERS: Lines 90 to 91 in 1e075ab
This is only for a12s and proxied non-support requests. The default TTL for regular users is 3600 seconds. |
This comment has been minimized.
This comment has been minimized.
SSR AssessmentSince the recent SSR issue (p4TIVU-9zp-p2) I have learnt a lot more about how it works, this is an assessment of this package interacting with SSR. On Supporting SSR-time ExperimentsThese are experiments that have code that runs on the server on a SSR'd page. As I have mentioned this probably won't be a short term goal for us due to two issues:
Chances are this would require an entirely different client so this client should not support it at all. SSR SafetyWe need to ensure that this client does not run at SSR-time. We can mitigate this by by swapping this module for another at server-time. By replacing it with a mock package that log's errors to the server we will know when this happens (potential for a traffic light on this in Abacus), and it gives us a layer of protection against using browser-only APIs. As mentioned in p4TIVU-9zp-p2 it can be hard to know when code is run server-side, but there are only a few routes that do so (and specifically The only real way I can see to prevent it running at SSR-time is more vigilant code reviews, particularly around the proposed new API: The hook and Component implementations should be safe as they run in a We already have some level of mitigation against general crashing by wrapping everything in try blocks, but we need to ensure if we are calling any of the dep-injected functions that they are also SSR safe - particularly Tasks
|
UpdateThe ExPlat client has mostly been built, mainly PR approvals and The rebuilding PRs:
I plan to merge them together at the end and I am keeping this PR in sync with them. Todo for this stage:
|
@jessie-ross What's the reasoning behind not merging them separately and moving forward with what's been approved? It'd be nice to have a commit history that matches the discussions. |
Do you mean merging into trunk or merging into a base branch? I do plan merging them separately but once they are all assembled and ready? |
Merging into trunk. A bunch of them are ready, so it'd be good to get them in. |
My thoughts here are less deploys, less chance for something to go wrong, i.e. deploying 5 times verses 1. |
I strongly disagree on that. One big deploy is way scarier than five small ones. And in this case, nothing is calling the deployed code, so it should be safe. For example, if there are any issues with deploying the "empty" package, we're better off finding out when it's empty. |
Reassessing the PR strategyYeah I am not sure this is working for me, it is getting a bit messy especially when I need to go over already committed PRs, make changes and try to sync everything up (e.g. #49990 which I had actually fixed in a later PR). @yanirs I would prefer to not commit as we go. Stacked commits also aren't feeling great partly from having too large commits and partly from needing to backtrack. I am going to try re-split what we already have into smaller pieces - I think not having to make sure each PR is commit ready will make that easier. It would allow me to make mistakes without worrying about them and fix them later, and with more PRs, committing at the end will reduce the overhead involved.
As far as the danger of plugging everything at the end, I think having the start of it already committed and then committing the package separately to the |
@jessie-ross let's chat about this over Slack today. I'm not sure what alternative you're suggesting if it's not:
From the perspective of reviewing, I find it harder to review changes that aren't working/complete with context that's split across multiple PRs. I also don't fully see the benefit of re-splitting now that #49602 is ready to go and #49829 and #49832 don't need much work. However, I'm open to seeing what you have in mind. 🙂 |
I think this is fine for now, severe duplication of errors occurs when lots of |
Its time :) |
🎉 |
This very rough PR proposes a self-contained ExPlat Client.
This PR is at proof-of-concept level and code shouldn't be thoroughly reviewed.Main code is
/packages/explat-client
, there are a fair few files but most of them are very small.Self-Contained:
const ExPlatClient = createExPlatClient(makeRequest, getAnonId, logError, isDevelopmentMode)
/packages
or could even be moved to its own repo.New API Surface
The Core ExPlat function:
loadExperimentAssignment
Type signature
loadExperimentAssignment: (experimentName: string) => Promise<ExperimentAssignment>
Usage
const experimentAssignment = await loadExperimentAssignment('experiment_name')
top-level AND logged-out AND en
Benefits
I was messing around with subscription and external Redux integration when I realised there was a much simpler way: A function which provides a promise. There is a bit more messing around involved to make it intuitive and able to be used as much and whenever an Experimenter wants but this is moving complexity away from Experimenters hands to our hands which I think is worth it.
/lib
code, where promises are quite commonExperimentAssignment | null
where ExperimentAssignment is a central data structure used ubiquitously throughout. This extra level of boxing is important and yet another layer of protection for the loading state - having anexperimentAssignment.variationName === null
is different to not having anexperimentAssignment
.loadExperimentAssignment
can be used.AFAICT SSR is just in
logged-out AND EN
cases, which means this function can be used everywhere else.Synchronous escape hatch:
dangerouslyGetExperimentAssignment
Type signature
dangerouslyGetExperimentAssignment: ( experimentName: string ) => ExperimentAssignment
Usage
This is a safer alternative to the current "selector method" usage:
It can be useful within
/lib
code.The ExPlat Hook:
useExperiment('experiment_name')
Type signature
useExperiment: (experimentName: string) => [boolean, ExperimentAssignment | null]
Usage
const [isLoadingExperimentAssignment, experimentAssignment] = useExperiment('experiment_name')
loadExperimentAssignment
Added safety measures
try
block.internal
namespace, making it harder to misuse.Different behaviour surrounding TTL (Time To Live)
getExperimentAssignment
obeys TTL - unlike using thegetVariationForUser
selector - it will refetch if past the TTL. This makes sense from the lib perspective as a user can be using a site for a long period of time, and we need to ensure experiments are turned on/off for them too. Currently we just load data once e.g. in/layout/index.js
but if an experimenter is using this in/lib
code it will check each time it is called. I would say this is intuitive for experimenters and will give better behaviour for experiments.However, this is not intuitive for the react side of code, since when you render a react component using an experiment you don't expect or want it to suddenly change for a user - so long as a user has that component up they should have the same experience. Hence
useExperiment(Assignment)
doesn't obey TTL and same to<Experiment />
.Future compatible
TODO
Fixes #536 #528