Skip to content

Site: add configurable right-gutter CTA templates#3602

Merged
reakaleek merged 11 commits into
mainfrom
thundering-motor
Jul 1, 2026
Merged

Site: add configurable right-gutter CTA templates#3602
reakaleek merged 11 commits into
mainfrom
thundering-motor

Conversation

@reakaleek

Copy link
Copy Markdown
Member

Why

The right-gutter CTA card (the "Get started free" trial widget) was hardcoded sitewide, with no way for a docset or page to show a different offer, and no click/impression tracking to measure its effectiveness.

What

Docsets can now define named CTA templates (button label/url, up to 3 benefit bullets) under a cta map in docset.yml, and individual pages select one via the cta frontmatter field. Pages that don't opt in keep the existing built-in trial card. An unknown cta name falls back to the default and emits a build warning with a "did you mean" hint. CTA clicks and impressions are now tracked via OpenTelemetry (cta_clicked / cta_viewed) so click-through rate can be compared across templates and placements.

Codex builds are explicitly excluded from rendering the CTA card, matching their existing exclusion from other assembler-only sidebar chrome.

Docs for the new docset.yml key and frontmatter field are added under docs/configure/content-set/cta.md, linked from navigation.md and frontmatter.md.

Test plan

  • dotnet build on Elastic.Markdown and Elastic.Codex succeeds
  • docs-builder build --path docs succeeds with 0 errors/warnings on the new docs page and links
  • Manually verify a docset with a custom cta: template renders the correct label/url/benefits in the sidebar, and that an unknown name falls back to trial with a warning
  • Manually verify Codex-built pages never render the sidebar CTA card, even with a cta: frontmatter value set

Co-Authored-By: Claude Sonnet 4.6 (1M context) noreply@anthropic.com

reakaleek and others added 2 commits July 1, 2026 12:24
Docsets can now define named CTA templates under docset.yml's `cta`
map (label, url, benefits) and select one per-page via frontmatter
`cta: <name>`, instead of the previously hardcoded trial card. Clicks
and impressions are tracked via OpenTelemetry.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Documents the docset.yml `cta` map and the page-level `cta`
frontmatter field on a new content-set page, linked from
navigation.md and frontmatter.md.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek requested review from a team as code owners July 1, 2026 10:31
@reakaleek reakaleek requested a review from Mpdreamz July 1, 2026 10:31
Defines a 'docs-builder' CTA template in docs/_docset.yml and selects
it on the CTA docs page itself, so the feature has a working example
to preview and test against.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 10:34 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d20c7696-1648-4b66-b2d8-2ac952eaf4ba

📥 Commits

Reviewing files that changed from the base of the PR and between b2c9064 and ec9ce82.

📒 Files selected for processing (2)
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs

📝 Walkthrough

Walkthrough

Changes

This PR adds configurable right-gutter CTAs. CTA templates are defined in docset.yml, selected per page through frontmatter, resolved during markdown rendering, and passed into the page and layout view models for Razor rendering. The sidebar CTA replaces the prior hardcoded trial card. The site also adds CTA impression and click telemetry with new semantic attributes. Documentation for configuration, frontmatter, and navigation is updated.

Related PRs: #3167

Suggested labels: feature, documentation

Suggested reviewers: None identified

Poem
A CTA took shape in config and page,
Then flowed through views and telemetry’s stage.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: configurable right-gutter CTA templates.
Description check ✅ Passed The description directly matches the change set, covering CTA templates, tracking, docs, and Codex exclusions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch thundering-motor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs`:
- Around line 305-320: The CTA validation in ConfigurationFile should reject
unsafe values for definition.Button.Url before storing them in _ctas, not just
check for non-empty text. Add URI validation in the CTA parsing block for Cta
definitions so only the intended schemes/forms are accepted and anything like
javascript: or data: triggers context.EmitError; keep the check рядом with the
existing label/url presence validation and the Cta construction.

In `@src/Elastic.Markdown/Layout/_TableOfContents.cshtml`:
- Around line 75-80: The sidebar trial CTA condition in _TableOfContents should
also respect white-label mode, not just BuildType and Cta.Name. Update the
rendering guard around the sidebar-trial-card anchor so the default trial card
is skipped when Model.Branding is present, matching the rest of the sidebar’s
Elastic-chrome suppression. Use the existing Model.BuildType, Model.Cta, and
Model.Branding checks in this Razor block to keep the CTA hidden for branded
builds unless a custom CTA is explicitly provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ab011318-5c00-407f-8754-25254108acc8

📥 Commits

Reviewing files that changed from the base of the PR and between 09a29bb and 37990da.

📒 Files selected for processing (16)
  • docs/_docset.yml
  • docs/configure/content-set/cta.md
  • docs/configure/content-set/index.md
  • docs/configure/content-set/navigation.md
  • docs/syntax/frontmatter.md
  • src/Elastic.Codex/Page/Index.cshtml
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs
  • src/Elastic.Documentation.Site/Assets/main.ts
  • src/Elastic.Documentation.Site/Assets/telemetry/semconv.ts
  • src/Elastic.Markdown/HtmlWriter.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Markdown/MarkdownLayoutViewModel.cs
  • src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs
  • src/Elastic.Markdown/Page/Index.cshtml
  • src/Elastic.Markdown/Page/IndexViewModel.cs

Comment thread src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs Outdated
Comment thread src/Elastic.Markdown/Layout/_TableOfContents.cshtml Outdated
The button used text-nowrap with a fixed width and a single-line
leading, so longer custom labels overflowed into the horizontal
padding instead of wrapping — verified with devtools that a label
like "Star docs-builder on GitHub" left ~7px of visible padding
instead of the intended 24px. Dropping text-nowrap/leading-[30px]
in favor of normal wrapping fixes this without affecting the
short default label, which stays single-line as before.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Swaps the illustrative docset.yml/frontmatter example from an
Elastic Cloud trial CTA to a generic beta-signup CTA, so the
example reads as a general-purpose feature rather than something
Elastic-specific.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 10:56 — with GitHub Actions Inactive
`cta: <name>` becomes `cta: { id: <name> }`, via a new CtaFrontMatter
type. This leaves room to add other per-page CTA options (e.g. a
future `hidden: true`) without another frontmatter format change.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 11:01 — with GitHub Actions Inactive
- Move CTA name-lookup/unknown-id-warning logic from HtmlWriter into
  ConfigurationFile.ResolveCta, alongside the Ctas dictionary it
  resolves against, so any future caller gets the same fallback
  behavior for free instead of reimplementing it.
- Extract the CTA validation loop body into ValidateCta, matching
  the existing ValidateBranding convention in the same file.
- Reuse a single IntersectionObserver across htmx swaps instead of
  disconnecting and recreating it on every navigation; re-observing
  an already-observed element is a documented no-op.
- Reorder the sidebar CTA render condition so "show it" and "except
  in Codex" read as separate clauses (same truth table).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 11:12 — with GitHub Actions Inactive
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 11:43 — with GitHub Actions Inactive
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs (1)

378-397: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

CTA button.url still isn't scheme-validated.

ValidateCta only checks that Url is non-empty before storing it verbatim into Cta.Url, which flows straight into <a href="@Model.Cta.Url"> in _TableOfContents.cshtml. A javascript:/data: URL in docset.yml would become an executable link. This was already flagged on a previous revision of this PR and remains unaddressed.

🔒 Proposed fix
 	private static Cta? ValidateCta(string name, CtaDefinition definition, IDocumentationSetContext context)
 	{
 		if (string.IsNullOrWhiteSpace(definition.Button?.Label) || string.IsNullOrWhiteSpace(definition.Button?.Url))
 		{
 			context.EmitError(context.ConfigurationPath, $"'cta.{name}' must define both 'button.label' and 'button.url'.");
 			return null;
 		}
+		var url = definition.Button.Url.Trim();
+		if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri &&
+			uri.Scheme is not Uri.UriSchemeHttp and not Uri.UriSchemeHttps)
+		{
+			context.EmitError(context.ConfigurationPath, $"'cta.{name}.button.url' must use http/https or an allowed relative URL.");
+			return null;
+		}
 		if (definition.Benefits.Count > Cta.MaxBenefits)
 		{
 			context.EmitError(context.ConfigurationPath, $"'cta.{name}.benefits' has {definition.Benefits.Count} entries; a maximum of {Cta.MaxBenefits} is allowed.");
 			return null;
 		}
 		return new Cta
 		{
 			Name = name,
 			Label = definition.Button.Label,
-			Url = definition.Button.Url,
+			Url = url,
 			Benefits = definition.Benefits
 		};
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs` around
lines 378 - 397, Validate the CTA URL scheme in ValidateCta before assigning
Cta.Url, not just for non-empty input. Update the CtaDefinition/Button
validation path in ConfigurationFile.cs to reject unsafe schemes like
javascript: and data:, and emit a configuration error through context.EmitError
when the URL is not an allowed http/https (or relative) link. Keep the existing
name, label, benefits, and return-new-Cta flow unchanged, but ensure the value
stored in Cta.Url is sanitized/validated before _TableOfContents.cshtml can
render it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs`:
- Around line 378-397: Validate the CTA URL scheme in ValidateCta before
assigning Cta.Url, not just for non-empty input. Update the CtaDefinition/Button
validation path in ConfigurationFile.cs to reject unsafe schemes like
javascript: and data:, and emit a configuration error through context.EmitError
when the URL is not an allowed http/https (or relative) link. Keep the existing
name, label, benefits, and return-new-Cta flow unchanged, but ensure the value
stored in Cta.Url is sanitized/validated before _TableOfContents.cshtml can
render it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ab36896f-799b-4d35-8bac-47c90ad4b3b5

📥 Commits

Reviewing files that changed from the base of the PR and between 37990da and b2c9064.

📒 Files selected for processing (8)
  • docs/_docset.yml
  • docs/configure/content-set/cta.md
  • docs/syntax/frontmatter.md
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Documentation.Site/Assets/main.ts
  • src/Elastic.Markdown/HtmlWriter.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs
✅ Files skipped from review due to trivial changes (2)
  • docs/syntax/frontmatter.md
  • docs/configure/content-set/cta.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Documentation.Site/Assets/main.ts

…erabbitai)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@reakaleek reakaleek temporarily deployed to integration-tests July 1, 2026 15:15 — with GitHub Actions Inactive
@reakaleek reakaleek merged commit 012c708 into main Jul 1, 2026
25 checks passed
@reakaleek reakaleek deleted the thundering-motor branch July 1, 2026 15:48
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.

2 participants