# ga4

The ga4 plugin reports Google Analytics 4 figures for every account and property one Google login can see. Per property it sends users active right now, users, sessions and page views for today, yesterday and the last 7 days, and 30 days of daily figures. It answers "is traffic still arriving", and it pages when Google stops answering.

## Set it up

Google Analytics has no service-account path for this plugin. It runs on a user refresh token with the `analytics.readonly` scope, minted once by hand.

1. In Google Cloud, create an OAuth client of type Desktop app and download its JSON.
2. Enable the Google Analytics Admin API and the Google Analytics Data API in that project.
3. Set the OAuth consent screen to In production, or Internal. A client left in Testing hands out refresh tokens that expire after 7 days.
4. From the `client` directory, run the token helper.

   ```bash
   node etc/google-auth.mjs --source=ga4 <client_secret.json> [ssh-host] [remote-config-path]
   ```

   It opens Google's consent page, catches the redirect on a loopback port and trades the code for a refresh token. Before it saves anything, it checks that the token can see at least one Analytics account. With no `ssh-host` it writes the `ga4` block into `./client.config.json`. With one, it writes into that host's config over ssh and sends the secrets on stdin. Set `BROWSER_APP` to pick the browser that holds the right Google login.
5. Add `ga4` to the agent's `plugins` array and restart the agent.

The Google account you consent with must have access to the Analytics properties.

```json
{
  "plugins": ["ga4"],
  "ga4": {
    "name": "Google Analytics",
    "uniqueId": "ga4",
    "refreshInterval": 900000,
    "clientId": "<CLIENT_ID>.apps.googleusercontent.com",
    "clientSecret": "<CLIENT_SECRET>",
    "refreshToken": "<REFRESH_TOKEN>",
    "accounts": ["<GA_ACCOUNT_ID>"]
  }
}
```

| Option | Default | What it does |
|---|---|---|
| `clientId` | required | OAuth client id. |
| `clientSecret` | required | OAuth client secret. |
| `refreshToken` | required | Refresh token from `google-auth.mjs`. |
| `accounts` | every account | GA account ids to collect, as strings. Accounts outside the list are never queried. |
| `refreshInterval` | `900000` | Milliseconds between reports. |
| `timeout` | `20000` | Milliseconds per HTTP request. |

Set `accounts` unless the login sees only this customer's accounts. A Google login often sees accounts that belong to other customers, and everything collected lands in the view of the system this agent reports to.

`client.config.json.example` has no `ga4` block. `google-auth.mjs` creates one.

## What it reports

One report per interval, sent immediately on start.

- `name`, always `Google Analytics`.
- `properties`, the number of properties reported.
- `accounts[]`, each with `id`, `name` and `properties[]`.
- Per property, `id`, `name`, `asOf`, which is today's date in the property's time zone, and `activeNow`, the users active in the last 30 minutes. `activeNow` is null when the realtime call fails.
- Per property, `today`, `yesterday` and `last7`, each with `users`, `sessions` and `views`. The 7-day users figure comes from GA itself, so one person on three days counts once.
- Per property, `daily` with a `start` date and 30-entry `users`, `sessions` and `views` arrays. Days with no traffic are filled with 0.
- A property that fails carries `error` in place of figures. The others still report.
- When the whole poll fails, the report is `{ error }`, plus `authExpired: true` if Google answered `invalid_grant`.

Only ordinary GA4 properties are listed. The plugin filters out other property types.

The server does not write a history row per report. The type is in `SELF_HISTORIED`, because each report already carries 30 days.

## When it alerts

| Condition | Severity | Reason text |
|---|---|---|
| `authExpired` | error | `Google Analytics: authorization expired, mint a new refresh token` |
| `error` | error | `Google Analytics: <error>` |
| `clientId`, `clientSecret` or `refreshToken` missing | error | `Google Analytics: ga4 plugin needs clientId, clientSecret and refreshToken` |
| One or more properties carry `error` | warning | `Google Analytics: <n> properties failed to report (<first name>: <error>)` |

A new problem does not notify on the first bad report. The server waits for a second consecutive bad report, which is 15 minutes later at the default interval. A report that comes back clean resolves the alert.

There is no traffic threshold. A property that drops to zero users does not alert.

## On the dashboard

The card is titled `Google Analytics`.

| Reading | Source |
|---|---|
| PROPERTIES | Number of properties |
| ACTIVE NOW | Sum of `activeNow` across properties |
| USERS TODAY | Sum of `today.users` |
| SESSIONS 7D | Sum of `last7.sessions` |

Below that, one row per property with today's users, or `error` in amber. While the report carries a top-level `error`, the figures blank out rather than showing zeroes. The card links to Google Analytics.

The card goes OFFLINE after 1800 seconds without a report, which is two missed polls at the default interval.

## Gotchas

- A failed realtime call is silent. `activeNow` becomes null, and ACTIVE NOW counts it as 0.
- Changing `accounts` is a privacy decision, not a display one. Hiding properties and nicknames live in the portal.
- Refresh tokens from a consent screen in Testing die after 7 days, and the card turns red with `authorization expired`. Run `google-auth.mjs` again with the app set to In production.
- If Google returns no refresh token, remove the app's access at myaccount.google.com/permissions and run the helper again.
