Skip to content

feat: let admins switch download analytics on from the settings page - #2340

Merged
netomi merged 13 commits into
mainfrom
gnugomez/main/extension-analytics-setting
Oct 16, 2026
Merged

netomi merged 13 commits into
mainfrom
gnugomez/main/extension-analytics-setting

Conversation

@gnugomez

@gnugomez gnugomez commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Download analytics shows a weekly downloads card on every extension page as soon as ovsx.analytics.enabled is set. A registry that has just turned analytics on usually wants to backfill its download history first, so the card would show incomplete data in the meantime. This adds a runtime setting that decides whether the web UI shows the card, switched by an admin on the Settings page, and lets a deployment add runtime settings of its own to that page.

Server

  • DownloadAnalyticsSetting, stored under download-analytics-visible, off by default. Switching it on is refused where analytics is not configured.
  • GET /admin/settings returns the stored switch as downloadAnalyticsVisible, and leaves it out where analytics is not configured (ovsx.analytics.enabled). In an update, as for every setting, an absent field leaves it unchanged.
  • The public site settings publish the effective value, downloadAnalyticsVisible = configured && switched on, so a switch left on after analytics was unconfigured reads as off.
  • The switch only controls what the web UI shows. Collection, ingestion and the series endpoint are unchanged by it: the endpoint serves data wherever analytics is configured, and is unmapped (404) where it is not.
  • GET /api/{namespace}/{extension}/analytics/downloads now always returns the daily downloads of the last 52 weeks, ending today, and ignores query parameters. A public endpoint with a caller-chosen from/to/interval let anyone request arbitrary windows, up to five years each, every one a miss in the settled-range cache and a distinct URL for HTTP caches. With one window per extension, the work per request is bounded, the settled-range cache holds one entry per extension and day (refreshed after its TTL, ovsx.analytics.settled-cache.ttl, one hour by default), and the 10-minute public Cache-Control works at CDNs for the plain URL. Appending an arbitrary query string still bypasses a CDN; it no longer changes what the server computes.
  • analyticsEnabled is removed from /api/version. It was added in feat: time-series download analytics #2134 and is unreleased; the site settings carry the same information and follow the switch live.
  • SettingsService returns SettingsJson and SiteSettingsJson and takes a SettingsJson update, like the other services, so AdminAPI and SettingsAPI no longer pair it with SettingsJsonMapper. The mapper is now a package-private detail of the service.
  • Settings a deployment registers travel on the settings endpoints. SettingsJson and SiteSettingsJson carry them as additional top-level properties (@JsonAnyGetter/@JsonAnySetter), named in camelCase after the row key: a setting stored under audit-log-enabled is auditLogEnabled on the wire. Safety is unchanged: the service only writes rows a registered setting claims, so an unknown property is ignored; every value goes through its setting's validate; built-in settings are writable only through their own properties, never under another spelling; and only a PublicSetting's own view reaches /api/-/settings.

Web UI

  • An Analytics section on the Settings page with the switch, shown only where the server reports it, i.e. where analytics is configured.
  • The weekly downloads card follows SiteSettings.downloadAnalyticsVisible. An admin's save seeds the public settings and they poll, so a switch flipped during a session reaches the card without a reload.
  • getExtensionDownloadSeries takes only the namespace and name, since the server fixes the window. The unreleased DownloadSeriesInterval type is removed.
  • Deployments can add switches to the built-in sections, or sections of their own, through PageSettings.elements.adminSettingsSections. Additive only: a switch over a key the page already uses is ignored, a section left without switches is omitted, and switches added to a built-in section share its availability.
  • The Settings page sorts its sections by title, contributed ones included.
  • The max extension size control moves into its own component, settings-max-size-item.tsx.

Adding a setting in a deployment

A deployment that builds on the registry, such as openvsx-ef, needs two pieces and no changes upstream.

1. Server: a WritableSetting bean in the deployment's own package, picked up by its component scan. The row key is kebab-case; the property on the wire is its camelCase form.

@Component
public class RegistryAnalyticsPublicSetting implements WritableSetting<Boolean>, PublicSetting {

    static final String KEY = "registry-analytics-public";   // -> "registryAnalyticsPublic"

    @Override
    public String getName() {
        return KEY;
    }

    @Override
    public Boolean read(SettingRows stored) {
        return stored.asBoolean(KEY, false);                  // the default where nothing is stored
    }

    @Override
    public Boolean merge(Boolean current, SettingRows update) {
        return update.asBoolean(KEY, current);                // absent in the update: unchanged
    }

