Add white-label branding support for isolated builds#3159
Conversation
Allows any OSS repository using docs-builder to de-elasticise their isolated/GitHub Pages output via a new `branding` section in docset.yml. When `branding` is configured, the Elastic logo, footer, OG image tags, "Report a docs issue" / "Learn how to contribute" right-nav links, and the version dropdown are all suppressed. A custom header background colour and icon image (copied to `_static/`) replace the Elastic-branded chrome. Branding images are validated for allowed extensions, path traversal, and symlinks during configuration loading. Also fixes hardcoded `elastic/` org in GitHub edit links, docs links, and the DeploymentInfo component — these now derive the correct org/repo from the git remote via a new `GitHubRepository` property on `GitCheckoutInformation`. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds optional white‑label branding support: a Sequence DiagramsequenceDiagram
actor User
participant ConfigLoader as Config Loader
participant Validator as Branding Validator
participant Generator as Doc Generator
participant AssetCopier as Asset Copier
participant ViewModel as View Model
participant Layout as Layout Renderer
participant Browser as Browser
User->>ConfigLoader: Load documentation set config (includes branding)
ConfigLoader->>Validator: Validate branding (extensions, paths, symlinks, existence)
Validator-->>ConfigLoader: Return validated BrandingConfiguration (invalid fields => null + errors)
ConfigLoader->>Generator: Provide configuration/context
Generator->>AssetCopier: Branding present?
alt Branding present
AssetCopier->>AssetCopier: Copy icon and og-image into _static (check collisions)
AssetCopier-->>Generator: Report copy results/errors
else No branding
AssetCopier-->>Generator: Skip
end
Generator->>ViewModel: Populate Branding and static paths
ViewModel->>Layout: Supply branding props (icon, header-bg, og-image)
Layout->>Browser: Render conditional header/footer/OG/meta
Browser->>Browser: Display branded or default UI
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
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. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Elastic.Markdown/Layout/_TableOfContents.cshtml (1)
40-50:⚠️ Potential issue | 🟠 MajorSuppress “Report a docs issue” for branded builds.
This link still renders when
Model.Brandingis set, so white-label output keeps Elastic-specific chrome. Match the branding guard used for “Learn how to contribute”.Proposed fix
- `@if` (Model.BuildType != BuildType.Codex) + `@if` (Model.BuildType != BuildType.Codex && Model.Branding is null)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Elastic.Markdown/Layout/_TableOfContents.cshtml` around lines 40 - 50, The "Report a docs issue" link is not suppressed for branded builds; update the condition that currently checks only Model.BuildType (the block rendering the "Report a docs issue" <li> with Model.ReportIssueUrl and the SVG) to also require no branding by matching the guard used for the "Learn how to contribute" link (e.g. wrap or change the if to check string.IsNullOrEmpty(Model.Branding) or the same Model.Branding predicate used elsewhere), so the list item only renders for non-branded outputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs`:
- Around line 275-315: ValidateBrandingImage currently uses StartsWith to check
escape paths and only emits errors without removing invalid values; change the
flow so ValidateBrandingImage returns a sanitized string?null for invalid inputs
and make ValidateBranding assign the return back to branding.Icon and
branding.OgImage in ValidateBranding. Replace the StartsWith check with a robust
relative-path check using Path.GetRelativePath(sourceDir, resolved) and treat
any relative path that begins with ".." or is rooted outside sourceDir as
invalid; also validate extensions, symlinks and existence as before, emit the
same errors, and return null when invalid so the calling ValidateBranding can
drop the value from the BrandingConfiguration.
In `@src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx`:
- Around line 46-103: The component uses headerBg as the sentinel for "branded"
mode which causes docsets that have branding defined but no icon/header-bg to
fall through to the default EuiHeaderLogo (see headerBg, iconSrc, and
logoSection); fix by accepting an explicit branded flag from the Razor view (or
ensure the view always passes a default headerBg when Branding exists) and use
that branded boolean to select the branded/title-only branches instead of
testing headerBg for null—update the component to check the new branded prop
when building logoSection and ensure the isolated header Razor view sets branded
= Model.Branding != null.
In `@src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml`:
- Around line 10-11: Replace the expressions that concatenate full attribute
strings for the web component with proper Razor attributes: instead of
outputting literal strings like header-bg="..." and icon-src="..." from the
Model.Branding?.HeaderBg and Model.BrandingIconStaticPath expressions, render
those values as attribute values on the element (assign Model.Branding?.HeaderBg
to the header-bg attribute and Model.BrandingIconStaticPath to the icon-src
attribute) and omit each attribute when its value is null; this ensures Razor
treats the values as attribute content (not pre-built strings) so they are
encoded correctly for the web component.
In `@src/Elastic.Documentation/GitCheckoutInformation.cs`:
- Around line 53-64: The current GitHub repo derivation in
GitCheckoutInformation (using Remote and RepositoryName) can return non-GitHub
remotes or unnormalized values; change the logic in the GitCheckoutInformation
partial record to (1) normalize SSH and HTTPS GitHub remotes (handle formats
like git@github.com:org/repo.git, ssh://git@github.com/org/repo.git,
https://github.com/org/repo.git), (2) strip a trailing .git, (3) validate that
the result is exactly an org/repo pattern (two path segments with no host), and
only return that; otherwise return the fallback "elastic/docs-builder". Add the
proposed regex/helper methods into the same partial record (e.g.,
IsGitHubRemote, ExtractGitHubOrgRepo, StripDotGit) and use them from the
existing code paths that reference Remote and RepositoryName so downstream
interpolation always receives a validated org/repo.
In `@src/Elastic.Markdown/DocumentationGenerator.cs`:
- Around line 212-225: The loop that copies branding images (iterating imagePath
over branding.Icon and branding.OgImage) currently writes both files to
_static/{source.Name}, causing silent overwrites when basenames collide; update
the logic in DocumentationGenerator.cs (the block using
Context.ReadFileSystem.FileInfo.New, source.CopyTo, and
_writeFileSystem.FileInfo.New) to either (a) preserve a namespaced relative path
when building destination (e.g., include the image's relative directory under
outputStaticDir) or (b) detect duplicate destination names before copying and
emit an error via Context.Collector.EmitError (or skip with a warning) so
duplicates are not overwritten—ensure the chosen behavior handles both
branding.Icon and branding.OgImage and uses source.Name and the original
relative path to compute a unique destination.
In `@src/Elastic.Markdown/HtmlWriter.cs`:
- Around line 137-138: The docs tree URL is hardcoded to "/docs" causing broken
links for repos that use a different documentation directory; update the logic
that sets gitHubDocsUrl (where gitHubRepo and gitBranch are used) to include the
actual DocumentationSourceDirectory value (the same relative path used by the
edit URL) instead of "/docs", ensuring you only build the tree URL when
gitHubRepo != "elastic/docs-builder" and gitBranch is valid and non-empty.
In `@src/Elastic.Markdown/Page/IndexViewModel.cs`:
- Around line 74-75: IndexViewModel instances are created without setting the
new nullable Branding property, so pages still show Elastic chrome; when
constructing IndexViewModel (wherever it is instantiated for markdown pages and
passed to HtmlWriter/HtmlWriter.cs) populate the Branding property from the
source view/context (e.g., propagate the existing Branding from the surrounding
PageViewModel/controller or layout model) by assigning Branding =
existingModel.Branding (or equivalent) on the IndexViewModel before passing it
to HtmlWriter; ensure the same propagation is applied wherever IndexViewModel is
constructed so the layout receives the branding value.
---
Outside diff comments:
In `@src/Elastic.Markdown/Layout/_TableOfContents.cshtml`:
- Around line 40-50: The "Report a docs issue" link is not suppressed for
branded builds; update the condition that currently checks only Model.BuildType
(the block rendering the "Report a docs issue" <li> with Model.ReportIssueUrl
and the SVG) to also require no branding by matching the guard used for the
"Learn how to contribute" link (e.g. wrap or change the if to check
string.IsNullOrEmpty(Model.Branding) or the same Model.Branding predicate used
elsewhere), so the list item only renders for non-branded outputs.
🪄 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: Pro Plus
Run ID: bbaaf9d3-b750-41cf-9cda-a552eb085572
📒 Files selected for processing (14)
src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cssrc/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cssrc/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsxsrc/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsxsrc/Elastic.Documentation.Site/Layout/_Head.cshtmlsrc/Elastic.Documentation.Site/Layout/_IsolatedFooter.cshtmlsrc/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtmlsrc/Elastic.Documentation.Site/_ViewModels.cssrc/Elastic.Documentation/GitCheckoutInformation.cssrc/Elastic.Markdown/DocumentationGenerator.cssrc/Elastic.Markdown/HtmlWriter.cssrc/Elastic.Markdown/Layout/_TableOfContents.cshtmlsrc/Elastic.Markdown/Page/Index.cshtmlsrc/Elastic.Markdown/Page/IndexViewModel.cs
…tection - ConfigurationFile: ValidateBrandingImage now returns string?/null for invalid inputs; ValidateBranding assigns results back to branding.Icon / branding.OgImage so downstream code never sees invalid paths. Replaced StartsWith path-escape check with IFileInfo.IsSubPathOf() extension. - GitCheckoutInformation: Replace ad-hoc string manipulation with ExtractGitHubOrgRepo / NormalizeToGitHubPath helpers that handle all GitHub remote formats (HTTPS, git@, ssh://git@), strip .git, and validate the result is exactly org/repo before returning it. - Header.tsx: Add explicit branded boolean prop (r2wc 'boolean') so the Razor view controls branded-mode selection directly; removes the fragile headerBg-as-sentinel pattern. Isolated header template now passes branded="true"/"false" and binds header-bg/icon-src as plain attribute values for correct Razor HTML encoding. - DocumentationGenerator: Detect basename collisions between branding icon and og-image before copying; emit an error and skip on duplicate. - HtmlWriter: Tree URL now uses the real relative docs directory path (same computation as the edit URL) instead of the hardcoded "/docs". - _TableOfContents: Also suppress "Report a docs issue" for branded builds. - Prettier + dotnet format: fix DeploymentInfo.tsx formatting and IndexViewModel.cs import ordering. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
primary-nav injects the Elastic global navigation bar, which is not available in white-label isolated builds. Emit a build error when both are configured together. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Elastic.Markdown/DocumentationGenerator.cs`:
- Around line 203-237: CopyBrandingResources can attempt to copy branding images
into a non-existent _static directory causing DirectoryNotFoundException (and
runs for Codex/Assembler even when they shouldn't); update CopyBrandingResources
to first skip copying unless the current build type requires it (check
Context.Configuration.BuildType or equivalent to only run for HTML/exporting
builds) and before copying ensure the destination directory exists by creating
outputStaticDir via the write filesystem (use _writeFileSystem.DirectoryInfo/New
or Directory.CreateDirectory equivalent) so the directory is created prior to
any source.CopyTo calls; keep the existing collision checks (seen HashSet) and
logging but gate and create the directory before any copy operations.
🪄 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: Pro Plus
Run ID: 259a219c-cd67-4943-a849-02624b2669a9
📒 Files selected for processing (9)
src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cssrc/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsxsrc/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsxsrc/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtmlsrc/Elastic.Documentation/GitCheckoutInformation.cssrc/Elastic.Markdown/DocumentationGenerator.cssrc/Elastic.Markdown/HtmlWriter.cssrc/Elastic.Markdown/Layout/_TableOfContents.cshtmlsrc/Elastic.Markdown/Page/IndexViewModel.cs
🚧 Files skipped from review as they are similar to previous changes (4)
- src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml
- src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
- src/Elastic.Markdown/Page/IndexViewModel.cs
- src/Elastic.Markdown/HtmlWriter.cs
CreateDirectory is idempotent so this is safe even when the directory was already created by ExtractEmbeddedStaticResources. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Header.tsx: non-branded header now uses main's light gradient+shadow styling; branded header retains solid custom bgColor. Non-branded EuiHeaderLogo updated to main's light-mode colours (dark textInk, hover background). _TableOfContents.cshtml: version dropdown, report-an-issue, and learn-how-to-contribute now require both Model.Features.PrimaryNavEnabled (from main) and Model.Branding is null (from this branch). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Merging this in as experimental! |
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Why
`elastic/docs-builder` and its GitHub Pages action are open source, but until now every isolated build output was visually tied to Elastic — the Elastic logo in the header, the Elastic footer with copyright and legal links, and hard-coded references to `elastic/` GitHub URLs throughout. This made it impractical for any non-Elastic OSS project to use docs-builder to publish their own documentation.
This PR adds a `branding` block to `docset.yml` that lets any repository fully replace the Elastic chrome with their own identity.
What
A new optional `branding` section in `docset.yml`:
```yaml
branding:
icon: my-icon.svg # relative to docs dir — copied to _static/
header-bg: "#336699" # header background colour (default when absent: #000000)
og-image: og.png # Open Graph social image — copied to _static/
```
When `branding` is present (all sub-fields optional), the following Elastic-specific chrome is suppressed:
Using `branding` together with `features.primary-nav: true` is a build error — the primary nav requires Elastic global navigation which is not available in white-label builds.
Safety
Test plan
🤖 Generated with Claude Code