influxdata / influxdata/docs-v2

Product selector: data-driven refactor

Open
#7,520 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

agent-ready area:site-ui
Dominant language
JavaScript
Stars
82
Forks
326
Avg merge
1d 1h
Merged PRs (30d)
82

Description

# Product selector: data-driven refactor

Status: proposed. Separate PR on `master` (NOT on any cloud-docs branch).
Author context: fell out of debugging why `influxdb3/cloud` failed to render
(dropped `$telegrafEnterprise` from the product-selector merge, commit
`94fe509ff`). The bugfix shipped separately (`26ef42568` on
`ritwika/cloud3_init`); this plan removes the class of bug entirely.

## Problem

`layouts/partials/topnav/product-selector.html` hand-authors the product model
in the template. Each product is declared in THREE places that must stay in
sync:

1. a `{{ $xxx := dict "path/version" (slice name altLinkKey) }}` var,
2. the `$productInfo := merge $xxx ...` line,
3. a `{{ template "productLink" (dict "productPath" "path/version") }}` `

  • `.

    Missing step 2 (what `94fe509ff` did) yields a zero value that makes `isset`
    panic (`reflect.Value.Type on zero Value`) and aborts the Hugo build. Because
    Hugo renders concurrently, the abort leaves NONDETERMINISTIC partial output:
    whole product dirs silently missing from `public/`, which breaks the
    feedback-link check and starves `build:md` / `build:llms-full`.

    A temporary `errorf` guard (in `26ef42568`) now makes a missing entry fail
    loud and deterministic, naming the offending path. This refactor removes the
    footgun instead of guarding it.

    Secondary problem: alt_link keys are duplicated. They live in the template
    (step 1 above) AND in `data/api_products.yml`. The `cloud` / `cloud3` collision
    between `influxdb/cloud` (TSM v2) and `influxdb3/cloud` is managed by hand in
    both files.

    ## Constraints (why a pure products.yml loop does not work)

    1. **Grouping is per product+version, not per product key.** The `influxdb` key
    has versions `[v2, v1]` that split across two different dropdown groups
    (InfluxDB 2 vs InfluxDB 1). `products.yml` has one entry per key.
    2. **Three presentation fields are not catalog facts:** `alt_link_key`, the
    `New` state badge, and special links (`influxcloud/v1` →
    `/platform/#influxdb-cloud-1`).
    3. **Grouping does not map to one existing field.** `flux` is
    `menu_category: languages` but renders in the InfluxDB 2 group.

    Justifications for a separate `product_selector.yml`:

    1. One key splits across groups. influxdb has versions: [v2, v1] that render in different dropdown groups with different alt_link_keys. A single-entry-per-key catalog can't hold per-version group/link data without either splitting the key (breaks every influxdb lookup) or nesting a per-version map (rebuilds the separate model inside products.yml).
    2. Dropdown group ≠ menu_category. flux is languages but renders under InfluxDB 2; telegraf* are other but get their own group; influxdb_cloud1 is managed but sits under InfluxDB 1. Grouping is curated presentation, not a catalog fact, so it needs its own field either way.
    3. Presentation fields don't belong in the catalog. alt_link_key, state: New, special_link, and api_path are selector/rendering concerns. Folding them into products.yml pollutes the source of catalog facts and risks breaking other consumers of those keys.
    4. Order is meaningful. The dropdown renders in deliberate sequence within each group. An ordered list expresses that natively; a keyed product map in products.yml has no guaranteed iteration order in Hugo.
    5. Separation keeps single sources of truth. products.yml stays authoritative for name/altname (referenced by key); the selector file owns presentation. Inverting merges two concerns into one file and trades a clean boundary for no real gain.

    Conclusion: the dropdown is curated presentation, distinct from the product
    catalog. Model it as its own data file. `products.yml` stays the source of
    catalog facts (name, altname); the new file references products by key and adds
    presentation.

    ## Proposed design

    One data file that folds dropdown presentation + `alt_link_key` + API path,
    killing the `api_products.yml` duplication (decision: yes, fold it in).

    ```yaml
    # data/product_selector.yml
    # Presentation + cross-product link keys for the topnav product selector.
    # Catalog facts (name, altname) come from data/products.yml by `key`.
    # `alt_link_key` is the single source of truth for cross-product API/page
    # alternate links; `api_path` replaces data/api_products.yml.
    groups:
    - title: InfluxDB 3
    categories:
    - label: Self-managed
    items:
    - { path: influxdb3/core, key: influxdb3_core, alt_link_key: core, api_path: /influxdb3/core/api/, state: New }
    - { path: influxdb3/enterprise, key: influxdb3_enterprise, alt_link_key: enterprise, api_path: /influxdb3/enterprise/api/, state: New }
    - { path: influxdb3/clustered, key: influxdb3_clustered, alt_link_key: clustered, api_path: /influxdb3/clustered/api/ }
    - label: Fully-Managed
    items:
    - { path: influxdb3/cloud, key: influxdb3_cloud, alt_link_key: cloud3, state: New } # api_path added when cloud gets a generated API ref
    - { path: influxdb3/cloud-serverless, key: influxdb3_cloud_serverless, alt_link_key: cloud-serverless, api_path: /influxdb3/cloud-serverless/api/ }
    - { path: influxdb3/cloud-dedicated, key: influxdb3_cloud_dedicated, alt_link_key: cloud-dedicated, api_path: /influxdb3/cloud-dedicated/api/ }
    - label: Tools
    items:
    - { path: influxdb3/explorer, key: influxdb3_explorer, alt_link_key: explorer, state: New }
    - title: InfluxDB 2
    items:
    - { path: influxdb/v2, key: influxdb, alt_link_key: v2, api_path: /influxdb/v2/api/ }
    - { path: influxdb/cloud, key: influxdb_cloud, alt_link_key: cloud, api_path: /influxdb/cloud/api/ }
    - { path: flux/v0, key: flux, alt_link_key: flux }
    - title: InfluxDB 1
    items:
    - { path: influxdb/v1, key: influxdb, alt_link_key: v1, api_path: /influxdb/v1/api/ }
    - { path: enterprise_influxdb/v1, key: enterprise_influxdb, alt_link_key: enterprise_v1, api_path: /enterprise_influxdb/v1/api/ }
    - { path: influxcloud/v1, key: influxdb_cloud1, alt_link_key: v1, special_link: /platform/#influxdb-cloud-1 }
    - title: Telegraf
    items:
    - { path: telegraf/v1, key: telegraf }
    - { path: telegraf/controller, key: telegraf_controller, state: New }
    - { path: telegraf/enterprise, key: telegraf_enterprise, state: New }
    - title: Other products
    items:
    - { path: chronograf/v1, key: chronograf }
    - { path: kapacitor/v1, key: kapacitor }
    ```

    Notes:
    - `influxdb` appears twice (v2 and v1) under different groups — the per-version
    grouping constraint, expressed naturally by `path`.
    - `name` / `altname` resolved in-template: `index .Site.Data.products .key`.
    - `api_path` present only for products that have a generated API reference.
    `influxdb3/cloud` omits it until it enters the OpenAPI pipeline.

    ### Template

    `product-selector.html` collapses to two nested `range` loops over
    `.Site.Data.product_selector.groups` (+ optional `.categories`). The 18 `dict`
    vars, the `merge`, the 18 hardcoded `

  • `s, and the `errorf` guard all go away.
    Selected-label logic keys off the current page's `path`.

    ### api_products.yml removal

    - Delete `data/api_products.yml`.
    - `api-docs/scripts/generate-openapi-articles.ts` (+ compiled `dist/`) currently
    loads `api_products.yml` as `alt_link_key -> api_path`. Point it at
    `data/product_selector.yml`, flattening `groups[].(categories[].)items[]` into
    the same `{alt_link_key: api_path}` map. Skip items without `api_path`.
    - Verify the flattened map matches the old `api_products.yml` exactly before
    deleting (snapshot both, diff).

    ## Risks and test plan

    - **Visual regression** (order, grouping, badges, active state): primary risk.
    - `cypress/e2e/topnav.cy.js` `product dropdown > has links to all products`
    is the guard. NOTE: on the cloud branch this spec currently fails/skls due
    to a draft release-notification `beforeEach` and a home-background
    assertion — unrelated to the selector, but fix or account for it so the
    dropdown assertion actually runs.
    - Screenshot-diff the dropdown before/after.
    - **Cross-product alt links**: `generate-openapi-articles.ts` output frontmatter
    `alt_links` must be byte-identical after the source swap. Diff generated API
    pages before/after.
    - **Coherence**: rerun `scripts/check-md-alternate-coherence.js` and
    `.ci/scripts/check-feedback-links.js`.
    - **Full build**: `npx hugo` exit 0, no `reflect.Value` panic, every product
    dir present in `public/`.

    ## Open questions

    - Keep `data/products.yml` as the name source, or denormalize `name` into
    `product_selector.yml`? (Prefer keep — single source for catalog facts.)
    - Does any other template/script consume `api_products.yml`? Grep before delete
    (known: `generate-openapi-articles.ts` + its `dist/` twin).
    - Should the selector data file also drive other product lists (e.g. the v3
    wayfinding modal), or stay scoped to the topnav selector for this PR?

    ## Scope

    Refactor only. No content changes. Separate PR branched from `master`.
    Does not depend on the cloud-docs branch; the bugfix already shipped.

  • Contributor guide

    No contributing guide indexed for this repository

    First steps

    1. Read the whole issue, then the project's contributing guide.
    2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
    3. Fork the repository and make your change on a branch.
    4. Open a pull request that references the issue number.

    Research direction

    Start with layouts/partials/topnav/product-selector.html, data/products.yml, data/api_products.yml, and api-docs/scripts/generate-openapi-articles.ts. Review cypress/e2e/topnav.cy.js and the coherence, feedback-link, and Hugo build checks before changing the data flow. Done means the selector preserves its grouping, order, links, badges, and active state, while generated API links remain identical and the old API data file is no longer needed.

    Written by the indexing model from the issue text.

    Assessment

    Tech stack
    javascript, typescript, yaml
    Domain
    build-system, documentation, frontend, testing-qa
    Issue type
    Refactor
    Difficulty
    5/5
    Estimated time
    Over a week
    Activity status
    Quiet
    Clarity
    Mostly clear
    Newbie friendliness
    35/100

    Get new issues in your inbox

    A short digest of beginner-friendly GitHub issues.