Skip to content

Add analytics of user behaviour - #96

Merged
david-mears-2 merged 12 commits into
mainfrom
analytics-on-downloads
May 14, 2026
Merged

david-mears-2 merged 12 commits into
mainfrom
analytics-on-downloads

Conversation

@david-mears-2

@david-mears-2 david-mears-2 commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

This adds a few more analytics Katy would like to have on vaxviz:

  • usage of downloads
  • usage of plot controls (to answer the question, what are the combinations of controls that users are interested in)
  • usage of log scale toggle

Downloads analytics

Do accounting of download events under a single event type named 'download', but also send information on (1) the type of download, corresponding to whether the download came via the location-based download UI or the other (file-based), (2) if applicable, which locations were selected, and (3) which files were downloaded.

This is sent to posthog, and we can configure the way we want to slice that data there; this can and will be changed, so the analytics sent from vaxviz should not make assumptions about that.

Plot controls

We want to know which views (combinations of plot control selections) are of most interest to users so that we can adjust our controls in response if necessary. Every input change triggers a change in the app store, but that would pollute the events with transient states that were just the user playing around or navigating to their desired state via changing multiple inputs. Thus I only send events once the view has been stable for 10 seconds.

Log scale toggle analytics

Although this is technically a subset of the plot controls event, in order to make it really easy to get a sense of how much this toggle is used, I track this separately, with an event that also records the state of the plot view at the time of toggling the scale from log to linear / vice versa.

Analytics dashboard

Example of how the VIMC dashboard could be configured to consume these analytics events (sent from my localhost): https://eu.posthog.com/project/128512/dashboard/543420

  • TODO: exclude localhost events from all VIMC dashboard components
  • TODO: exclude behaviour analytics events that pre-date publication from all VIMC dashboard components

Summary by CodeRabbit

  • New Features

    • Analytics added for downloads and plot control changes (including log/linear toggles), capturing download type and selected items.
  • Documentation

    • Privacy notice clarified to explicitly include user behaviour in collected analytics.
  • Tests

    • Unit and end-to-end tests updated to assert analytics tracking; shared download helpers added for E2E flows.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds analytics helpers (trackDownload, trackLogScaleToggle, debounced trackPlotControls), records download events in DownloadSelect and LocationDownload, updates privacy wording, and extends unit and e2e tests and helpers to exercise the new tracking.

Changes

Download Analytics Tracking

Layer / File(s) Summary
Analytics Utility
src/utils/analytics.ts
Adds trackDownload and trackLogScaleToggle and exports a debounced trackPlotControls; emits Posthog events only when analytics are permitted.
useAppStoreAnalytics composable
src/stores/composables/useAppStoreAnalytics.ts
New composable that snapshots store refs into plot analytics properties and registers watchers to call trackPlotControls and trackLogScaleToggle.
Store wiring
src/stores/appStore.ts
Wires useAppStoreAnalytics into the store, passing reactive refs including logScaleEnabled and other view/plot state.
DownloadSelect Integration
src/components/DownloadSelect.vue
Imports trackDownload and records summary_table_by_filename with selected filenames before initiating download.
LocationDownload Integration
src/components/LocationDownload.vue
Imports trackDownload and records summary_table_by_location with selected locations and computed filenames before initiating download.
Privacy Disclosure
src/components/PrivacyModal.vue
Privacy modal text updated to include "user behaviour" among collected analytical data.
DownloadSelect Tests
tests/unit/components/DownloadSelect.spec.ts
Adds mocks and assertions verifying trackDownload is called with summary_table_by_filename and expected filenames across scenarios.
LocationDownload Tests
tests/unit/components/LocationDownload.spec.ts
Adds shared mock helpers and assertions verifying trackDownload is called with summary_table_by_location, selected locations, and filenames in single- and multi-location tests.
Analytics Utility Tests
tests/unit/utils/analytics.spec.ts
Adds tests asserting trackDownload and trackLogScaleToggle call Posthog.capture with expected event names and payloads; uses fake timers to test debounce.
Analytics tests (module reload)
tests/unit/utils/analyticsDisabled.spec.ts, tests/unit/utils/analyticsExplicitlyEnabled.spec.ts
Refactors tests to dynamically import analytics per test with module reset/localStorage control to exercise disabled/enabled initialization paths.
Store Unit Tests
tests/unit/stores/appStore.spec.ts
Adds tests spying on analytics to verify trackPlotControls and trackLogScaleToggle are called with expected payloads when controls change and when logScaleEnabled toggles.
E2E Helpers
tests/e2e/utils.ts
Adds exported doDownload(page, expectedNumberOfFiles) helper that waits for Playwright download completion, closes dialog, and returns the Download instance; import list updated to include Download.
E2E Specs
tests/e2e/downloads.spec.ts
Specs refactored to use shared helpers and explicit UI clicks; adds a test verifying location-focused download ZIP naming (e.g., vaxviz_download_Oceania.zip).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • M-Kusumgar

Poem

🐰 I hop and count each file and name,

CSVs and ZIPs I gently claim,
I log each download with a tiny cheer,
A hush of events for the team to peer,
Hooray — analytics by rabbit’s fame!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "Add analytics of user behaviour" is vague and overly broad—it doesn't specify which analytics are being added or clarify the main implementation details. Consider a more specific title like "Add analytics for downloads, plot controls, and log-scale toggles" to clearly convey the scope of the changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch analytics-on-downloads

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@david-mears-2
david-mears-2 requested review from EmmaLRussell and absternator and removed request for absternator May 11, 2026 13:18
Do accounting of download events under a single event named 'download', but
also send information on (1) the type of download, corresponding to whether
the download came via the location-based download UI or the other (file-based),
(2) if applicable, which locations were selected, and (3) which files were
downloaded.

This is sent to posthog, and we can configure the way we want to slice that
data there; this can and will be changed, so the analytics sent from vaxviz
should not make assumptions about that.
@david-mears-2
david-mears-2 force-pushed the analytics-on-downloads branch from e88ed5b to 7a8ff6c Compare May 11, 2026 13:20
@codecov

codecov Bot commented May 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.43%. Comparing base (c367ac8) to head (4b80b25).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #96      +/-   ##
==========================================
+ Coverage   98.39%   98.43%   +0.04%     
==========================================
  Files          37       38       +1     
  Lines         870      896      +26     
  Branches      247      256       +9     
==========================================
+ Hits          856      882      +26     
  Misses          9        9              
  Partials        5        5              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit/utils/analytics.spec.ts (1)

93-104: ⚡ Quick win

Add edge-case coverage for trackDownload.

Current tests only validate the success path. Please add cases for “analytics disabled” and “capture throws” so privacy and resilience behavior stay protected.

🤖 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 `@tests/unit/utils/analytics.spec.ts` around lines 93 - 104, Add two unit tests
for trackDownload: one where analytics is disabled (e.g., set the flag or
env/config that trackDownload checks) and assert posthog.capture is NOT called;
and one where posthog.capture is stubbed to throw and assert that trackDownload
does not throw (it swallows the error) and that capture was invoked once with
the expected payload. Use the existing vi.spyOn/posthog spy pattern and
restore/cleanup spies after each test to avoid cross-test pollution; reference
the trackDownload function and posthog.capture to find where to add these cases.
🤖 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 `@src/utils/analytics.ts`:
- Around line 68-75: trackDownload is missing the privacy gating and error
handling used elsewhere; update the function so it first calls
analyticsPermittedInitially() and returns early if false, then wrap the
posthog.capture(...) call in a try-catch to swallow/log any exceptions (e.g.,
console.error or existing logger) so download flows aren't interrupted; mirror
the pattern used in getUserLocation and ensure analyticsPermittedInitially is
imported/available in the module.

---

Nitpick comments:
In `@tests/unit/utils/analytics.spec.ts`:
- Around line 93-104: Add two unit tests for trackDownload: one where analytics
is disabled (e.g., set the flag or env/config that trackDownload checks) and
assert posthog.capture is NOT called; and one where posthog.capture is stubbed
to throw and assert that trackDownload does not throw (it swallows the error)
and that capture was invoked once with the expected payload. Use the existing
vi.spyOn/posthog spy pattern and restore/cleanup spies after each test to avoid
cross-test pollution; reference the trackDownload function and posthog.capture
to find where to add these cases.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18983458-98a5-43cb-8aa0-ce495bfef391

📥 Commits

Reviewing files that changed from the base of the PR and between f8e2af4 and 7a8ff6c.

📒 Files selected for processing (7)
  • src/components/DownloadSelect.vue
  • src/components/LocationDownload.vue
  • src/components/PrivacyModal.vue
  • src/utils/analytics.ts
  • tests/unit/components/DownloadSelect.spec.ts
  • tests/unit/components/LocationDownload.spec.ts
  • tests/unit/utils/analytics.spec.ts

Comment thread src/utils/analytics.ts
@david-mears-2
david-mears-2 removed the request for review from EmmaLRussell May 11, 2026 13:29
@david-mears-2 david-mears-2 changed the title Add analytics on download behaviour Add analytics on download usage and log-scale-toggle usage May 13, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@src/utils/analytics.ts`:
- Line 3: Remove the unused import "Dimension" from the top of the module to
satisfy the `@typescript-eslint/no-unused-vars` rule; locate the import statement
importing Dimension (import type { Dimension } from "@/types";) in
src/utils/analytics.ts and delete it (or remove Dimension from the import list
if other types are present).
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2375014-d443-4a8a-91cb-0bd3796d2853

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4b1f3 and 762b698.

📒 Files selected for processing (4)
  • src/stores/appStore.ts
  • src/utils/analytics.ts
  • tests/unit/stores/appStore.spec.ts
  • tests/unit/utils/analytics.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • src/stores/appStore.ts

Comment thread src/utils/analytics.ts Outdated
@david-mears-2 david-mears-2 changed the title Add analytics on download usage and log-scale-toggle usage Add analytics of user behaviour May 13, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@src/stores/appStore.ts`:
- Line 8: The import statement in src/stores/appStore.ts referencing
'@/stores/composables/useAppStoreAnalytics' cannot be resolved (ENOENT); either
add the missing composable module at that path or update the import to the
correct existing file/casing (e.g., match the actual filename under
stores/composables) so Vite can resolve it—locate the import line in appStore.ts
and either create useAppStoreAnalytics.ts with the expected export or change the
import to the correct module name/export.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f62f4dd0-18c1-4a62-9485-2a6a17ce751f

📥 Commits

Reviewing files that changed from the base of the PR and between 762b698 and 9c11ea0.

📒 Files selected for processing (6)
  • src/stores/appStore.ts
  • src/utils/analytics.ts
  • tests/unit/stores/appStore.spec.ts
  • tests/unit/utils/analytics.spec.ts
  • tests/unit/utils/analyticsDisabled.spec.ts
  • tests/unit/utils/analyticsExplicitlyEnabled.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/stores/appStore.spec.ts
  • tests/unit/utils/analytics.spec.ts

Comment thread src/stores/appStore.ts
@david-mears-2
david-mears-2 force-pushed the analytics-on-downloads branch from 9c11ea0 to 847365b Compare May 13, 2026 05:09

@EmmaLRussell EmmaLRussell 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.

Looks great! I wonder if 10s might be slightly too long as a minimum meaningful interaction, but it's in that ballpark.,

For the plot control updates I'd suggest perhaps replacing "true"/"false" for the Split by activity type part with "split"/"no-split" - potentially a little more readable in the posthog dashboard.

What would be the approach if an analytics user wanted to aggregate the analytics e.g. show me all the downloads for India, regardless of user country, or show me all the plot control updates for Cholera, regardless of the other control values? I see that if I export to csv I do get all the values in different columns so I guess that makes it easy to do those kinds of operations outside the dashboard?

