Skip to content

Add tutorial: live viewer profiles with Signals media tracking - #1893

Draft
jborlase-snowplow wants to merge 7 commits into
mainfrom
tutorial/t3-live-viewer-profiles
Draft

Add tutorial: live viewer profiles with Signals media tracking#1893
jborlase-snowplow wants to merge 7 commits into
mainfrom
tutorial/t3-live-viewer-profiles

Conversation

@jborlase-snowplow

@jborlase-snowplow jborlase-snowplow commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Note

The companion-repo merge blocker is resolved: the accelerator is now fully self-contained — every file is provided inline and the (formerly 404) repository link has been removed. One process point for reviewers: tutorial-requirements/README.md says all source code must be on GitHub; the product owner has waived this for an inline-complete accelerator — confirm, or amend the requirement text.

What changed?

New tutorial tutorials/signals-live-viewer-profiles/ (5 pages + 3 images): instrument video playback with the media tracking plugin, define session-level viewer attributes AND per-video audience metrics (custom video_id attribute key) with the Python SDK, and build a live-viewers dashboard on getBatchServiceAttributes.

Why?

Fills the media-tracking + real-time-profiles gap in Signals tutorial coverage.

Reviewer guidance

  • Verification was end-to-end with real playback across multiple concurrent browser sessions; all attribute values in prose and screenshots are genuine captures with exact-value assertions (e.g. two sessions on one video → active_viewers: 2; the dashboard screenshot matches a 3-session/2-video assertion round).
  • Notable constraints discovered and designed around, both undocumented and worth separate docs/product follow-ups: a service cannot reference attribute groups with different attribute keys (one service per key), and media_player 2-0-0 has no content IDlabel carries the stable video ID (alternative documented).
  • Deliberate choices: page 3 is SDK-only (no Console/SDK tabs — the Console editor path wasn't workable for these definitions); seconds_watched uses last not sum (the session entity already accumulates timePlayed); the push-state page folded into the conclusion (Node SDK can't subscribe to interventions).
  • Still unverified (browser/extension constraints, flagged in evidence): Console UI wording for the read-only review step, the two Snowplow Inspector steps, and the one-event-type-per-attribute Console claim.

🤖 Generated with Claude Code

A Signals counterpart to the Kafka live viewer profiles accelerator. The
Kafka version needs Snowbridge, Kafka, a Java consumer, DynamoDB, a
WebSocket back-end, and local Docker or Terraform; this one needs a
tracker, a Signals attribute group, and a thin dashboard.

Five pages: the comparison and prerequisites, the React video page with
the media plugin, the `viewer_profile` stream attribute group and its
service, and the dashboard built on the Node.js SDK's
`getBatchServiceAttributes`.

Verified against a live trial pipeline with real media events driven in a
browser. Notable behaviours documented in the pages:

* `seconds_watched` reads `last` of the media `session` entity's
  `timePlayed`, which already accumulates, so summing would multiply-count
  every ping. It is scoped to a media session and resets on reload, while
  the `ads_skipped` counter accumulates over the `domain_sessionid`.
* The media plugin's `MediaType` enum is declared in its type definitions
  but not exported at runtime, so the page passes the `'video'` string.
* `getBatchServiceAttributes` returns a columnar response with no
  identifier column, so rows map from the request's `identifiers` order.
* Publishing is not instant and a published attribute group version is
  immutable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
documentation 0a36cba Commit Preview URL

Branch Preview URL
Jul 31 2026, 10:43 AM

@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Style and writing review

Overall this is a well-written accelerator: terminology is accurate (entity, self-describing event, attribute group, real time vs real-time, back-end, lowercase npm, no v-prefixed versions), all 22 internal links resolve, both anchors (#the-changed-operator, #self-describing-events) exist, alt text is descriptive on all seven images, admonitions all use bracket titles, no marketing filler, and frontmatter matches the tutorials convention (position rather than sidebar_position). Findings below, roughly most to least important.

1. The companion repo link is a 404 (introduction.md:42)

The finished demo application is available in the signals-live-viewer-profiles repository on GitHub.

Confirming the blocker you flagged in the PR description: https://github.com/snowplow-industry-solutions/signals-live-viewer-profiles returns HTTP 404. tutorial-requirements/README.md requires that "all source code must be available on GitHub, and linked to in the accelerator", so this has to land before merge.

Other external URLs touched by this PR check out: https://snowplow.io/get-started/snowplow-free-trial -> 200, https://vite.dev/config/ -> 200, https://media.w3.org/2010/05/sintel/trailer.mp4 -> 200. https://durian.blender.org/ returns 403 to an automated fetch, which reads like bot protection rather than a dead link, but it's worth one manual click to confirm.

2. The introduction has no architecture section or diagram (introduction.md)

tutorial-requirements/example-accelerator/introduction.md prescribes an ## Architecture section that includes "a diagram that shows all the components, and how they fit into a Snowplow implementation", and the style guide adds "prefer Mermaid diagrams for architecture visuals". Seven existing accelerators follow this, including the direct sibling tutorials/kafka-live-viewer-profiles/introduction.md and the structurally closest page, tutorials/flink-live-shopper-features/introduction.md (which uses the same ## Architecture -> ## Solution accelerator code -> ## Prerequisites sequence you have, minus the diagram).

The Kafka comparison table does a lot of this work, but it lists components without showing the flow. Suggest renaming ## What you'll build to ## Architecture and adding a Mermaid diagram of video page -> Collector -> Enrich -> Signals streaming engine -> Profiles Store -> Node.js back-end -> dashboard. That leaves the page at four H2s.

3. Two H2 sections open straight into a list

The style guide requires that you "precede every heading and list with at least one paragraph of prose" and "introduce lists with a sentence ending in a colon".

  • build-the-dashboard.md:250## Troubleshooting is followed immediately by bullets. Add a lead-in, for example "Common failures and what causes them:"
  • conclusion.md:24## Learn more is followed immediately by bullets. Add something like "These pages go further on the topics this accelerator touches:"

4. Prerequisites items start lowercase (introduction.md:48-52)

  • a Snowplow pipeline with a Collector endpoint you can send events to, because Signals computes attributes from your live event stream

The style guide's list rules say to "capitalize the first word of each item", and tutorials/signals-quickstart/start.md:23-25 does this for its own prerequisites. Capitalize all five items ("A Snowplow pipeline with…", "Node.js 20.6 or later…", "Basic familiarity with React…").

5. Bold on non-UI text

CLAUDE.md is stricter than the style guide here and wins: "use bold only for UI elements (buttons, page titles)".

  • define-viewer-attributes.md:29 — "The most recent event name is therefore the viewer's state". Clearest case; use italics or drop the emphasis, since the sentence already carries the contrast.
  • conclusion.md:18-22 — the five bold lead-ins (**Push instead of polling**, **Engagement scoring**, **Concurrent viewers**, **Ad performance**, **Viewer-level profiles**) are labels, not UI. Lower priority: the style guide's "use bold or italic to highlight key phrases" arguably covers them, and the pattern appears elsewhere in the repo — but the existing tutorial instances (signals-quickstart/define-service.md:19-21) are genuine Console field labels, which these aren't.

All the **Signals** > **Attribute groups** and **Skip ad** uses are correct.

6. "now" as a temporal marker (define-viewer-attributes.md:15 and :168)

The style guide says to "stay in the present tense. Never use 'currently', 'now'…".

With media events flowing, you can now tell Signals what to compute from them.

Drop the word: "With media events flowing, you can tell Signals what to compute from them." Same at line 168, "Group them into a StreamAttributeGroup…". (introduction.md:10's "who's watching right now" is doing real work distinguishing live from historical, so I'd leave that one.)

7. "The quickest way" (define-viewer-attributes.md:259)

The quickest way to watch the attribute values update is the Snowplow Inspector Signals integration

"Quickly" and "best" are on the style guide's banned-marketing list and this superlative is the same family. Suggest: "You can watch the attribute values update with the Snowplow Inspector Signals integration".

8. Minor

  • build-the-dashboard.md has six H2 sections against CLAUDE.md's "limit pages to 3-5". Folding ## Add the Signals credentials into ## Create the back-end would bring it to five without restructuring anything.
  • build-the-dashboard.md:127 — the code comment "register this viewer's session with the dashboard backend" uses the unhyphenated spelling, while the surrounding prose consistently uses "back-end" (style guide: "always hyphenate: back-end"). The same word appears in the console.log string, but that one is program output and fine to leave; the comment is prose the reader reads.
  • set-up-media-tracking.md — in the VideoPage.jsx bullet list, "onPlay, onPause, onEnded, onSeeking, and onSeeked track the corresponding media events." is a single sentence ending in a period, where the style guide says no terminal period unless the item has multiple sentences. The neighboring items are multi-sentence so the period reads consistent; noting it only for completeness.

Nothing here touches technical correctness, which is out of scope for this pass.

Aggregate viewer metrics at the video level as well as the session level.
A custom attribute key reads the media player's label, which the video page
sets to the video's ID, and a second attribute group counts active viewers,
distinct viewers, and skipped ads across every session watching a title.

A service can only reference attribute groups that share an attribute key,
so the two groups get a service each and the back-end makes one batch call
per service. The Python definitions are now a single script that publishes
the key, both groups, and both services together, passing the group objects
straight to Service rather than hand-written name and version dicts.

Also, from review:
- Drop the Kafka comparison table for an Architecture section with a Mermaid
  diagram, and drop the companion repository link: every file is inline
- Define the attribute group with the Python SDK directly, instead of
  starting in Console and cancelling, and remove the screenshots of the
  flow that is no longer instructed
- Move the session registration step ahead of the back-end section, and
  fold the credentials into it
- Style fixes: capitalized prerequisites, bold reserved for UI elements,
  lead-ins before bullet lists, and neutral phrasing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recreating an attribute group at the same name and version resumes the
existing counters rather than starting from zero, which is worth knowing
before you test the counts repeatedly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Pushed 3f91b783 + 5a10d033 applying product-owner feedback and the style review: accelerator is now self-contained (repo link removed — see updated PR description), Kafka comparison replaced by a Mermaid architecture diagram, Console-then-cancel flow cut in favor of SDK-only definitions, per-video audience metrics added on a custom video_id attribute key (verified with concurrent sessions), register-session step moved ahead of the back-end, service wiring corrected (one service per attribute key — the cross-key 422 is undocumented), and all style-review items applied. Full re-verification: every code block re-run in a fresh scaffold and diffed against the executed files; three passing builds; dev org left clean.

🤖 Generated with Claude Code

Offer a single paste-able prompt that produces the same Signals
configuration as the Python SDK path: the custom video_id attribute key,
both attribute groups, and one service per attribute key. The SDK path
stays as written, since the dashboard reads the names it creates.

The prompt works with any MCP-capable assistant connected to the Snowplow
MCP server, or with the Snowplow Assistant in Console. A note explains
that new definitions are saved as drafts, so the reader reviews the
configuration before publishing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Pushed 9d1ec7c6: an AI-assisted definition path (product-owner suggestion) — a paste-able prompt that produces the same five Signals resources via the Snowplow MCP server or the Console-embedded Snowplow Assistant, as an alternative alongside the Python SDK path.

Equivalence was verified for real, two ways: the MCP-built and SDK-built configurations' canonical registry JSON diff to identical (sole exception: owner, which MCP creates cannot set — flagged as an MCP gap), and both routes returned identical attribute values from one live event stream. Every MCP create lands as a draft, so the review-before-publish safety net is real and the section says so.

Reviewer notes: the Console Assistant variant is mentioned, not walked (no UI claims made); no cross-link to the in-flight signals-mcp tutorial yet — add one in the new section and the conclusion when that PR merges; page 3 now sits at 7 H2s against the 3–5 guideline (declared, restructure if you prefer).

🤖 Generated with Claude Code

Published tutorials shouldn't narrate the Console and API errors hit
while verifying them, or how long anything took in those runs.

* Page 2: state the media type strings as an instruction instead of an
  import failure, without the package version.
* Page 3: keep one-service-per-attribute-key as the design statement it
  is, minus the 422 text; state group immutability without the 400 text;
  drop the delete-and-republish aside about stored values, which nothing
  else on the pages relies on; drop the publish-wait timing while keeping
  the wait itself, since values are empty until the definitions land.
* Page 4: turn the registration note into the ordering rule the reader
  needs, and the troubleshooting list into positive checks on service
  names, credentials, and event ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Pushed bb8f9bcc — editorial sweep per product direction: verification-run artifacts removed from reader-facing pages. Error strings and lifecycle-quirk notes are gone; the underlying constraints remain as positive instructions (one service per attribute key, versions are immutable — increment to change), and measured wait durations are now neutral phrasing. Reader-code error handling (the dashboard's own error states) is untouched.

🤖 Generated with Claude Code

@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Claude (@claude) review please

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Writing quality review

Comments below are limited to things worth changing; the voice, second-person framing, and terminology are otherwise in good shape.

Page structure

  1. define-viewer-attributes.md has seven H2 sections (CLAUDE.md: limit pages to 3-5) and runs to ~330 lines. Splitting "Define the attributes with an AI assistant" and "Verify the attributes while a video plays" into a separate page would bring both pages inside the guidance and give the verification step its own place in the sidebar.

  2. Five :::note admonitions on that same page ("Why this group is defined in code", "The media player entity has no ID field", "One service per attribute key", "Republishing", "Review before you publish"). The style guide asks for admonitions to be used sparingly. "One service per attribute key" and "Republishing" both read fine as plain prose in the paragraphs they sit next to.

  3. conclusion.md doesn't use the conventional "Next steps" heading. tutorial-requirements/README.md and every other Signals accelerator (signals-quickstart, signals-interventions, kafka-live-viewer-profiles) end with ## Next steps. Rename "Learn more", or merge both H2s under it.

  4. introduction.md is missing a "Snowplow implementation" subsection. The accelerator template asks for one where the demo app contains Snowplow tracking: which events and entities it generates, and why each Snowplow component is used. At the moment the reader doesn't learn what's tracked until page 2. A short H3 under Architecture would cover it.

Headings

  1. ## How viewer actions map to session attributes is descriptive rather than imperative. Suggest "Map viewer actions to session attributes".

Wording and formatting

  1. build-the-dashboard.md: "the video's Watching count goes to 2" — spell out single-digit numbers: "goes to two". (The digits in the image alt text are fine, since they describe what's on screen.)

  2. define-viewer-attributes.md: The session profiles answer "what is this viewer doing?". A dashboard for the whole catalog also needs the opposite view: "how many people are watching this title, across every session?". The period after each closing quote is redundant after the question mark, and the quoted sentences should start with a capital.

  3. The attribute table's Aggregation column uses "Last" and "Counter", but the prose and the Python both use the SDK values. Set them as last and counter — the style guide keeps code in its own case in table cells.

  4. introduction.md: "someone is watching a video with Snowplow media tracking" — the house convention is "the user" (or "a viewer" here) for the reader's end users.

  5. introduction.md: "served from the Signals Profiles Store" — the term used throughout /docs/signals/ is just "the Profiles Store".

  6. introduction.md prerequisites: "Snowplow events and entities" links only to the events page. Either add a second link for entities, or make the link text "Snowplow events".

  7. The AI-assistant prompt block contains "Our video page puts the video's ID in that label". Everything else in these pages is second person; "The video page puts the video's ID in that label" keeps the voice consistent.

  8. define-viewer-attributes.md links [Snowplow Console](https://console.snowplowanalytics.com) three times. Link it on first mention and use plain "Console" afterwards, as the page already does elsewhere.

  9. The frontmatter description values for build-the-dashboard.md and define-viewer-attributes.md are single 35-40 word sentences with several stacked clauses. The guidance allows one or two sentences — splitting each into two would make them readable as search snippets.

  10. define-viewer-attributes.md: "Publishing isn't instant: give the definitions a moment to reach the streaming engine." If you measured a rough figure during verification, state it. "A moment" leaves the reader unsure whether to wait 10 seconds or five minutes before deciding something is broken.

  11. build-the-dashboard.md says "start the back-end before you open the video page" in the first section, then "Start the back-end before the video page, so that the registration call has somewhere to go" again under "Run it end to end". One of the two can go.

  12. List punctuation is inconsistent with the "no period unless the item has multiple sentences" rule. Single-sentence items ending in a period include the onPlay, onPause bullet in set-up-media-tracking.md, the "Engagement scoring" and "Ad performance" bullets in conclusion.md, and the troubleshooting bullets in build-the-dashboard.md. The prerequisites list and the event-verification list already follow the rule.

meta.json

  1. "technologies": ["React"] — the accelerator also requires Node.js/Express for the back-end and the Python SDK for the definitions. Adding those makes it findable by readers filtering on either, in line with python-tracking-and-signals listing "Python".

Product-owner directives:

- Page 1 no longer frames the requirement as a "full pipeline" that rules out
  Snowplow Micro. It now asks for a Snowplow account and pipeline, keeping the
  free-trial pointer.
- Removes the media-type-strings, "Why this group is defined in code", and
  "The media player entity has no ID field" panels. The label-to-video-ID fact
  survives as one prose sentence, since the custom attribute key depends on it.
- Moves the AI-assistant section above the Python on page 3, and drops the two
  rationale paragraphs after the prompt.

Review round:

- Splits the attribute verification steps into their own page, renumbering the
  positions after it, and converts the last two page-3 notes to prose.
- Adds a Snowplow implementation summary to the architecture section, listing
  the events and entities the demo generates.
- Renames two headings, links the Console URL on first mention only, splits the
  over-length frontmatter descriptions, and drops a duplicated instruction to
  start the back-end first.
- Adds Node.js and Python to the tutorial's technologies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Pushed b5d884d6 — product-owner edits plus the latest review round:

  • Prerequisites simplified to a Snowplow account and pipeline (no Micro mention); three info panels removed (media type strings, why-in-code, media-player ID) with the load-bearing label-carries-the-video-ID fact kept as one prose sentence
  • The AI-assistant definition route now leads the page (ahead of the SDK code), with the rationale paragraphs cut
  • Review round applied: verification split into its own page (positions renumbered), ## Next steps conclusion, new Snowplow implementation subsection in the architecture, imperative headings, and the wording/formatting items. The measured-wait suggestion was deliberately skipped (conflicts with the no-internal-timings direction)
  • Two notes for reviewers: define-viewer-attributes sits at 6 H2s (reaching 5 would need merging "Connect to Signals" into the definitions section — not done unbidden), and the removed rationale paragraphs took the assistant-created-resources-record-no-owner caveat with them (one line to reinstate if wanted)

🤖 Generated with Claude Code

Page 3 mixed the two ways of creating the definitions into one linear
flow: the AI-assistant section sat between the shared design and the
Python, and ended by telling the reader to skip the rest of the page.

Restructure it into two parallel route sections. The shared design now
comes first in full, including the per-video conceptual paragraphs that
were interleaved with the Python, so that the AI route's review step has
something to check against. Then `## Define using the AI assistant` and
`## Define using the Python SDK`, with the SDK flow's four steps nested
as H3s and each route publishing on its own. The lead-in link points at
the renamed AI section, and the page drops from 6 H2s to 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jborlase-snowplow

Copy link
Copy Markdown
Contributor Author

Pushed 0a36cba9 — the definitions page now presents two parallel, self-contained routes: Define using the AI assistant or Define using the Python SDK, with the SDK flow's steps nested as H3s beneath its route heading. The per-video design concepts moved into the shared sections so both routes reference the same page content. Page is now 4 H2s (resolves the earlier H2-count flag); each route carries its own publish step.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants