Skip to content

Analytics#89

Open
TatevikGr wants to merge 2 commits into
devfrom
analytics
Open

Analytics#89
TatevikGr wants to merge 2 commits into
devfrom
analytics

Conversation

@TatevikGr

@TatevikGr TatevikGr commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added a new Analytics page accessible from the app.
    • The page includes summary metrics, campaign performance charts, domain confirmation stats, and top-domain/local-part tables.
  • Bug Fixes

    • Added coverage to ensure the Analytics page loads correctly and receives the expected page data.
  • Tests

    • Added integration and UI tests for the new Analytics experience.

Summary

Provide a general description of the code changes in your pull request …
were there any bugs you had fixed? If so, mention them. If these bugs have open
GitHub issues, be sure to tag them here as well, to keep the conversation
linked together.

Unit test

Are your changes covered with unit tests, and do they not break anything?

You can run the existing unit tests using this command:

vendor/bin/phpunit tests/

Code style

Have you checked that you code is well-documented and follows the PSR-2 coding
style?

You can check for this using this command:

vendor/bin/phpcs --standard=PSR2 src/ tests/

Other Information

If there's anything else that's important and relevant to your pull
request, mention that information here. This could include benchmarks,
or other information.

If you are updating any of the CHANGELOG files or are asked to update the
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.

Thanks for contributing to phpList!

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new Analytics page is added end-to-end. On the backend, AnalyticsController registers a GET /analytics/ Symfony route that renders the existing SPA shell template with session-derived api_token and configured api_base_url. On the frontend, AnalyticsView.vue is a new Vue SFC that fetches campaign statistics, view opens, top domains, domain confirmation stats, and top local parts concurrently on mount, then renders metric cards, an ApexCharts bar chart, domain confirmation stats with a progress bar, and campaign/domain/local-part tables. The component is wired into the Vue Router at /analytics. A Vitest suite and a Symfony integration test cover the new pieces. One import in api.js was reformatted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the change, but it is too generic to explain what the pull request actually adds. Use a concise, specific title like "Add Analytics page and controller".
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch analytics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/views/AnalyticsView.vue`:
- Around line 71-125: The empty-state sections in AnalyticsView.vue are
rendering before loadAnalytics() finishes, which causes a misleading “no data”
flash on first paint. Update the loading logic in AnalyticsView and the affected
empty-state blocks to stay hidden until the initial fetch completes, either by
introducing a hasLoaded flag or by guarding all “No ... found” branches with
!isLoading. Make sure the fix is applied consistently across the
domainConfirmation view and the other matching empty states so the template only
shows “no data” after the data request has actually finished.
- Around line 306-320: The formatDate helper in AnalyticsView.vue currently
passes date-only strings straight into new Date, which can shift the displayed
day in some time zones. Update formatDate to detect YYYY-MM-DD inputs and
normalize them before formatting, while keeping the existing Unknown date
fallback and Intl.DateTimeFormat output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 137c0e28-face-46b0-ab9a-c3fa52df4efc

📥 Commits

Reviewing files that changed from the base of the PR and between 7a456a9 and 4bc4166.

📒 Files selected for processing (6)
  • assets/router/index.js
  • assets/vue/api.js
  • assets/vue/views/AnalyticsView.spec.js
  • assets/vue/views/AnalyticsView.vue
  • src/Controller/AnalyticsController.php
  • tests/Integration/Controller/AnalyticsControllerTest.php

Comment on lines +71 to +125
<div v-if="domainConfirmation" class="space-y-4">
<div>
<p class="text-xs uppercase tracking-wide text-slate-500">Domain</p>
<p class="mt-1 break-all text-sm font-semibold text-slate-900">
{{ domainConfirmation.domain || 'Unknown domain' }}
</p>
</div>

<div class="grid grid-cols-2 gap-3 text-sm">
<div class="rounded-lg bg-emerald-50 px-3 py-3">
<p class="text-xs uppercase tracking-wide text-emerald-700">Confirmed</p>
<p class="mt-1 text-lg font-bold text-emerald-900">
{{ formatCount(domainConfirmation.confirmed) }}
</p>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-3">
<p class="text-xs uppercase tracking-wide text-amber-700">Unconfirmed</p>
<p class="mt-1 text-lg font-bold text-amber-900">
{{ formatCount(domainConfirmation.unconfirmed) }}
</p>
</div>
</div>

<div>
<div class="mb-2 flex items-center justify-between text-xs text-slate-500">
<span>Confirmation rate</span>
<span>{{ formatPercentage(domainConfirmation.confirmationRate) }}</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div
class="h-full rounded-full bg-emerald-500"
:style="{ width: `${clampPercentage(domainConfirmation.confirmationRate)}%` }"
/>
</div>
</div>

<dl class="grid grid-cols-3 gap-3 text-sm">
<div class="rounded-lg bg-slate-50 px-3 py-3">
<dt class="text-xs uppercase tracking-wide text-slate-500">Total</dt>
<dd class="mt-1 font-semibold text-slate-900">{{ formatCount(domainConfirmation.total) }}</dd>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-3">
<dt class="text-xs uppercase tracking-wide text-slate-500">Confirmed</dt>
<dd class="mt-1 font-semibold text-slate-900">{{ formatCount(domainConfirmation.confirmed) }}</dd>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-3">
<dt class="text-xs uppercase tracking-wide text-slate-500">Unconfirmed</dt>
<dd class="mt-1 font-semibold text-slate-900">{{ formatCount(domainConfirmation.unconfirmed) }}</dd>
</div>
</dl>
</div>

<div v-else class="flex min-h-[260px] items-center justify-center text-sm text-slate-500">
No confirmation data found.
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid the false “no data” flash on first paint.

loadAnalytics() starts in onMounted, so the first render happens with empty refs and isLoading === false. That briefly shows “No ... found” states before any request has finished. A hasLoaded flag, or consistently gating those empty states behind !isLoading, would make the UI truthful.

Also applies to: 152-156, 193-197, 241-245, 287-288, 486-486

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/views/AnalyticsView.vue` around lines 71 - 125, The empty-state
sections in AnalyticsView.vue are rendering before loadAnalytics() finishes,
which causes a misleading “no data” flash on first paint. Update the loading
logic in AnalyticsView and the affected empty-state blocks to stay hidden until
the initial fetch completes, either by introducing a hasLoaded flag or by
guarding all “No ... found” branches with !isLoading. Make sure the fix is
applied consistently across the domainConfirmation view and the other matching
empty states so the template only shows “no data” after the data request has
actually finished.

Comment on lines +306 to +320
const formatDate = (dateValue) => {
if (!dateValue) {
return 'Unknown date'
}

const date = new Date(dateValue)
if (Number.isNaN(date.getTime())) {
return 'Unknown date'
}

return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
}).format(date)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== outline AnalyticsView.vue ==\n'
ast-grep outline assets/vue/views/AnalyticsView.vue --view expanded || true

printf '\n== relevant slices AnalyticsView.vue ==\n'
sed -n '260,360p' assets/vue/views/AnalyticsView.vue
printf '\n---\n'
sed -n '420,540p' assets/vue/views/AnalyticsView.vue

printf '\n== relevant slices AnalyticsView.spec.js ==\n'
sed -n '1,240p' assets/vue/views/AnalyticsView.spec.js

printf '\n== node date probe ==\n'
node - <<'JS'
const d = new Date('2026-06-01');
console.log('toISOString', d.toISOString());
console.log('en-US default', new Intl.DateTimeFormat('en-US',{year:'numeric',month:'short',day:'2-digit'}).format(d));
console.log('TZ', process.env.TZ || '(default)');
JS

Repository: phpList/web-frontend

Length of output: 7721


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== template slice AnalyticsView.vue ==\n'
sed -n '1,260p' assets/vue/views/AnalyticsView.vue

printf '\n== search loading / empty state branches ==\n'
rg -n "isLoading|empty|No|nothing|failed|v-if|v-else" assets/vue/views/AnalyticsView.vue

Repository: phpList/web-frontend

Length of output: 12389


Parse date-only strings before formatting. new Date('2026-06-01') is treated as UTC midnight, so local formatting can show the previous day in some time zones. Normalize YYYY-MM-DD values before passing them to Date.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/vue/views/AnalyticsView.vue` around lines 306 - 320, The formatDate
helper in AnalyticsView.vue currently passes date-only strings straight into new
Date, which can shift the displayed day in some time zones. Update formatDate to
detect YYYY-MM-DD inputs and normalize them before formatting, while keeping the
existing Unknown date fallback and Intl.DateTimeFormat output.

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