Skip to content

Experiments Portlet — Screen 1: portlet base + site-wide List #36989

Description

@oidacra

Description

This is the issue that makes the portlet exist: the site-wide/ subtree, the /experiments route, and the List screen from the approved design — search, status filter with counts, pagination, sorting, row actions — listing every experiment on the current site. No backend dependency: it ships against GET /api/v1/experiments as it exists today (see Interim below).

Design: approved prototype, list screen.

Portlet base (additive — three existing files, one line each)

  • New subtree libs/portlets/dot-experiments/portlet/src/lib/site-wide/ with its own lib.routes.ts exporting dotExperimentsPortletRoutes ('', new, :id/configuration, :id/results — the last three are placeholders wired by their own issues).
  • Export the new route array from the lib src/index.ts (the existing dotExperimentsRoutes export untouched; edit-ema keeps consuming it).
  • Route entry in apps/dotcms-ui/src/app/app.routes.ts (PORTLETS_ANGULAR, copy the tags entry). The path must be experiments: MenuGuardService validates the first URL segment against /api/v1/menu.
  • No registration UpgradeTask and no flag guard — the portlet is reachable only where a cms_layouts_portlets row was inserted manually (dev/QA): one row against an existing layout (e.g. Marketing); nothing in portlet, layouts_cms_roles or cms_layout. Roles & Tools on the admin role works too.

List screen

  • Table copied from dot-tags (libs/portlets/dot-tags/src/lib/dot-tags-list/): p-toolbar + p-iconField search (300 ms debounce), lazy p-table + paginator + sort, per-row skeletons, dot-empty-container with the science icon. Cross-check dot-users (same skeleton, newest iteration).
  • Columns: Experiment (name + description below) · Page (path, monospace) · Goal (type label or —) · Variants (count) · Schedule (Jun 25, 2026 → Jul 9, 2026 / → Open / Not scheduled) · Status (tag) · Modified (sortable, default DESC) · Actions.
  • Status tag: portlet-local p-tag + Record<DotExperimentStatus, severity> map (pattern: dot-publishing-status-chip).
  • Status filter chip: reuse DotChipFilterComponent / DotFilterListItemComponent / CHIP_FILTER_*_PT from @dotcms/portlets/content-drive/ui (precedent: dot-users-filter-by). Multi-select, per-status counts, Clear/Done. Default excludes ARCHIVED.
  • Primary row action (derived): status ∈ {RUNNING, ENDED} → View Results, else Configure. Hidden until Screens 2 and 3 land — the new list must not route into the old screens.
  • Row actions — full parity with the current UVE list (dot-experiments-list-store.ts:483-640): Archive/Restore inline icons per the design (archive-from-DRAFT and restore enabled once Experiments API gaps for the standalone portlet: archive/restore, PATCH side effects, goal operators, variant cap #36988 lands, via the new archiveDraft/restore keys), plus a kebab with cancelSchedule, end, abort, delete, pushPublish, addToBundle — all gated by AllowedActionsByExperimentStatus, each with confirm + toast. Push-publish/add-to-bundle need DotPushPublishEnvironmentsResolver on the route and reuse dot-add-to-bundle.
  • Site scoping: the site selector drives the list; switching site reloads it.

Store — NgRx Signal Store + Events plugin

  • experiments-list.events.ts with eventGroup; state transitions in withReducer(on(...)); HTTP in withEventHandlers with Request → Succeeded → Failed triples and switchMap (re-trigger cancels in-flight). Components dispatch via injectDispatch — no store methods for state changes.
  • Errors: catchError → DotHttpErrorManagerService.handle; load failure → status: 'error', CRUD failure → back to 'loaded'.
  • Reference: libs/image-editor/src/lib/store/. Note: the async hook is withEventHandlers in the installed @ngrx/signals — examples naming withEffects will not compile.
  • Add an events-pattern section to libs/portlets/CLAUDE.md in this PR, pointing at libs/image-editor, so reviewers have a written standard.

URL state

All navigable state in the URL: page, per_page, orderby, direction, filter, status (repeatable). Hydrate from route.snapshot.queryParams before the first fetch; write back via effect with queryParamsHandling: 'merge', null clearing defaults (a bare /experiments is the default view), guarded against location.path(true) to avoid redundant history entries. Reference: dot-content-drive-shell.component.ts:454-494.

Interim: client-side list operations (temporary by design)

GET /api/v1/experiments with no params returns every experiment, so this issue computes paging, sorting, text/status/site filtering and status counts client-side. Acceptable at current volumes; safe because the portlet is unregistered for customers; sidesteps two SQL bugs that only fire on server-side filtering. The follow-up "swap to the server-side contract" replaces this when #36823 lands — the URL param contract is final from day one, so the swap changes where params are applied, not what they are called. Known gap: without #36823's enriched rows the API returns pageId, not the page path — resolve titles client-side in one bulk lookup (fallback: show the id). Shape the data-access service so the swap changes only the service and the store's load handler.

Hazard: dot-experiments.model.ts:204-215 runs getComputedStyle(document.body) at import time — stub it in the new specs (lazy-getter fix belongs to the migration issue).

Acceptance Criteria

  • /experiments renders the site-wide list for the current site (manual layout-row insert documented in the PR).
  • Search filters by name and page path (substring, case-insensitive), debounced 300 ms.
  • Status chip shows the 5 statuses with counts for the whole filtered set; default view excludes ARCHIVED.
  • Table pages, sorts (default modDate DESC) and shows the design's columns + Modified.
  • Kebab exposes cancelSchedule / end / abort / delete / pushPublish / addToBundle exactly where AllowedActionsByExperimentStatus allows them; each has confirm + toast; the list reloads after.
  • Archive/Restore icons per the design (enabled once Experiments API gaps for the standalone portlet: archive/restore, PATCH side effects, goal operators, variant cap #36988's transitions exist).
  • Filter + page + sort state round-trips through the URL: copying the URL into a new tab restores the identical view; back/forward walks the states; a bare /experiments has no params.
  • Switching site via the site selector reloads the list.
  • The existing UVE experiments UI is untouched: dotExperimentsRoutes, the nav item and dot-ema-shell.component.spec.ts:117-120 all unchanged.
  • libs/portlets/CLAUDE.md documents the events-store pattern.
  • Jest specs: store (dispatch events, assert signals; error paths assert DotHttpErrorManagerService.handle), list component, filter chip, URL sync (hydration + write-back).

Priority

High — everything else in #36987 hangs off this.

Additional Context

  • Out of scope: Configure/Results navigation targets (own issues); server-side paging/filtering (swap issue); registration UpgradeTask/starter bump; metrics in the list (GET /{id}/results costs 2 CubeJS round-trips + a Monte Carlo per experiment, @NoCache, no batch form — the design deliberately shows none).
  • E2E for this screen (including deep-links) lives in the dedicated portlet E2E issue.
  • Verify: pnpm nx lint|test portlets-dot-experiments-portlet · pnpm nx test portlets-edit-ema-portlet · pnpm nx test dotcms-ui --testPathPattern=app.routes · pnpm nx format:write · pnpm nx build dotcms-ui

Activity

  1. added theissue type on Aug 10, 2026
  2. self-assigned this
    on Aug 11, 2026
  3. moved this from New to Current Sprint Backlog in dotCMS - Product Planningon Aug 11, 2026
  4. moved this from Current Sprint Backlog to In Progress in dotCMS - Product Planningon Aug 11, 2026
  5. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor
  6. added a commit that references this issue on Aug 13, 2026
    662a762
  7. moved this from In Progress to In Review in dotCMS - Product Planningon Aug 13, 2026
  8. added a commit that references this issue on Aug 18, 2026
    0d454a2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions