Skip to content

Wip/add decision point to the assign call - #3249

Open
bcb37 wants to merge 10 commits into
devfrom
wip/add-dps-to-assign
Open

Wip/add decision point to the assign call#3249
bcb37 wants to merge 10 commits into
devfrom
wip/add-dps-to-assign

Conversation

@bcb37

@bcb37 bcb37 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@bcb37
bcb37 requested review from Copilot and danoswaltCL July 24, 2026 19:59
@bcb37 bcb37 changed the title Wip/add dps to assign Wip/add decision point to the assign call Jul 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/target through 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;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment thread packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts Outdated
Comment thread clientlibs/js/src/DataService/DataService.ts
bcb37 and others added 4 commits July 27, 2026 12:33
@bcb37
bcb37 marked this pull request as ready for review July 28, 2026 21:20
@bcb37
bcb37 requested a review from Copilot July 28, 2026 21:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

  • getCachedValidExperiments uses a cache key based only on context, but it can return either the full-context experiment set or a decision-point-scoped subset depending on whether site is 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/target are typed as required string parameters in getExperimentsForUser, but the call site passes decisionPoint?.site / decisionPoint?.target, which can be undefined when no decision point is provided. The current types don’t reflect actual usage (and ExperimentService.getCachedValidExperiments also checks for undefined), 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 via Object.assign(...), which does not run @Transform. As written, decisionPoint.target stays undefined and 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(

Comment on lines +218 to +221
if (site != null) {
requestBody.site = site;
requestBody.target = target ?? '';
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, that's coming up next!

Comment on lines +155 to +162
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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, that's coming up next!

Comment on lines +354 to +357
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);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, that's coming up next!

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.

2 participants