lxbme opened a new pull request, #3462:
URL: https://github.com/apache/apisix-dashboard/pull/3462

   Please answer these questions before submitting a pull request, **or your PR 
will get closed**.
   
   **Why submit this pull request?**
   
   - [ ] Bugfix
   - [x] New feature provided
   - [ ] Improve performance
   - [ ] Backport patches
   
   **What changes will this PR take into?**
   
   Choosing a plugin meant picking from a grid of bare names — 
`ai-aws-content-moderation`, `graphql-proxy-cache`, `workflow`, `api-breaker` — 
with no description and nothing to click. Once a plugin was open, the drawer 
showed its name and the editor was handed that plugin's JSON schema, so there 
was validation and completion, but nothing anywhere said what the plugin was 
*for*, and nothing linked out. A user who did not already know the plugin had 
to leave the dashboard and search for it.
   
   Each plugin now carries a link to its documentation: an icon on the plugin 
card, and an icon with a label in the editor drawer's title row.
   
   ### Why a link rather than a description
   
   The gateway cannot supply the text. Of the plugins returned by `GET 
/apisix/admin/plugins?all=true` on APISIX 3.17.0, **none carries a 
`schema.description`**. Bundling a name-to-description table in the dashboard 
would mean 107 entries times five locales, maintained against a plugin list 
that changes every gateway release — and a stale description is worse than 
none. The docs site is the only available source, and its URL is derivable from 
the plugin name.
   
   ### The URL is derived, and the exceptions are enumerated
   
   `src/utils/pluginDocs.ts` maps a plugin name and the UI language to a URL or 
to `null`. Coverage was measured against the live docs site across every plugin 
name the gateway reports in both subsystems (106 http plus `mqtt-proxy` from 
stream):
   
   | Result | Count |
   |---|---|
   | Resolves on the released docs | 100 / 107 |
   | Documented under a different name | 2 (`serverless-pre-function`, 
`serverless-post-function` share `/plugins/serverless/`) |
   | No page at all | 2 (`example-plugin` is the gateway's sample; `ai` has no 
page of its own) |
   | Newer than the current docs release | 3 (`ai-cache`, `ai-lakera-guard`, 
`mcp-bridge` — they resolve only under `/next/`) |
   
   The two `serverless-*` names get a slug override. The other five render **no 
link at all**: a dashboard that is the first thing many people see of APISIX 
should not hand out broken links, and a missing affordance is a smaller failure 
than one that lies.
   
   The three unreleased plugins deliberately do not link `/next/`. That would 
resolve today, but it points users at documentation for a version they are not 
running, and those entries would expire silently once the released docs catch 
up. Their absence is self-correcting; a stale `/next/` pointer is not.
   
   ### Following the UI language
   
   The link resolves to `/zh/docs/...` when the dashboard is in Chinese and to 
the English docs otherwise. Coverage was measured separately for Chinese and is 
identical — 100 of 107, and **the same seven names fail in both locales, 
character for character** — so following the language introduces no failure 
mode that English does not already have. The docs site publishes English and 
Chinese only; `/es/`, `/de/` and `/tr/` are 404, so those three fall back to 
English rather than producing a URL that does not exist.
   
   The comparison is exact (`language === 'zh'`), not a prefix match: 
`src/config/i18n.ts` derives `supportedLngs` from the resource keys, so the 
only possible values are the five bare codes with no regional variants. This 
matches how `antdConfigProvider` already indexes its locale table.
   
   ### Why the control is an anchor
   
   `PluginDocsLink` renders a Mantine `Anchor`, i.e. a real `<a href 
target="_blank" rel="noopener noreferrer">`, following the pattern 
`Header/index.tsx` already uses for the docs button. The repo's `RouteLinkBtn` 
is a Mantine `Button` carrying `href`; middle-click, ⌘-click and "copy link 
address" are exactly what someone reaching for documentation does, and none of 
them work on a `<button>`. `RouteLinkBtn`'s own call sites are deliberately 
untouched.
   
   The e2e assertions use `getByRole('link', …)`, which fails if the control 
ever regresses to a button.
   
   ### Two call sites, six surfaces
   
   `PluginCard` and `PluginEditorDrawer` are both shared: the card renders in 
the select-plugins picker, in the list of already-selected plugins, and on the 
Plugin Metadata page; the drawer is used by the plugins form field and by 
Plugin Metadata. Two edits therefore cover every place a plugin is chosen or 
configured.
   
   The link is unconditional on drawer mode — it shows in `add`, `edit` and 
`view` alike. What a plugin does is the same question whether you are choosing 
it, configuring it, or reading someone else's configuration, and `view` is what 
a read-only operator sees.
   
   On the card the link is icon-only, with an `aria-label` naming the plugin, 
so a screen reader can tell which of a hundred identical icons it has landed 
on. In the drawer the label is visible and **no** `aria-label` is set: 
overriding a visible label with text that does not contain it would break WCAG 
2.5.3 Label in Name. The drawer's label reuses the existing top-level `docs` 
key rather than introducing a second key meaning the same thing.
   
   One new string, `form.plugins.docsFor`, in all five locales. It is 
interpolated with `escapeValue: false`, prophylactically — #3459 shipped an 
accessible name reading `R&amp;D` before that was caught.
   
   **Related issues**
   
   Closes #3461
   
   **Checklist:**
   
   - [x] Did you explain what problem does this PR solve? Or what new features 
have been added?
   - [x] Have you added corresponding test cases?
   - [ ] Have you modified the corresponding document?
   - [x] Is this PR backward compatible? If it is not backward compatible, 
please discuss on the mailing list first
   
   Tests:
   
   - `src/utils/pluginDocs.test.ts` — 13 cases: a plain name; both 
`serverless-*` collapsing to `serverless`; each of the five undocumented names 
returning `null`; the `/zh` prefix; `es`/`de`/`tr` falling back to English 
rather than emitting a `/es/` URL that does not exist; and the zh prefix 
combined with a slug override, which is the interaction the individual cases 
miss.
   
   - `e2e/tests/regression/plugin.docs-link.spec.ts` — five tests: the drawer 
link carries the exact expected `href` and `target="_blank"`; a plugin with no 
documentation page (`example-plugin`) exposes **no** link, which is the 
assertion that fails if the exception table is dropped; the same link's `href` 
gains `/zh/` after switching the UI to Chinese; a picker card exposes a link 
whose accessible name is `key-auth documentation`, which is the only coverage 
of the component's icon-only branch; and an undocumented plugin's card exposes 
no link.
   
   The card sits inside a `Combobox.Option`, so whether a nested anchor's click 
survives Mantine's option handling was checked by hand in a real browser rather 
than inferred: clicking the icon opens a new tab on the plugin's documentation 
page and leaves the picker open and unchanged. No `stopPropagation` was needed, 
so none was added.
   
   Verified: `pnpm test` (122 passed), `pnpm lint`, `pnpm exec tsc -b`, and 
both a `--mode test` and a production `pnpm build`, all clean. The new spec 
passes 5/5 in isolation.
   
   A full Playwright run on this machine is not currently a usable signal, and 
the reason is worth stating rather than hiding behind a pass rate. The suite's 
own `bulk-1000` spec drives etcd into multi-second stalls — its logs show 
51-second raft reads on the health-probe key — after which unrelated specs fail 
with `has no healthy etcd endpoint available`. Separately, this machine cannot 
run the Monaco-backed specs headless at all. Both were confirmed to be 
independent of this change by checking out `master` (`89fc2ed1`), rebuilding, 
and re-running the failures: every one of the eight specs sampled that way 
fails identically on `master`, and the specs that recovered once etcd was 
healthy — including this PR's own — recovered on both trees.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to