    @Override
    public Map<String, Object> toRows(Boolean value) {
        return Map.of(KEY, value);
    }

    // Optional: refuse a value with WritableSetting.reject("..."), which answers 400.
    @Override
    public void validate(Boolean value, Boolean current) {
    }

    // Only if visitors may see it: what /api/-/settings publishes. Leave PublicSetting off otherwise.
    @Override
    public Map<String, Object> publicView(SettingRows stored) {
        return toRows(read(stored));
    }
}

The value is stored, cached per node and propagated across the cluster like the built-in settings, and a save publishes SettingsChangedEvent. Server code that needs the value at runtime can read SettingsService.getCurrentSettings().getAdditionalSettings().get("registryAnalyticsPublic"), which comes from the cached snapshot.

2. Web UI: an adminSettingsSections entry in the deployment's PageSettings, keyed by the camelCase property:

elements: {
    adminSettingsSections: [
        {
            id: 'analytics',   // a built-in id appends to that section; any other id adds a section
            flags: [
                {
                    key: 'registryAnalyticsPublic',
                    title: 'Public registry analytics',
                    description: 'Shows the registry analytics page to visitors.'
                }
            ]
        }
    ]
}

A switch appended to the built-in analytics section is shown only where download analytics is configured; a section of its own can hide itself with available. If the setting is public, a visitor-facing page reads it through useSiteSettings(). SiteSettings only types the built-in properties, so a deployment's own property is read as (useSiteSettings().data as Record<string, unknown> | undefined)?.registryAnalyticsPublic === true.

Upgrade note

Registries already running with ovsx.analytics.enabled=true stop showing the weekly downloads card until an admin switches it on.

Test plan

  • Server: DownloadAnalyticsAPITest (fixed window, query parameters ignored), DownloadAnalyticsSettingTest, SettingsJsonMapperTest (additional properties both ways, the built-in guard, the flat wire shape), SettingsServiceTest (the effective analytics switch, and a deployment-style setting read, updated and published through the JSON documents), SettingsAPITest, AdminAPITest, LocalRegistryServiceTest; the full suite passes apart from the Testcontainers tests
  • Web UI: yarn lint, yarn tsc, yarn test (536 tests), including the Settings page and weekly downloads specs
  • Postgres-backed tests (DownloadAnalyticsEndpointTest, DownloadAnalyticsDisabledTest) left to CI

@gnugomez
gnugomez requested review from netomi and a balanced review from Copilot October 15, 2026 15:42

Copilot AI 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.

🟡 Changes recommended

Analytics visibility can remain stale or incorrect, and contribution filtering permits conflicting settings controls.

3 open findings
What changed in this PR

Adds an admin-controlled download analytics switch, allowing registries to backfill history before exposing charts and analytics endpoints.

Changes:

  • Adds runtime analytics visibility settings and endpoint gating.
  • Adds the dashboard switch and extensible settings sections.
  • Updates API models, tests, and changelogs.