I think one tricky consideration for this type of analytic is what to do about default values - it might be that the default value is exactly what the user actively wants and would have chosen if it wasn't already selected, so arguably that should be sent as an analytics event after 10s post-load (but it might also just be that the user is disinterested and not paying attention at all!). If we don't send them, those default values are potentially under-represented in the analytics data.

Comment thread src/components/DownloadSelect.vue Outdated
downloadErrors.value = [];
const filenames = files.map((f) => `${f}.csv`);

trackDownload("summary_table_by_filename", { filenames });

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.

Could you call trackDownload from inside downloadCsvAsSingleOrZip to make it part of the download process, so that we don't need to remember to initially track download on each call to the download method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason I didn't was because I wanted to track the use of the different UI journeys to downloading: whether the user downloads files by name or by location. It also means that we don't have to pass an extra locations argument to downloadCsvAsSingleOrZip to be forwarded to trackDownload. But probably still worth it for the reason you say.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The function now looks like:

export const downloadCsvAsSingleOrZip = async (
  dataDir: string,
  filenames: string[],
  zipFileName: string,
  locations?: string[]
) => {
  if (locations) {
    trackDownload("summary_table_by_location", { locations, filenames });
  } else {
    trackDownload("summary_table_by_filename", { filenames });
  }

  if (filenames.length === 1 && filenames[0]) {
    await downloadSingleFile(dataDir, filenames[0]);
  } else if (filenames.length > 1) {
    await downloadAsZip(dataDir, filenames, zipFileName);
  }
};

Comment thread src/components/PrivacyModal.vue Outdated
Comment thread src/utils/analytics.ts
Comment on lines +96 to +100
if (!analyticsPermittedInitially) return;
posthog.capture('log_scale_toggle', {
newScale,
...plotProperties,
});

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.

Should this be delayed to ensure stability too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's only fair.

@david-mears-2

david-mears-2 commented May 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Looks great! I wonder if 10s might be slightly too long as a minimum meaningful interaction, but it's in that ballpark.

Yes... I considered '5' as too short, but I hear that there exist some numbers in between - e.g. 8. I will try that one.

What would be the approach if an analytics user wanted to aggregate the analytics e.g. show me all the downloads for India, regardless of user country, or show me all the plot control updates for Cholera, regardless of the other control values? I see that if I export to csv I do get all the values in different columns so I guess that makes it easy to do those kinds of operations outside the dashboard?

I believe for those specific cases you can use the 'filter' feature when creating an 'insight' (a dashboard component):

image image

I think one tricky consideration for this type of analytic is what to do about default values - it might be that the default value is exactly what the user actively wants and would have chosen if it wasn't already selected, so arguably that should be sent as an analytics event after 10s post-load (but it might also just be that the user is disinterested and not paying attention at all!). If we don't send them, those default values are potentially under-represented in the analytics data.

Interesting point. I don't know a good solution. I would hope that, if the selections that make the default view are indeed helpful, we would also see views showing up as helpful that vary only slightly from the default, which could still tell us whether the default view is a helpful one (or not as the case may be) if only via reinforcing the individual selections that make it up.

Also reduce time for tracking plot controls updates - 10 secs was a bit too long
@david-mears-2
david-mears-2 requested review from EmmaLRussell and removed request for EmmaLRussell May 13, 2026 18:36

@EmmaLRussell EmmaLRussell 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.

LGTM

@david-mears-2

Copy link
Copy Markdown
Contributor Author

I forgot to do this part yesterday:

For the plot control updates I'd suggest perhaps replacing "true"/"false" for the Split by activity type part with "split"/"no-split" - potentially a little more readable in the posthog dashboard.

david-mears-2 and others added 2 commits May 15, 2026 00:11
Co-authored-by: Emma Russell <44669576+EmmaLRussell@users.noreply.github.com>
@david-mears-2
david-mears-2 force-pushed the analytics-on-downloads branch from c8d29b3 to ad55151 Compare May 14, 2026 16:13
@david-mears-2
david-mears-2 merged commit 73b4285 into main May 14, 2026
6 checks passed
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