Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
**/bin/
**/obj/
**/node_modules/
.pnpm-store/
**/coverage/
**/.vite/
artifacts/
src/Umbraco.VercelAnalytics/wwwroot/App_Plugins/
samples/Umbraco.VercelAnalytics.Example/umbraco/Data/
samples/Umbraco.VercelAnalytics.Example/umbraco/Logs/
samples/Umbraco.VercelAnalytics.Example/wwwroot/App_Plugins/
samples/Umbraco.VercelAnalytics.Example/appsettings-schema.json
src/TheBuilder.WebAnalytics/wwwroot/App_Plugins/
samples/TheBuilder.WebAnalytics.Example/umbraco/Data/
samples/TheBuilder.WebAnalytics.Example/umbraco/Logs/
samples/TheBuilder.WebAnalytics.Example/wwwroot/App_Plugins/
samples/TheBuilder.WebAnalytics.Example/appsettings-schema.json
*.db
*.db-shm
*.db-wal
Expand Down
4 changes: 2 additions & 2 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 2,
"generatedAt": "2026-07-16T19:48:26Z",
"title": "Design System: Umbraco Vercel Analytics",
"title": "Design System: Web Analytics",
"extensions": {
"colorMeta": {
"backoffice-ink": {
Expand Down Expand Up @@ -172,7 +172,7 @@
],
"narrative": {
"northStar": "The Editorial Instrument",
"overview": "Umbraco Vercel Analytics is a precise but approachable instrument inside the editorial workspace. It should reveal the traffic signal an editor needs, then get out of the way. The visual language is friendly and conversational, but its structure is disciplined: familiar Umbraco controls, clear hierarchy, compact reporting, and feedback close to the action that caused it. The extension belongs to the backoffice rather than merely living inside it. It inherits Umbraco UI Library semantics, Lato typography, spacing, focus treatment, and surface hierarchy. Custom analytics patterns may borrow the clarity of Vercel Analytics, but they must be translated into Umbraco's interaction language. The system explicitly rejects the density, configuration burden, and specialist terminology of an overly dense enterprise analytics platform. It is not Google Analytics, and it must never feel like a separate third-party dashboard embedded in Umbraco.",
"overview": "Web Analytics is a precise but approachable instrument inside the editorial workspace. It should reveal the traffic signal an editor needs, then get out of the way. The visual language is friendly and conversational, but its structure is disciplined: familiar Umbraco controls, clear hierarchy, compact reporting, and feedback close to the action that caused it. The extension belongs to the backoffice rather than merely living inside it. It inherits Umbraco UI Library semantics, Lato typography, spacing, focus treatment, and surface hierarchy. Custom analytics patterns may borrow the clarity of Vercel Analytics, but they must be translated into Umbraco's interaction language. The system explicitly rejects the density, configuration burden, and specialist terminology of an overly dense enterprise analytics platform. It is not Google Analytics, and it must never feel like a separate third-party dashboard embedded in Umbraco.",
"keyCharacteristics": [
"Native to Umbraco and visually continuous with the backoffice.",
"Compact, calm, and focused on decisions rather than exhaustive data.",
Expand Down
6 changes: 3 additions & 3 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Umbraco Vercel Analytics
name: Web Analytics
description: A native editorial instrument for clear Vercel traffic insight inside Umbraco.
colors:
backoffice-ink: "#060606"
Expand Down Expand Up @@ -82,13 +82,13 @@ components:
padding: "6px 9px"
---

# Design System: Umbraco Vercel Analytics
# Design System: Web Analytics

## Overview

**Creative North Star: "The Editorial Instrument"**

Umbraco Vercel Analytics is a precise but approachable instrument inside the editorial workspace. It should reveal the traffic signal an editor needs, then get out of the way. The visual language is friendly and conversational, but its structure is disciplined: familiar Umbraco controls, clear hierarchy, compact reporting, and feedback close to the action that caused it.
Web Analytics is a precise but approachable instrument inside the editorial workspace. It should reveal the traffic signal an editor needs, then get out of the way. The visual language is friendly and conversational, but its structure is disciplined: familiar Umbraco controls, clear hierarchy, compact reporting, and feedback close to the action that caused it.

The extension belongs to the backoffice rather than merely living inside it. It inherits Umbraco UI Library semantics, Lato typography, spacing, focus treatment, and surface hierarchy. Custom analytics patterns may borrow the clarity of Vercel Analytics, but they must be translated into Umbraco's interaction language.

Expand Down
2 changes: 1 addition & 1 deletion PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The primary users are content editors and marketing staff working in the authent

## Product Purpose

Umbraco Vercel Analytics brings Vercel Web Analytics into the Umbraco backoffice. It helps editors understand site and page traffic where they already manage content, while administrators retain secure control of project connections and server-side credentials.
Web Analytics brings Vercel Web Analytics into the Umbraco backoffice. It helps editors understand site and page traffic where they already manage content, while administrators retain secure control of project connections and server-side credentials.

Success means that an editor can open Analytics, understand the important traffic signals quickly, and return to their content work without needing to visit Vercel or a separate analytics suite.

Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Umbraco Vercel Analytics
# Web Analytics

`Umbraco.VercelAnalytics` displays Vercel Web Analytics in the Umbraco 17 and 18 backoffice.
`TheBuilder.WebAnalytics` displays Vercel Web Analytics in the Umbraco 17 and 18 backoffice.

It provides:

Expand All @@ -23,7 +23,7 @@ The package reads analytics already collected by Vercel. It does not add Vercel
Add the package to the Umbraco web project:

```sh
dotnet add path/to/Your.Umbraco.Web.csproj package Umbraco.VercelAnalytics
dotnet add path/to/Your.Umbraco.Web.csproj package TheBuilder.WebAnalytics
```

The package registers its services and backoffice extensions automatically. No changes to `Program.cs` are required.
Expand Down Expand Up @@ -75,7 +75,7 @@ The shared token is used by every connection. If a project must use a different

### 3. Configure the connection in Umbraco

Sign in as an administrator and open **Settings → Vercel Analytics**.
Sign in as an administrator and open **Settings → Web Analytics**.

1. Select **Add connection**.
2. Enter the Vercel project ID. The project name is loaded from Vercel.
Expand Down Expand Up @@ -106,7 +106,7 @@ On the first successful package startup, the Analytics section is added to the b

To give other users access, add the **Analytics** section to their Umbraco user group. Global reports require Analytics-section access. Document reports additionally require Content-section access and document read permission.

Only administrators can open or update **Settings → Vercel Analytics**.
Only administrators can open or update **Settings → Web Analytics**.

## Configuration-only setup

Expand Down Expand Up @@ -176,7 +176,7 @@ The default cache duration is five minutes. Each Umbraco instance maintains its

After deployment:

1. Open **Settings → Vercel Analytics** and confirm the shared access token says **Configured on the server**.
1. Open **Settings → Web Analytics** and confirm the shared access token says **Configured on the server**.
2. Select **Save settings**, then **Test connection**.
3. Open the global **Analytics** section and confirm totals and history load.
4. If document analytics is enabled, open a mapped published document and select its **Analytics** workspace view.
Expand All @@ -198,9 +198,9 @@ The available reporting window and some dimensions depend on the Vercel plan and

## Development

When the example host runs with `ASPNETCORE_ENVIRONMENT=Development`, **Settings → Vercel Analytics** includes development data presets for a full demo, UTM campaigns, feature flags, and custom events. Add and save a mock connection like any other connection, then select it in the Analytics dashboard. Mock reports are deterministic, require no access token, and never call Vercel. Persisted mock connections become inactive when the host is not running in Development.
When the example host runs with `ASPNETCORE_ENVIRONMENT=Development`, **Settings → Web Analytics** includes development data presets for a full demo, UTM campaigns, feature flags, and custom events. Add and save a mock connection like any other connection, then select it in the Analytics dashboard. Mock reports are deterministic, require no access token, and never call Vercel. Persisted mock connections become inactive when the host is not running in Development.

The client uses pnpm 11. From `src/Umbraco.VercelAnalytics/Client`:
The client uses pnpm 11. From `src/TheBuilder.WebAnalytics/Client`:

```sh
pnpm install
Expand All @@ -216,12 +216,12 @@ Run the example host against the Umbraco version whose document you want to use:
```sh
# Umbraco 17
dotnet run \
--project samples/Umbraco.VercelAnalytics.Example \
--project samples/TheBuilder.WebAnalytics.Example \
-p:UmbracoVersion=17.1.0

# Umbraco 18
dotnet run \
--project samples/Umbraco.VercelAnalytics.Example \
--project samples/TheBuilder.WebAnalytics.Example \
-p:UmbracoVersion=18.0.0
```

Expand All @@ -230,13 +230,13 @@ Use a separate database for each major when switching the example host between v
Then regenerate the client from the matching development endpoint:

```sh
cd src/Umbraco.VercelAnalytics/Client
cd src/TheBuilder.WebAnalytics/Client

# Umbraco 17
corepack pnpm generate-client -- \
https://localhost:44389/umbraco/swagger/umbracovercelanalytics/swagger.json
https://localhost:44389/umbraco/swagger/thebuilderwebanalytics/swagger.json

# Umbraco 18
corepack pnpm generate-client -- \
https://localhost:44389/umbraco/openapi/umbracovercelanalytics.json
https://localhost:44389/umbraco/openapi/thebuilderwebanalytics.json
```
11 changes: 11 additions & 0 deletions TheBuilder.WebAnalytics.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Solution>
<Folder Name="/samples/">
<Project Path="samples/TheBuilder.WebAnalytics.Example/TheBuilder.WebAnalytics.Example.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/TheBuilder.WebAnalytics/TheBuilder.WebAnalytics.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/TheBuilder.WebAnalytics.Tests/TheBuilder.WebAnalytics.Tests.csproj" />
</Folder>
</Solution>
11 changes: 0 additions & 11 deletions Umbraco.VercelAnalytics.slnx

This file was deleted.

Binary file added icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>enable</Nullable>
<CompressionEnabled>false</CompressionEnabled> <!-- Disable compression. E.g. for umbraco backoffice files. These files should be precompressed by node and not let dotnet handle it -->
<UmbracoVersion Condition="'$(UmbracoVersion)' == ''">17.1.0</UmbracoVersion>
<DefineConstants Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(UmbracoVersion)', '18.0.0'))">$(DefineConstants);UMBRACO_18_OR_LATER</DefineConstants>
Expand All @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Umbraco.VercelAnalytics\Umbraco.VercelAnalytics.csproj" />
<ProjectReference Include="..\..\src\TheBuilder.WebAnalytics\TheBuilder.WebAnalytics.csproj" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
#endif
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.VercelAnalytics;
using TheBuilder.WebAnalytics;

namespace Umbraco.VercelAnalytics.Example;
namespace TheBuilder.WebAnalytics.Example;

public sealed class VercelAnalyticsOpenApiComposer : IComposer
{
Expand All @@ -26,14 +26,14 @@ public void Compose(IUmbracoBuilder builder)
builder.AddBackOfficeOpenApiDocument(
Constants.ApiName,
document => document
.WithTitle("Umbraco Vercel Analytics Backoffice API")
.WithTitle("Web Analytics Backoffice API")
.WithBackOfficeAuthentication()
.ConfigureOpenApiOptions(options => options.AddOperationTransformer(
(operation, context, _) =>
{
if (context.Description.ActionDescriptor is ControllerActionDescriptor controller &&
controller.ControllerTypeInfo.Namespace?.StartsWith(
"Umbraco.VercelAnalytics.Controllers",
"TheBuilder.WebAnalytics.Controllers",
StringComparison.InvariantCultureIgnoreCase) is true)
{
operation.OperationId = context.Description.ActionDescriptor.RouteValues["action"];
Expand All @@ -49,7 +49,7 @@ public void Compose(IUmbracoBuilder builder)
Constants.ApiName,
new OpenApiInfo
{
Title = "Umbraco Vercel Analytics Backoffice API",
Title = "Web Analytics Backoffice API",
Version = "1.0",
});
options.OperationFilter<VercelAnalyticsOperationSecurityFilter>();
Expand All @@ -70,7 +70,7 @@ protected override bool CanHandle(
ApiDescription apiDescription,
ControllerActionDescriptor controllerActionDescriptor)
=> controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith(
"Umbraco.VercelAnalytics.Controllers",
"TheBuilder.WebAnalytics.Controllers",
StringComparison.InvariantCultureIgnoreCase) is true;

public override string Handle(ApiDescription apiDescription)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "umbraco-vercel-analytics",
"name": "thebuilder-web-analytics",
"private": true,
"version": "0.1.0",
"type": "module",
Expand Down
2 changes: 2 additions & 0 deletions src/TheBuilder.WebAnalytics/Client/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
allowBuilds:
esbuild: true
14 changes: 14 additions & 0 deletions src/TheBuilder.WebAnalytics/Client/public/umbraco-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "TheBuilder.WebAnalytics",
"name": "Web Analytics",
"version": "0.1.0",
"allowTelemetry": false,
"extensions": [
{
"name": "Web Analytics Bundle",
"alias": "TheBuilder.WebAnalytics.Bundle",
"type": "bundle",
"js": "/App_Plugins/TheBuilder.WebAnalytics/web-analytics.js"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const swaggerUrl = process.argv.slice(2).find((argument) => argument !== '--');
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
console.error(`Please provide the URL to the OpenAPI spec as the first argument found in ${chalk.yellow('package.json')}`);
console.error(`Umbraco 17 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/swagger/umbracovercelanalytics/swagger.json')}`);
console.error(`Umbraco 18 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/openapi/umbracovercelanalytics.json')}`);
console.error(`Umbraco 17 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/swagger/thebuilderwebanalytics/swagger.json')}`);
console.error(`Umbraco 18 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/openapi/thebuilderwebanalytics.json')}`);
process.exit();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const sdk = vi.hoisted(() => ({
eventDetails: vi.fn(),
eventPropertyValues: vi.fn(),
}));
vi.mock("../api/sdk.gen.js", () => ({ UmbracoVercelAnalyticsService: sdk }));
vi.mock("../api/sdk.gen.js", () => ({ WebAnalyticsService: sdk }));
vi.mock("@umbraco-cms/backoffice/element-api", () => ({
UmbElementMixin: <T extends CustomElementConstructor>(base: T) => class extends base {
readonly localize = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ export type DashboardEnvironment = {
const defaultEnvironment = (): DashboardEnvironment => ({
currentUrl: () => new URL(window.location.href),
replaceUrl: (url) => window.history.replaceState(window.history.state, "", url),
getStoredConnection: () => localStorage.getItem("umbraco-vercel-analytics:connection"),
setStoredConnection: (connection) => localStorage.setItem("umbraco-vercel-analytics:connection", connection),
getStoredConnection: () => localStorage.getItem("thebuilder-web-analytics:connection"),
setStoredConnection: (connection) => localStorage.setItem("thebuilder-web-analytics:connection", connection),
languages: navigator.languages,
});

Expand Down
37 changes: 37 additions & 0 deletions src/TheBuilder.WebAnalytics/Client/src/analytics/dashboard-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { WebAnalyticsService } from "../api/sdk.gen.js";
import type {
AnalyticsBreakdown,
AnalyticsConnectionsResponse,
AnalyticsDocumentRoute,
AnalyticsEventDetails,
AnalyticsEventProperty,
AnalyticsEventsReport,
AnalyticsFlagsReport,
AnalyticsSummary,
FlagsData,
} from "../api/types.gen.js";

type ApiResponse<T> = Promise<{ data?: T; error?: unknown; response: Response }>;
type FlagsOptions = { query?: FlagsData["query"]; signal?: AbortSignal };

export type DashboardApi = {
connections: (options?: Parameters<typeof WebAnalyticsService.connections<false>>[0]) => ApiResponse<AnalyticsConnectionsResponse>;
documentRoutes: (options: Parameters<typeof WebAnalyticsService.documentRoutes<false>>[0]) => ApiResponse<AnalyticsDocumentRoute[]>;
summary: (options?: Parameters<typeof WebAnalyticsService.summary<false>>[0]) => ApiResponse<AnalyticsSummary>;
events: (options?: Parameters<typeof WebAnalyticsService.events<false>>[0]) => ApiResponse<AnalyticsEventsReport>;
flags: (options?: FlagsOptions) => ApiResponse<AnalyticsFlagsReport>;
breakdown: (options: Parameters<typeof WebAnalyticsService.breakdown<false>>[0]) => ApiResponse<AnalyticsBreakdown>;
eventDetails: (options?: Parameters<typeof WebAnalyticsService.eventDetails<false>>[0]) => ApiResponse<AnalyticsEventDetails>;
eventPropertyValues: (options?: Parameters<typeof WebAnalyticsService.eventPropertyValues<false>>[0]) => ApiResponse<AnalyticsEventProperty>;
};

export const dashboardApi: DashboardApi = {
connections: (options) => WebAnalyticsService.connections(options),
documentRoutes: (options) => WebAnalyticsService.documentRoutes(options),
summary: (options) => WebAnalyticsService.summary(options),
events: (options) => WebAnalyticsService.events(options),
flags: (options) => WebAnalyticsService.flags(options),
breakdown: (options) => WebAnalyticsService.breakdown(options),
eventDetails: (options) => WebAnalyticsService.eventDetails(options),
eventPropertyValues: (options) => WebAnalyticsService.eventPropertyValues(options),
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

const sdk = vi.hoisted(() => ({ summary: vi.fn(), events: vi.fn(), flags: vi.fn(), breakdown: vi.fn() }));
vi.mock("../api/sdk.gen.js", () => ({ UmbracoVercelAnalyticsService: sdk }));
vi.mock("../api/sdk.gen.js", () => ({ WebAnalyticsService: sdk }));

import { loadDashboardReports, type DashboardReportQuery, type DashboardReportUpdate } from "./dashboard-report-loader.js";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
meta?: Record<string, unknown>;
};

export class UmbracoVercelAnalyticsService {
export class WebAnalyticsService {
public static connections<ThrowOnError extends boolean = false>(options?: Options<ConnectionsData, ThrowOnError>) {
return (options?.client ?? client).get<ConnectionsResponses, ConnectionsErrors, ThrowOnError>({
security: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const manifests: Array<UmbExtensionManifest> = [
{
name: "Umbraco Vercel Analytics Entrypoint",
alias: "Umbraco.VercelAnalytics.Entrypoint",
name: "Web Analytics Entrypoint",
alias: "TheBuilder.WebAnalytics.Entrypoint",
type: "backofficeEntryPoint",
js: () => import("./entrypoint.js"),
},
Expand Down
Loading