Skip to content

[TASK] UI overhaul for site list, page module and site performance widget - #26

Open
hahnmg wants to merge 9 commits into
developfrom
feature/ui-overhaul
Open

hahnmg wants to merge 9 commits into
developfrom
feature/ui-overhaul

Conversation

@hahnmg

@hahnmg hahnmg commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Reworks the backend views: the site list becomes a compact table with collapsed inactive sites, the register form asks whether an inactive site is registered as new or added to an existing active site, the page module gets an expandable panel with all metric details, and the Site Performance widget shows descriptions, daily values and sparklines for all four metrics.

  • Why — Site list was hard to scan; inactive sites could only be registered as new; metric details were only reachable via hover
  • Changes — Collapsed state persists per backend user (uc); daily series from one analytics/sessions request; widget charts fill the remaining card height; register form sends mode=register|attach (+ targetSiteIdentifier); new labels in en/de only
  • Tested — Unit (415), functional (63), CGL, PHPStan green; TYPO3 13.4 demo data, before/after screenshots desktop/mobile, widget measured at 1280–1920 px and 390 px; dark mode not checked
  • Open — Verify averageSessionDuration vs. averageVisitDuration on a site with traffic; draft until registerAction handles mode=attach

@hahnmg
hahnmg requested a review from sfsmfc October 6, 2026 10:38
@hahnmg
hahnmg marked this pull request as draft October 7, 2026 12:27
@sfsmfc

sfsmfc commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

1. Load all daily series with one request

SitePerformanceService::loadSeries() sends two requests: fetchSiteVisitsGraph and fetchSiteVisitorsGraph. These requests return series only for visits and visitors. Thus, the widget shows no chart and no today, yesterday and peak values for "Avg. bounce rate" and "Avg. visit duration". The PR description lists this as open. A test with site 5 found an endpoint that returns all four daily series in one request:

POST /v2/websites/{externalWebsiteId}/analytics/sessions
{
  "where": { "and": [] },
  "metrics": ["visitCount", "visitorCount", "bounceRate", "averageSessionDuration"],
  "dimensions": ["date"],
  "granularity": "day",
  "dateRange": { "start": "...", "end": "..." }
}

Rules of the API for the date dimension:

  • granularity is mandatory.
  • pagination and order are not permitted.

Fix:

  1. Replace the two graph requests in loadSeries() with this one request.
  2. Show a chart and the today, yesterday and peak values for all four metrics.
  3. Make sure that averageSessionDuration gives the same value as the averageVisitDuration that the widget shows now. Use a site with more traffic for this check.

2. The cache keeps failed time series (solved with 1)

When loadSeries() fails, it returns empty series. loadPerformanceData() then caches this result for the full cache lifetime. The default lifetime is 3600 seconds. After a short API timeout, the widget shows no details and no sparklines for up to one hour.

Fix: Do not cache empty series. Alternatively, use a short cache lifetime for them.

3. A visits error also stops the visitors series (solved with 1)

Both API calls are in the same try block. When the first call fails, the code does not do the second call.

Fix: Put each API call in its own try block.

4. The sparkline axes show a false zero line

Without yMin, SparklineRenderer::render() scales the chart from min(values) to max(values). With axes: true, the bottom axis is at the lowest value, not at 0. Small changes look like large drops.

Example: For the visits [980, 1000, 1020], the value 980 touches the axis.

These calls use axes: true:

  • PagePerformanceBarBuilder::renderMetricDetails()
  • SitePerformanceService::renderSparkline()

Fix: Set yMin: 0 for count values. Alternatively, remove the axes.

5. The "Register" button also closes the form

The chevron toggle and the "Register" button control the same details row. The click handler in analytics-module.js toggles the row. When the row is open, a click on "Register" closes the form.

Fix: Make the "Register" button open the row only.

6. The credit warnings are hidden

The old SiteCard always showed the "exhausted" and "low credits" alerts with the "Manage plan" link. Now these alerts are in the collapsed details row.

Fix: Open the details row by default when warning or exhausted is set. Alternatively, show the link in the touchpoints column.

Minor items

7. The widget sends more API requests

On a cache miss, the widget sends three API requests one after the other. Before the change, it sent one request. Item 1 partly fixes this.

8. The open and closed state is stored in different ways

The UI stores the open and closed state in three different ways:

  • The page module details: in the backend user uc, through Persistent.
  • The plans section: in localStorage.
  • The site groups: the UI does not store the state. The "Inactive" group is always collapsed after a page reload.

Fix: Use one method for all three cases.

9. Unused CSS

SiteCard.html was removed, but these rules are still in Resources/Public/Css/Backend.css:

  • .tx-analytics-site-card > .card-footer (line 98)
  • .tx-analytics-site-card-value in the media query (line 395)

Fix: Remove the two rules.

10. An interface changed

SitePerformanceServiceInterface::buildMetricItems() has a new optional parameter. Existing callers continue to work. Custom implementations of the interface break.

11. Two number formats

The page module uses a narrow no-break space as the thousands separator. The widget uses MetricFormatter::formatNumber() and shows "3.400". The widget also shows this format in the English UI.

12. The toggle label has no context

The toggle in each row has the same label: "Details". A screen reader reads the same text for each row.

Fix: Add the site name to the label, for example "Details: {site.pageName}".

13. An aria-label has no effect

The aria-label on .tx-analytics-performance-tooltip-chart is on a div that has no role. Screen readers usually ignore this label. The element is now also rendered when the chart is empty. The SVG already has its own label.

Fix: Remove the aria-label from the div.

@hahnmg

hahnmg commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

@sfsmfc Thanks for the review. All 13 points are in the last three commits. Over to you:

  1. Attach to an active site: this is yours. registerAction() ignores mode and targetSiteIdentifier, so "Attach" fails with "Invalid input". This is the last thing keeping the PR in draft.
  2. Check point 1 on your site with traffic: does averageSessionDuration match averageVisitDuration? Is bounceRate a percentage? My only real site had no traffic.
  3. Point 10: I kept both interface changes (buildMetricItems() and the new fetchSiteDailyMetrics()). The interfaces are internal to the extension. OK for you?
  4. Point 11: both number formats existed before this PR. I'd fix that in a separate issue.

One thing to know: the API groups the date dimension by UTC day, so I send the date range in UTC.

…and page bar

Data
- Get views, visitors and bounce rate of the site from analytics/sessions
  and the time on page from the page view duration graph (same metric as
  the page module). Load totals, previous period and daily series with
  five parallel requests.
- Query full UTC days in the widget and the page bar, so charts have no
  partial first day.
- Do not cache missing current values; drop the trend when the previous
  period fails; drop the details when the series fails.
- Put all extension caches into the group "pages", so cache:flush and the
  backend cache buttons flush them.

Trends
- Show trends from 0 and "±0" for unchanged values.
- Show an info icon with an accessible tooltip when there is no value to
  compare with. Use the same rules in the page performance bar.
- Name the percentage points of the bounce rate in the tooltip.

Display
- Format dates in the locale of the backend user.
- Draw flat zero series on the baseline of charts with an explicit yMin.
- Render the widget sparklines with a ViewHelper instead of the service.
- Rename "Visits" to "Views" and "Avg. visit duration" to
  "Avg. time on page" in the widget; update the documentation.
…registration

The API has no support for this choice yet, so the option always failed.
A follow-up change adds the choice together with its implementation.
@sfsmfc
sfsmfc marked this pull request as ready for review October 14, 2026 09:10
@sfsmfc
sfsmfc added this pull request to stack #31 October 14, 2026 09:22
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