File Description
webui/​test/​unit/​pages/​admin-dashboard/​settings.spec.tsx Tests analytics saves and contributed switches.
webui/​src/​pages/​admin-dashboard/​use-settings.ts Seeds analytics into public settings cache.
webui/​src/​pages/​admin-dashboard/​settings.tsx Renders configurable settings sections.
webui/​src/​pages/​admin-dashboard/​settings-sections.ts Defines built-in and contributed sections.
webui/​src/​pages/​admin-dashboard/​settings-max-size-item.tsx Extracts the size-limit control.
webui/​src/​page-settings.ts Adds section contribution configuration.
webui/​src/​index.ts Exports contribution types.
webui/​src/​extension-registry-types.ts Adds analytics settings fields.
webui/​CHANGELOG.md Documents dashboard additions.
server/​src/​test/​java/​org/​eclipse/​openvsx/​UserAPITest.java Updates registry service wiring.
server/​src/​test/​java/​org/​eclipse/​openvsx/​settings/​SettingsJsonMapperTest.java Tests analytics field mapping.
server/​src/​test/​java/​org/​eclipse/​openvsx/​settings/​SettingsAPITest.java Tests public analytics serialization.
server/​src/​test/​java/​org/​eclipse/​openvsx/​settings/​DownloadAnalyticsVisibilityServiceTest.java Tests effective visibility.
server/​src/​test/​java/​org/​eclipse/​openvsx/​settings/​DownloadAnalyticsSettingTest.java Tests storage, validation, and publication.
server/​src/​test/​java/​org/​eclipse/​openvsx/​RegistryAPITest.java Updates registry service wiring.
server/​src/​test/​java/​org/​eclipse/​openvsx/​LocalRegistryServiceTest.java Tests reported analytics availability.
server/​src/​test/​java/​org/​eclipse/​openvsx/​analytics/​DownloadAnalyticsEndpointTest.java Enables analytics during endpoint tests.
server/​src/​test/​java/​org/​eclipse/​openvsx/​analytics/​DownloadAnalyticsAPITest.java Tests disabled analytics returning 404.
server/​src/​test/​java/​org/​eclipse/​openvsx/​admin/​AdminAPITest.java Updates registry service wiring.
server/​src/​main/​java/​org/​eclipse/​openvsx/​settings/​SettingsJsonMapper.java Maps analytics settings fields.
server/​src/​main/​java/​org/​eclipse/​openvsx/​settings/​DownloadAnalyticsVisibilityService.java Combines configuration and runtime visibility.
server/​src/​main/​java/​org/​eclipse/​openvsx/​settings/​DownloadAnalyticsSetting.java Implements the persisted analytics switch.
server/​src/​main/​java/​org/​eclipse/​openvsx/​LocalRegistryService.java Reports effective analytics availability.
server/​src/​main/​java/​org/​eclipse/​openvsx/​json/​SiteSettingsJson.java Exposes public analytics visibility.
server/​src/​main/​java/​org/​eclipse/​openvsx/​json/​SettingsJson.java Adds the admin analytics field.
server/​src/​main/​java/​org/​eclipse/​openvsx/​json/​RegistryVersionJson.java Clarifies availability documentation.
server/​src/​main/​java/​org/​eclipse/​openvsx/​analytics/​DownloadAnalyticsAPI.java Gates requests on runtime visibility.
server/​CHANGELOG.md Documents admin-controlled analytics.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@gnugomez
gnugomez added this pull request to stack #2341 October 15, 2026 18:48
@gnugomez
gnugomez marked this pull request as ready for review October 15, 2026 20:00
Base automatically changed from gnugomez/main/settings-reader-beans to main October 16, 2026 07:02
@netomi
netomi force-pushed the gnugomez/main/extension-analytics-setting branch from 76c8be4 to a49ac9c Compare October 16, 2026 07:11
netomi and others added 10 commits October 16, 2026 08:17
The site settings carry whether download analytics is shown and follow
the admin switch live, so the flag on /api/version has no reader left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The switch decides whether the web UI shows download analytics, not
whether analytics is collected, which ovsx.analytics.enabled controls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The visibility switch only decides what the web UI shows. The endpoint
serves data wherever analytics is configured, so it can be read while a
registry backfills its history before showing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…analytics tests

A contributed section whose switches were all dropped as duplicates
rendered a bare heading. The weekly downloads test for a hidden card
asserted before the site settings loaded, so it passed even with the
check removed. Document that contributed switches share the built-in
section's availability.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributed sections are placed among the built-in ones alphabetically
instead of after them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The endpoint is public, and a caller-chosen from/to/interval let anyone
request arbitrary windows, each a cache miss on the server and a
distinct URL for HTTP caches. The only consumer, the weekly downloads
card, always asks for the last 52 weeks of days, so the server now
fixes that window and ignores query parameters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onfigured

downloadAnalyticsVisible is absent from GET /admin/settings where download
analytics is not configured, which replaces the separate read-only
downloadAnalyticsConfigured field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The controllers had to pair the service with SettingsJsonMapper in both
directions. The service now returns SettingsJson and SiteSettingsJson
and takes a SettingsJson update, like the other services do, and the
mapper becomes a package-private detail of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elogs

DownloadAnalyticsSetting.isVisible() lost its last callers, so it and
the store it read go. Cover the effective analytics switch through
SettingsService and a contributed section hiding itself, split a test
whose assertion read like a bug, and shorten the changelog entries to
their final state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SettingsJson carried a fixed set of fields, so a WritableSetting added by
a deployment's own module never reached /admin/settings and a switch
contributed through adminSettingsSections could not be saved. Rows of
such settings now travel as camelCase properties named after their row
keys, on the public settings too when the setting is a PublicSetting.
Built-in settings stay writable only through their own properties.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netomi
netomi merged commit 0da91fe into main Oct 16, 2026
5 checks passed
@netomi
netomi deleted the gnugomez/main/extension-analytics-setting branch October 16, 2026 09:29
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.

3 participants