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
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> ✅ **Verified** — used in production at **buymywishlist**. This plugin has been validated end-to-end in a merged main-branch wfctl.yaml of an active GoCodeAlone project.

`workflow-plugin-analytics` injects analytics and tag-manager snippets into rendered HTML assets from `wfctl`.
`workflow-plugin-analytics` injects analytics and tag-manager snippets into rendered HTML assets from `wfctl`. It can also provision Google Analytics 4 web streams and Google Tag Manager web containers programmatically.

The first supported provider is Google Analytics through the Google tag (`gtag.js`). The plugin also includes Google Tag Manager snippet injection so apps can switch to a container-managed setup later.

Expand Down Expand Up @@ -53,6 +53,86 @@ circuits to `skipped: true, reason: "empty tag id"`. When the tenant sets
`anonymize_ip: true`, the GA4 `config(...)` call emits `{'anonymize_ip':
true}`.

## Google provisioning in wfctl YAML

The primary provisioning surface is Workflow/wfctl YAML. Put the desired GA/GTM resources in the same config that owns deploy and secret wiring, usually `deploy.yaml` or `infra.yaml`.

```yaml
secretStores:
github-actions:
provider: github
config:
repo: GoCodeAlone/example-site
token_env: RELEASES_TOKEN

secrets:
defaultStore: github-actions
entries:
- name: GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON
description: Service-account JSON with GA Admin + GTM access.

modules:
- name: google-analytics
type: analytics.google_provider
config:
credentials_json: ${GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON}
# Set allow_adc: true only for runners where Application Default
# Credentials are the intended deploy identity.
allow_adc: false
analytics_account: accounts/123456789
tag_manager_account: accounts/987654321

pipelines:
apply:
steps:
- name: ensure_ga4
type: step.analytics_google_ga4_ensure
config:
provider: google-analytics
property_name: example.com
stream_name: example.com
default_uri: https://example.com
dry_run: true

- name: ensure_gtm
type: step.analytics_google_gtm_ensure
config:
provider: google-analytics
container_name: example.com
domains: [example.com, www.example.com]
workspace_name: workflow
measurement_id: ${ensure_ga4.measurement_id}
dry_run: true
```

`wfctl infra apply -c deploy.yaml` can run the `pipelines.apply` path for config-driven resources. Keep `dry_run: true` until Google API access and account permissions are in place.

The CLI is still useful for smoke checks and one-off operator probes. Dry-run GA4 provisioning:

```sh
wfctl analytics google ga4 ensure \
--account accounts/123456789 \
--property-name example.com \
--stream-name example.com \
--default-uri https://example.com \
--credentials-json-env GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON \
--dry-run
```

Dry-run GTM provisioning:

```sh
wfctl analytics google gtm ensure \
--account accounts/987654321 \
--container-name example.com \
--domain example.com \
--measurement-id G-XXXXXXXXXX \
--credentials-json-env GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON \
--dry-run
```

Live apply requires Google API access and credentials. Audit events for state-mutating and dry-run ensure commands are appended to `${XDG_STATE_HOME:-$HOME/.local/state}/wfctl/plugins/analytics/google-audit.jsonl` unless `audit_path` overrides it.

## Providers

- `google-analytics`: injects the Google tag into `<head>`.
Expand All @@ -64,6 +144,7 @@ true}`.
- Managed blocks are replaced idempotently.
- Existing unmanaged snippets for the same provider ID are detected and left untouched to avoid double injection.
- The command can process one file with `--html` or all `.html` files below a directory with `--dir`.
- Google credentials are read from env/file/ADC and are never written to audit logs.

## References

Expand Down
130 changes: 130 additions & 0 deletions docs/gocodealone-multisite-google-analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# gocodealone-multisite Google Analytics Provisioning

This runbook wires `gocodealone-multisite` to programmatically provision GA4 web streams and optional GTM web containers through `workflow-plugin-analytics`.

The source of truth should be `deploy.yaml` / `deploy.prereq.yaml`, not ad hoc CLI state. CLI commands below are smoke probes for the same operations expressed in YAML.

## Prerequisites

- Enable Google Analytics Admin API and Tag Manager API for the credential project.
- Create or choose a service account / ADC principal.
- Grant it access to the target Google Analytics account(s) and Google Tag Manager account(s).
- Store credentials as a deploy secret, for example `GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON`.
- Choose umbrella accounts:
- GA: `accounts/<analytics-account-id>`
- GTM: `accounts/<tag-manager-account-id>`

Stop here until the operator has provisioned API access.

## wfctl plugin pin

Add the analytics plugin to `gocodealone-multisite/wfctl.yaml` after release:

```yaml
plugins:
- name: workflow-plugin-analytics
version: vNEXT
source: github.com/GoCodeAlone/workflow-plugin-analytics
```

## Secret entries

Add this entry to `deploy.prereq.yaml` and `deploy.yaml` secret lists:

```yaml
- name: GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON
description: Service-account JSON with Analytics Admin and Tag Manager access.
```

## deploy.yaml desired state

Add the provider module and `pipelines.apply` steps to `deploy.yaml` so analytics resources are reconciled with the rest of the deploy intent:

```yaml
modules:
- name: google-analytics
type: analytics.google_provider
config:
credentials_json: ${GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON}
# Keep false for GitHub-secret driven deploys; set true only when
# the runner's ADC identity is intentionally the deploy principal.
allow_adc: false
analytics_account: accounts/123456789
tag_manager_account: accounts/987654321

pipelines:
apply:
steps:
- name: ensure_gocodealone_ga4
type: step.analytics_google_ga4_ensure
config:
provider: google-analytics
property_name: gocodealone.tech
stream_name: gocodealone.tech
default_uri: https://gocodealone.tech
dry_run: true

- name: ensure_gocodealone_gtm
type: step.analytics_google_gtm_ensure
config:
provider: google-analytics
container_name: gocodealone.tech
domains: [gocodealone.tech, www.gocodealone.tech]
workspace_name: workflow
measurement_id: ${ensure_gocodealone_ga4.measurement_id}
dry_run: true
```

Keep `dry_run: true` until the operator grants API access. After reviewing dry-run output, switch the specific site step to `dry_run: false` and run:

```sh
wfctl infra apply -c deploy.yaml --wait
```

## CLI smoke probe

Run the equivalent GA4 dry-run directly when validating credentials or debugging:

```sh
wfctl analytics google ga4 ensure \
--account accounts/123456789 \
--property-name gocodealone.tech \
--stream-name gocodealone.tech \
--default-uri https://gocodealone.tech \
--credentials-json-env GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON \
--dry-run
```

Optional GTM dry-run:

```sh
wfctl analytics google gtm ensure \
--account accounts/987654321 \
--container-name gocodealone.tech \
--domain gocodealone.tech \
--domain www.gocodealone.tech \
--measurement-id G-XXXXXXXXXX \
--credentials-json-env GOOGLE_ANALYTICS_ADMIN_CREDENTIALS_JSON \
--dry-run
```

The output is JSON and can be copied into deployment logs or a future Workflow pipeline step. Live apply removes `--dry-run`, but only after the dry-run output is reviewed.

## Content repo update

After live GA4 apply returns a measurement ID, set it in the content repo `multisite.yaml`:

```yaml
analytics:
google:
measurement_id: G-XXXXXXXXXX
anonymize_ip: true
```

`gocodealone-multisite` then passes that ID into `step.analytics_inject_html`; tenants without an ID keep analytics disabled.

## Rollback

- Remove or blank `analytics.google.measurement_id` in the content repo and redeploy content.
- Run `wfctl analytics inject` with an empty tag ID when mutating static assets to remove managed snippets.
- Do not delete GA/GTM resources automatically; deletion can destroy useful history.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Google Analytics Provisioning Adversarial Review

## Design Phase

**Status:** PASS after author revision.

**Findings**

- Important: audit JSONL was a global guidance requirement in the design but absent from the implementation plan. Fixed by adding `internal/google_audit.go` and tests to Task 1.
- Important: the live-apply blocker could be bypassed accidentally because the verification section only tested dry-run. Fixed by adding an explicit no-credentials live command check to Task 6.
- Minor: GTM publish/versioning is deferred. Acceptable because the design returns container/workspace/config IDs and documents publish as out of scope.

**Bug-class scan transcript**

| Class | Result | Note |
|---|---|---|
| Project-guidance conflicts | Fixed | Audit trail requirement now maps to a task. |
| Assumptions under attack | Clean | Credential/account access assumptions are explicit and become the deploy blocker. |
| Repo-precedent conflicts | Clean | Extending the existing analytics plugin follows repo guidance and current plugin precedent. |
| YAGNI violations | Clean | Publishing/deletion are explicitly out of scope. |
| Missing failure modes | Fixed | No-credential live apply is now a required verification. |
| Security/privacy | Clean | Secrets are redacted; no user traffic/PII is processed. |
| Infrastructure impact | Clean | Google SaaS resource creation and no-delete rollback are stated. |
| Multi-component validation | Clean | CLI, step, fake SDK, and consumer dry-run are covered. |
| Rollback | Clean | Rollback avoids deleting analytics history. |
| Simpler alternative | Clean | Manual spreadsheet/env IDs considered and rejected. |
| User-intent drift | Clean | Design covers GA/GTM programmatic provisioning and multisite deploy steps. |

## Plan Phase

**Status:** PASS after author revision.

**Findings**

- Important: audit file promised by design was not task-owned. Fixed in Task 1.
- Important: deployment blocker was described in prose but not verified. Fixed in Task 6.
- Minor: one PR is large but reviewable because it stays within one public plugin and docs; splitting GA and GTM would create cross-PR dependency on shared provider/audit code.

**Verdict reasoning:** The revised plan covers the design without adding unrelated scope. Verification matches the change classes: unit tests for reconcilers, CLI smoke for command surface, build/test for plugin load, and an explicit blocked-live-apply check before operator credentials exist.
Loading
Loading