dotCMS / dotCMS/core

Expose the experiment creator's username in the Experiments API

Open
#37,304 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : Experiments Team : Falcon Type : Task
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Description

The new Experiments portlet needs to display who created each experiment in its listing. Today the API returns the creator only as an opaque user ID, so the UI has no name to render and would have to resolve every ID itself.

AbstractExperiment already serializes a createdBy property, but its value is the user ID — the same value that backs getOwner() for permission checks:

// dotCMS/src/main/java/com/dotcms/experiments/model/AbstractExperiment.java
@JsonProperty("createdBy")
String createdBy();

@Value.Derived
@JsonIgnore
default String getOwner() {
    return createdBy();
}

Current payload:

{
  "id": "0e8b8b1e-...",
  "name": "Homepage CTA test",
  "createdBy": "dotcms.org.1"
}

Desired payload:

{
  "id": "0e8b8b1e-...",
  "name": "Homepage CTA test",
  "createdBy": "dotcms.org.1",
  "createdByUserName": "Admin User"
}

Approach: add a new createdByUserName field resolved from the User behind createdBy. createdBy keeps its current meaning and value — this is an additive, non-breaking change, and existing consumers (including permission logic) are unaffected.

Because the field is added at the Experiment serialization layer, it applies to every response that returns an Experiment, not just the list endpoint.

Acceptance Criteria

Happy path

  • GET /v1/experiments returns a createdByUserName string for each experiment in the list, containing the creator's full name (first + last name).
  • GET /v1/experiments/{id} returns createdByUserName for the requested experiment.
  • Every other endpoint whose response carries an Experiment also includes createdByUserName — create, update, archive, delete, _start, _end, scheduled/{id}/_cancel, and the variant operations (/variants, /variants/{name}, /variants/{name}/_promote).
  • createdBy keeps its existing value (the user ID) and its existing JSON key — no consumer of the current contract breaks.
  • getOwner() / permission behavior is unchanged: it still resolves from createdBy, not from the new field.

Sad path / edge cases

  • When the user ID in createdBy cannot be resolved to a User (deleted user, orphaned reference), createdByUserName falls back to the raw user ID so the field is never null or empty.
  • When the experiment was created by the system user, createdByUserName returns a value (resolved name, or the ID per the fallback rule) rather than failing the request.
  • A failure to look up the user never fails the experiment request — the endpoint still returns 200 with the experiment payload and the fallback value.
  • Listing N experiments does not perform N uncached user lookups that measurably degrade list response time (resolve per distinct user ID, or rely on the user cache).

Contract / docs

  • createdByUserName is documented in the Swagger @Schema for the experiment view, and the annotation matches the actual return type.
  • openapi.yaml is regenerated (./mvnw compile -pl :dotcms-core -DskipTests) and committed alongside the Java changes.

Tests

  • Integration test: an experiment created by a known user returns that user's full name in createdByUserName from both the list and the single-GET endpoints.
  • Integration test: an experiment whose createdBy points at a non-existent user returns the raw ID as createdByUserName and still responds 200.
  • Existing experiments tests still pass — in particular any asserting on the shape/value of createdBy.

Priority

Medium

Additional Context

Decisions made during refinement:

Question Decision
Field shape New sibling field createdByUserName; createdBy unchanged (non-breaking)
Scope Every response that returns an Experiment, not just the list endpoint
lastModifiedBy Out of scope — creator only. Can be added later using the same mechanism if the portlet needs "last edited by"
Unresolvable user Fall back to the raw user ID so the portlet column is never blank

Relevant files:

  • dotCMS/src/main/java/com/dotcms/experiments/model/AbstractExperiment.java — where createdBy / lastModifiedBy are declared
  • dotCMS/src/main/java/com/dotcms/rest/api/v1/experiments/ExperimentsResource.java — all experiment endpoints
  • dotCMS/src/main/java/com/dotcms/rest/api/v1/experiments/ResponseEntityExperimentView.java and ResponseEntitySingleExperimentView.java — response wrappers

Consumer: the new Experiments portlet listing

Note: lastModifiedBy has the same ID-not-name problem. It is deliberately excluded here — file a follow-up if the portlet later surfaces a "last modified by" column.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with AbstractExperiment.java and the response wrappers ResponseEntityExperimentView.java and ResponseEntitySingleExperimentView.java, then trace the experiment responses in ExperimentsResource.java. Run the existing experiment integration tests and ./mvnw compile -pl :dotcms-core -DskipTests; done means every Experiment response exposes the creator name, preserves createdBy and permissions, handles unresolved users safely, and updates OpenAPI documentation.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.