Wip/add decision point to the assign call - #3249
Conversation
There was a problem hiding this comment.
Pull request overview
Adds optional decision-point scoping (site/target) to the /assign flow so clients can fetch (and cache) assignments either for the full context or for a specific decision point, updating backend services and multiple client SDKs accordingly.
Changes:
- Backend: plumbs
site/targetthrough assign controllers → assignment service → experiment fetch/caching → repository query. - Client SDKs (Python/JS/Java): add request fields + decision-point fetch semantics and merge decision-point results into local caches.
- Tests: extend SDK unit tests and backend integration tests to cover the new parameters and caching behavior.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/backend/test/integration/utils/index.ts | Updates integration test helper to match new getAllExperimentConditions signature. |
| packages/backend/test/integration/UserNotDefined/index.ts | Updates integration test to pass new parameters to getAllExperimentConditions. |
| packages/backend/src/api/services/ExperimentService.ts | Adds site/target-aware experiment fetch selection under caching. |
| packages/backend/src/api/services/ExperimentAssignmentService.ts | Threads site/target through assignment flow into experiment retrieval. |
| packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts | Extends assign request validator to include site/target. |
| packages/backend/src/api/controllers/ExperimentClientController.v6.ts | Passes site/target into assignment service for v6 assign endpoint. |
| packages/backend/src/api/controllers/ExperimentClientController.v5.ts | Passes site/target into assignment service for v5 assign endpoint. |
| clientlibs/python/tests/test_types.py | Adds AssignRequest test coverage for decision-point fields. |
| clientlibs/python/tests/test_data_service.py | Adds tests for decision-point cache upsert/merge behavior. |
| clientlibs/python/tests/test_client.py | Adds tests for request payload + decision-point caching behavior. |
| clientlibs/python/tests/test_api_service.py | Adds API-service test for sending site + default target. |
| clientlibs/python/src/upgrade_client_lib/types/requests.py | Adds optional site/target to AssignRequest model. |
| clientlibs/python/src/upgrade_client_lib/data_service.py | Adds assignment-key helper + upsert merge into cache. |
| clientlibs/python/src/upgrade_client_lib/client.py | Adds decision-point scoped fetching and cache merge logic. |
| clientlibs/python/src/upgrade_client_lib/api_service.py | Sends site/target only when decision-point scoped (defaulting target to ""). |
| clientlibs/js/src/UpGradeClient/UpgradeClient.ts | Adds decision-point options to getAllExperimentConditions + merges into cache. |
| clientlibs/js/src/types/requests.ts | Adds optional site/target to assign request type. |
| clientlibs/js/src/DataService/DataService.ts | Adds upsert merge behavior for decision-point assignment caching. |
| clientlibs/js/src/DataService/DataService.spec.ts | Adds unit tests for new upsert merge behavior. |
| clientlibs/js/src/ApiService/ApiService.ts | Sends site/target only when site is provided (defaulting target to ""). |
| clientlibs/js/src/ApiService/ApiService.spec.ts | Adds test verifying site + normalized target in request body. |
| clientlibs/java/src/main/java/org/upgradeplatform/requestbeans/ExperimentRequest.java | Extends request bean to include site/target. |
| clientlibs/java/src/main/java/org/upgradeplatform/client/ExperimentClient.java | Adds decision-point fetch with merge-upsert caching and ignore-cache option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| public async getCachedValidExperiments(context: string): Promise<Experiment[]> { | ||
| public async getCachedValidExperiments(context: string, site: string, target: string): Promise<Experiment[]> { | ||
| const cacheKey = CACHE_PREFIX.EXPERIMENT_KEY_PREFIX + context; |
There was a problem hiding this comment.
This is possibly a concern. If we're creating a cache key for each context/decision point, there could potentially be many more cold misses. That might increase the number of assign calls made.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (3)
packages/backend/src/api/services/ExperimentService.ts:283
getCachedValidExperimentsuses a cache key based only oncontext, but it can return either the full-context experiment set or a decision-point-scoped subset depending on whethersiteis provided. Because both variants share the same cache key, whichever call happens first can poison the cache for the other call pattern.
public async getCachedValidExperiments(context: string, site: string, target: string): Promise<Experiment[]> {
const cacheKey = CACHE_PREFIX.EXPERIMENT_KEY_PREFIX + context;
const fetchByDecisionPoint = site !== undefined && site !== null;
return this.cacheService
.wrap(
packages/backend/src/api/services/ExperimentAssignmentService.ts:703
site/targetare typed as requiredstringparameters ingetExperimentsForUser, but the call site passesdecisionPoint?.site/decisionPoint?.target, which can beundefinedwhen no decision point is provided. The current types don’t reflect actual usage (andExperimentService.getCachedValidExperimentsalso checks forundefined), making it easy to accidentally misuse these APIs.
previewUser: PreviewUser,
context: string,
site: string,
target: string
): Promise<Experiment[]> {
packages/backend/test/unit/controllers/ExperimentClientController.test.ts:293
- This test is named as if it verifies class-transformer normalization of a missing
target, but it builds validator instances viaObject.assign(...), which does not run@Transform. As written,decisionPoint.targetstaysundefinedand the assertion only checks that the controller forwards the object, not that normalization happens.
const decisionPoint = Object.assign(new DecisionPointValidator(), {
site: 'CurriculumSequence',
});
const response = await controller.getAllExperimentConditions(
| if (site != null) { | ||
| requestBody.site = site; | ||
| requestBody.target = target ?? ''; | ||
| } |
There was a problem hiding this comment.
Yeah, that's coming up next!
| async def get_all_experiment_conditions( | ||
| self, site: str | None = None, target: str | None = None | ||
| ) -> list[ExperimentAssignment]: | ||
| body: dict[str, Any] = {"context": self._context, "userId": self._user_id} | ||
| if site is not None: | ||
| body["site"] = site | ||
| body["target"] = target if target is not None else "" | ||
| data = await self._post_async("assign", body) |
There was a problem hiding this comment.
Yeah, that's coming up next!
| String normalizedTarget = target == null ? "" : target; | ||
| ExperimentRequest experimentRequest = new ExperimentRequest(this.context, site, normalizedTarget); | ||
| AsyncInvoker invocation = this.apiService.prepareRequest(GET_ALL_EXPERIMENTS); | ||
| Entity<ExperimentRequest> requestContent = Entity.json(experimentRequest); |
There was a problem hiding this comment.
Yeah, that's coming up next!
No description provided.