Skip to content

refactor(headless): inject answer generation analytics client to listener middleware - #8045

Draft
mmitiche wants to merge 2 commits into
mainfrom
feat/inject-generated-answer-generation-analytics-client
Draft

refactor(headless): inject answer generation analytics client to listener middleware#8045
mmitiche wants to merge 2 commits into
mainfrom
feat/inject-generated-answer-generation-analytics-client

Conversation

@mmitiche

@mmitiche mmitiche commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

The problem

Most Headless controllers ship in two flavors for two use cases: search and insight. Take the pager: both versions consume the same core pager controller, and the only real difference is the analytics client they use — one dispatches search analytics actions, the other insight analytics actions. Each version is then exported under the same name but from a different bundle, so importing from @coveo/headless gives you the search controller and importing from the insight bundle gives you the insight one. Clean, and it works well across basically every controller.

The GeneratedAnswerWithFollowUps controller is the odd one out. This controller is not the thing that generates the answer. The answer is generated by a listener middleware that reacts to search queries and does the streaming work. So even though the controller could own a use-case-specific analytics client, it isn't the one actually producing the answer — which makes it tricky to log the right analytics for the right use case. In practice the generation lifecycle events (logGeneratedAnswerStreamEnd, logGeneratedAnswerResponseLinked) were just hardcoded to the search analytics actions inside the streaming strategies, so there was no way for insight to ever log them correctly.

The fix

Instead of hardcoding, we introduce an AnswerGenerationAnalyticsClient and inject it. The key insight is where it gets injected: as an extra argument at engine build time (ThunkExtraArguments).

That's what makes this scale to both use cases in the future. The generation middleware is registered once for every engine, so it can't know on its own whether it's serving search or insight. But each engine builder already knows exactly which use case it is:

  • buildSearchEngine injects the search generation analytics client.
  • Later, buildInsightEngine can inject the insight one.

The middleware just reads whatever client the engine put in ThunkExtraArguments and hands it to the streaming strategy. So the same global generation machinery ends up logging search analytics inside a search engine and insight analytics inside an insight engine — no branching, no use-case detection, just the client the engine was built with. The follow-ups controller gets the same client through its constructor (for retry() and follow-up streaming), fed from the same per-use-case constant.

Scope

  • Search only for now. Insight is intentionally left out; the interface return type is already widened (CustomAction | InsightAction) so dropping in an insight client later is a one-liner in the insight engine + bundle.
  • Search behavior is unchanged — it's the same two action creators, now passed by reference instead of imported directly.

Replace the hardcoded search analytics imports in the answer/follow-up streaming strategies with an injected AnswerGenerationAnalyticsClient. The client is threaded to the global generation listener middleware via ThunkExtraArguments (search engine injects the search implementation, with a search fallback) and to the follow-ups controller via a constructor param. Search behavior is unchanged; insight can later supply its own implementation.
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2a28fee

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svcsnykcoveo

svcsnykcoveo commented Jul 22, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@chromatic-com

chromatic-com Bot commented Jul 22, 2026

Copy link
Copy Markdown

Tip

All tests passed and all changes approved!

🟢 UI Tests: 456 tests unchanged
🟢 UI Review: 465 stories published -- no changes
Storybook icon Storybook Publish: 465 stories published

@pkg-pr-new

pkg-pr-new Bot commented Jul 22, 2026

Copy link
Copy Markdown
@coveo/atomic

npm i https://pkg.pr.new/@coveo/atomic@8045

@coveo/atomic-hosted-page

npm i https://pkg.pr.new/@coveo/atomic-hosted-page@8045

@coveo/atomic-legacy

npm i https://pkg.pr.new/@coveo/atomic-legacy@8045

@coveo/atomic-react

npm i https://pkg.pr.new/@coveo/atomic-react@8045

@coveo/auth

npm i https://pkg.pr.new/@coveo/auth@8045

@coveo/bueno

npm i https://pkg.pr.new/@coveo/bueno@8045

@coveo/create-atomic

npm i https://pkg.pr.new/@coveo/create-atomic@8045

@coveo/create-atomic-component

npm i https://pkg.pr.new/@coveo/create-atomic-component@8045

@coveo/create-atomic-component-project

npm i https://pkg.pr.new/@coveo/create-atomic-component-project@8045

@coveo/create-atomic-result-component

npm i https://pkg.pr.new/@coveo/create-atomic-result-component@8045

@coveo/create-atomic-rollup-plugin

npm i https://pkg.pr.new/@coveo/create-atomic-rollup-plugin@8045

@coveo/create-ui

npm i https://pkg.pr.new/@coveo/create-ui@8045

@coveo/headless

npm i https://pkg.pr.new/@coveo/headless@8045

@coveo/headless-react

npm i https://pkg.pr.new/@coveo/headless-react@8045

@coveo/relay

npm i https://pkg.pr.new/@coveo/relay@8045

@coveo/shopify

npm i https://pkg.pr.new/@coveo/shopify@8045

commit: 2a28fee

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🔗 Scratch Orgs ready to test this PR:

…lyticsClient

Instead of passing a separate generation analytics client to buildGeneratedAnswerWithFollowUps, reuse the controller's existing analyticsClient by having GeneratedAnswerAnalyticsClient extend AnswerGenerationAnalyticsClient. The search and insight client objects already exposed logGeneratedAnswerStreamEnd; logGeneratedAnswerResponseLinked is added to both. Drops the extra controller parameter and the standalone searchAnswerGenerationAnalyticsClient constant; the engine now injects generatedAnswerAnalyticsClient.
@mmitiche mmitiche changed the title refactor(headless): inject answer generation analytics client refactor(headless): inject answer generation analytics client to listener middleware Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants