Skip to content

PMM-14109 Improve Advisor UX - #5656

Draft
ademidoff wants to merge 137 commits into
mainfrom
PMM-14109-improve-advisor-ux
Draft

PMM-14109 Improve Advisor UX#5656
ademidoff wants to merge 137 commits into
mainfrom
PMM-14109-improve-advisor-ux

Conversation

@ademidoff

Copy link
Copy Markdown
Member

Ticket number: PMM-14109

Feature build: SUBMODULES-4438

ademidoff and others added 30 commits June 27, 2026 10:53
- Implemented MarkCheckResultsRead function in the service layer to update the read state of check results.
- Added corresponding API endpoint for marking check results as read.
- Introduced new parameters and response structures for the API.
- Updated settings model to include Advisor notification settings.
- Enhanced environment variable parsing to support new Advisor notification feature.
- Added tests for the new functionality to ensure proper behavior.
…y Fields

- Introduced CheckSeverity and CheckTriggeredBy types to represent the severity levels and initiators of advisor checks.
- Updated CheckResult model to include Severity (CheckSeverity) and TriggeredBy (CheckTriggeredBy) fields.
- Modified database schema to accommodate new fields in the check_results table.
- Adjusted related services and models to handle the new fields, including changes in the execution flow of advisor checks.
- Updated API services to support the new fields in requests and responses.
- Enhanced tests to cover new functionality and ensure proper handling of severity and triggered by information.
… components

- Introduced new fields: environment, cluster, replication_set, and outcome to CheckResultHistoryItem in advisors.proto.
- Updated validation logic in advisors.pb.validate.go to include new fields.
- Modified JSON representations in list_check_results_history_responses.go and v1.json to accommodate new fields.
- Adjusted database schema and models to reflect new fields in CheckResult.
- Enhanced tests to validate new fields and ensure proper functionality in AdvisorInsights components.
…, updating labels, and enhancing test coverage
ademidoff added 19 commits July 26, 2026 14:32
Re-indent the secure_file_priv script and drop its stale commented-out
subcategory; remove an empty labels map from the indexes-larger finding.
percona-ui's theme pins MuiIconButton to a fixed 40x40, so the four action
icons needed 166px of content box and were clipped by the 150px column
whatever their padding. Unpin the button box instead, which collapses each
onto its icon and leaves room to spare.

Centre the Actions header too: align='center' only sets text-align, and MRT
pushes the head cell's actions box right with margin-left:auto, absorbing the
free space before justify-content applies - so stretch the label box and
centre inside it.

Badge the services action with the number of services the check is disabled
for, so a check with disablements is distinguishable at a glance; the count
doubles as the signal, so the state is not conveyed by colour alone.
The Script field sat at the editor's 160px minimum, roughly half the height of
its counterpart in the details pane, leaving the lower half of the form empty.
Let it grow into the leftover space, with alignment pinned to the top so a
short script is not centred once the box can outgrow its content.

Give it a 300px floor as well: the editor's own minimum lives inside an
overflow:auto wrapper, so it never reaches the outer box as a min-content
floor, and a long queries list would otherwise squeeze the editor away instead
of scrolling the form.
Advisor emails are sent by pmm-managed itself over the SMTP server Grafana is
configured with, so Grafana was never in the delivery path - it only stored the
recipient list in a contact point named 'PMM Advisor Insights'. That indirection
required caching the addresses in settings, refreshing the cache on every
settings change, and an authenticated request context to reach Grafana, and it
failed silently in two ways: no warning when the contact point was missing, and
no way to notice when its addresses had changed.

Take the recipients directly instead, as a settings field with its own input on
the Advisors tab. Addresses are validated as bare parseable emails, capped at
20, and deduplicated. Enabling notifications without a recipient is now
rejected up front, since it could only ever fail silently later; the delivery
path still logs if it finds no recipients, because
PMM_ENABLE_ADVISOR_NOTIFICATIONS bypasses that validation.

Drops GetEmailContactPoint and the whole contact-point reconcile/cache. Also
reorders the Settings and ChangeSettingsRequest field declarations by field
number - the numbers themselves are unchanged.
Category and Sub category were plain text inputs, so authors had to know the
existing values by heart and typos silently created near-duplicate categories.
Offer the values already in use through a freeSolo Autocomplete, which still
accepts anything typed in. autoSelect is required alongside it: freeSolo alone
only commits on Enter, so a typed value would be dropped on save.

Options come from the advisors query the list page already loaded, so opening
the form costs no extra request.
percona-ui's TextInput forwards only sx from formHelperTextProps, so the
*-field-error-message testids these 11 call sites passed never reached the DOM.
No test referenced them. Drop the helper and its only exporting file rather than
leave props that look wired up but are not.

Reported upstream as percona/peak-ui#52.
percona-ui's Table uses :has(), and nwsapi composes an internal querySelector
from the matched element while resolving it. When that element carries a MUI id
generated by React's useId (':r23:'), the selector is not valid raw CSS and
nwsapi throws, escaping jsdom's matchesDontThrow and failing the test. It fires
whenever a MUI input renders on a page containing the Table, which so far forced
an explicit id per input or a {hidden: true} escape hatch in each test.

Real browsers treat an unparseable selector as simply not matching, so restore
that in the test env: swallow SyntaxError from matches/closest/querySelector/
querySelectorAll and return the no-match result. Not fixable in MUI - React
colon-wraps useId values on purpose so they cannot be selectors, and
identifierPrefix is inserted inside the colons.

Note the error is built in nwsapi's own realm, so 'instanceof SyntaxError' is
false; the name has to be checked instead.
The report already broke findings down by severity; technology is the other axis
readers scan for, so add it alongside. Insights record the service type they ran
against rather than a technology, so the counts are keyed by service type and
rendered with the technology names used across the UI and API.

Every technology is listed, zero included, so a reader can tell 'no findings'
apart from 'not covered by this report'. The order comes from a slice rather than
map iteration, which would shuffle the lines between runs.

Also drops the stale reference to a Grafana contact point from the send
function's doc comment.
…pane

The insight details pane reported whether the underlying check is currently
enabled, which says nothing about the insight itself - it reflects the check's
state now, not when the insight was produced. How the run was triggered is the
useful fact, and it is already recorded on the insight.

Reuses the TRIGGERED_BY_LABEL map that copy-as-text already renders from, so the
pane and the clipboard output cannot drift. Drops the checkEnabled prop and the
enabled/disabled labels, which had no other consumer.
Editing a check previously required finding its row in the table, so the details
pane was a dead end for the most likely next action. Add Edit as the first button
in the pane's action row, mirroring Clone: open the form in edit mode and close
the pane behind it.

The button is disabled rather than hidden on Percona-shipped checks, so the
action stays discoverable and the Source field above explains why it is
unavailable.
Move the Read/Unread toggle out of its own column into Actions as an
envelope icon - a grey open envelope once read, a blue sealed one while
unread - so the Insights and Advisor checks panes read the same way.

Drop the "Toggle read" menu item, now duplicated by that icon, and add
"Copy URL" below "Copy as text". The copied link keeps the current
filters and page: the details pane resolves the insight against the
current page of results, so a bare ?insight=<id> can land on a page that
does not contain the row.

Run and disable a check for the insight's own service only; doing either
for every service stays on the Advisor checks page. This adds service_ids
to StartAdvisorChecksRequest and threads it down to the target lookup. A
service-scoped run also needs a service-scoped registry cleanup, since
deleting every result for the check name would drop the findings of the
services the run never touched.
The formatted timestamp needs 149px, more than the 128px of content width
the column had, so every row wrapped. Rather than widen the column to
190px, drop the cell text to 0.85rem and keep the column at 160px.

The font size goes on a span inside the cell, not on the cell itself:
percona-ui's Table styles '& .MuiTableCell-body' from the table root, and
that descendant selector outranks a cell-level sx.
Order the print() prefix as check -> function:line:col so it reads
top-down: the Starlark frame already carries the check name as its
position filename, it was just paired with the wrong half.

Drop the "thread <check name>:" prefix from the Starlark errors. Every
caller already names the check - the test pane shows "Failed to execute
check '<name>' on service '<service>'" - so the prefix only pushed the
real message further to the right. The thread keeps its name for
debugging, it just no longer leaks into the message.

Label the test results chip "Error" rather than "Failed", which is
reserved for checks that ran and found something.
Three failures in the Unit tests job:

- checks: funcs_test asserted the "thread <name>:" prefix that the
  Starlark errors no longer carry.
- management: the StartChecks mock was still set up for one argument
  after the call gained serviceIDs, so testify panicked on arity.
- server: enabling advisor notifications without a recipient became
  invalid when the recipient list landed, but this test still did
  exactly that. It fails since that commit, not since the run scoping.

Also switch the errors.As assertions in server_test to require.ErrorAs,
which testifylint flags in the file either way.
ChangeSettingsRequest takes the recipient list as common.StringArray so an
unset list can be told from one cleared to empty, while the Settings response
returns a plain repeated string. toPayload round-tripped the response shape
back to the API, so protojson rejected the bare array with
"proto: syntax error: unexpected token [" and no advisor setting could be saved.
Both panes opened at 60vh unless a row double-click asked otherwise, so a
pasted deep link (?details= or ?insight=) landed half-height and had to be
maximized by hand every time. Each pane now opens maximized however it was
triggered, and 60vh is reached only via the toggle; the choice is per-open and
does not carry over. Drops the initialMaximized prop and the page-level state
that fed it, and makes the toggle's aria-label follow its state.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8100cd2e-454f-45b7-8feb-1ee8074a3c60

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

ademidoff and others added 4 commits July 31, 2026 02:41
Filters the list to the insights of one check, via the existing check_name
support on ListInsights. No control was added to the filter bar: the table's
first column is the check summary, so with the filter on every row shows the
same check, and Clear filters already picks it up.

MarkInsightsRead shares its filter object with the list query but takes
InsightsFilters, which had no check_name — so the bulk mark-as-read would have
ignored the new filter and marked every insight read, defeating the very guard
that requires a filter before enabling the button. Added check_name there and
wired it through to models.InsightFilters, which already applied it.

InsightsFilters is unreleased (absent from main), so its fields are renumbered
to put check_name first; no reserved ranges are needed.
The link competed with the row's own click targets and pushed the summary
around. It becomes a non-clickable FileOpenOutlined indicator (a document with
a north-east arrow) marking rows that have documentation; the working link
already lives in the details pane, which the tooltip points at, and copy-as-text
still carries the URL.

Deliberately not OpenInNew: that glyph is the standard "opens in a new tab"
affordance and would promise a click this indicator does not accept.
format-fast used $(FILES), which is empty unless passed explicitly, so a bare
`make format-fast` died with "cannot use -w with standard input". $(GO_FILES)
falls back to every *.go file, matching how `format` behaves; passing
FILES="a.go b.go" still narrows it.
The interval spinner stepped by 0.1, so a click turned 24 into 24.1. step: 1
alone would not have fixed it: a number input anchors its steps to min, so the
0.1 floor made every stepped value fractional and would have left a plain 24
failing the browser's step check. The floor is now 1 hour, and the schema
rejects typed fractions the spinner cannot produce.

Intervals below an hour are no longer accepted by the form, so an install
carrying one must correct that field before the tab can be saved. pmm-managed
still accepts any interval down to a second; this is a UI floor only.
FileOpenOutlined's north-east arrow collapsed into something reading as a mouse
cursor at the cell's ~16px font size, implying a click the indicator does not
accept. DescriptionOutlined is the plain document glyph.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants