Files
harvester-ui-extension/pkg/harvester/promptRemove/__tests__/health.harvesterhci.io.healthignorerule.test.js
T
67ff2777d6 feat: add resource health dashboard in a Components tab (#1225)
* feat: add component health dashboard sections

Add health summary and component health tables with severity filters and expandable check details.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* feat: use health API group for component health resources

Update dashboard resource types to use the health.harvesterhci.io API group.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* feat: add component health ignore controls

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* feat: gate component health behind the componentHealth feature flag

Only show the component health dashboard sections on Harvester v1.10.0 and later.

Signed-off-by: Jack Yu <jack.yu@suse.com>

* fix: address component health review comments

- sort the health summary table by a severity rank
- use buttons instead of anchors for restore and show more actions
- let the affected resources list expand so every resource can be restored
- widen the ignore popup and cap its height

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* refactor: split component health into dedicated components

Move the health summary, component health tabs and ignore popup out of
the dashboard page into components/health, and share the constants and
helpers through utils/component-health.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* fix: sort component health by severity rank

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* fix: toggle health rows in place and tell empty filters from no checks

- toggle the expanded message and resource ids by mutating the unwrapped
  arrays, the template does not pass refs
- show "No checks match the selected filters" when the severity filters
  hide existing checks, keep "No issues detected" for a report without
  checks

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* refactor: fetch component health resources in their own components

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Yu <jack.yu@suse.com>

* feat: move health into a Components tab with the checks as sub-rows

The dashboard showed the summary table, then side tabs with a second
table for each component. It is now one table in a new Components tab
next to the Events tabs: one row per component, which opens to its
checks. Rows start closed, and the component name is a button so the
keyboard can open them. The tab only shows when the addon's resources
exist.

The table's own filter box replaces the severity dropdown. It only looks
at the table's own rows and the checks are in the sub-rows, so the rows
give it the text of their checks, a row only shows the checks it found,
and the rows it found open.

Ignoring a check moves from a popover, which cut off its Reason field
and Ignore button, to an RcModal that starts over each time it opens.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* refactor: use RcButton for the component health buttons

The Ignore modal's button already was one. This moves the rest of them,
the row's Ignore, Restore, the "show more" links and the component name,
off the global btn classes.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* refactor: manage one ignore rule per check from the row's actions

A check now has one rule, reached through the table's action menu: create
it when the check has none, view, edit or delete it when it has one. View
and edit use the same modal as create. This replaces the Ignore button and
the Restore links, which could only delete a whole rule and said the check
was restored when it wasn't.

The modal's section titles were the radio group's h3, as big as the modal's
own title. They follow what rancher/dashboard does in its forms now.

The actions follow what the user may do. The rules are only loaded with
their schema, and a list that fails no longer empties the table.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* refactor: let the tables filter by themselves and show resources as chips

The filter had logic of its own on top of the table's. The nested table now
filters its rows the way the outer one does, with what is typed in the box.
Rows no longer open by themselves, which is how the other tables with
sub-rows behave. Expand All and Collapse All are there instead, copied from
what the table has from shell 3.0.15.

Affected resources are chips under the name the UI has for their type, with
the usual counter badge and the API version beside it. Ignored resources are
outlined instead of filled, and the text of an ignored check is muted. The
links use the shell's wording.

The component rows lose Last Updated. The summary only has one time, for its
counts as a whole, so it said nothing about the component in the row. They say
Error instead of Critical, as the checks inside them do. The Node column goes,
as nothing sets its label.

The Components tab shows the number of errors as a badge, as nothing else on
the page tells there are any.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* fix: name the ignore rule after its check and make its expiry clear

- Name the rule the UI makes after its check, so Kubernetes refuses a
  second one for the same check. The row's menu only manages that rule.
- Show the rule's current expiry above the durations, and the new one
  under them once a duration is chosen. Edit starts with none chosen,
  Save stays off until something changes, and a warning says when an
  expired rule would stay expired.
- Delete the rule through the shell's delete prompt, like every other
  delete.
- Say which list could not be loaded, instead of showing it as empty.
- Keep the full count of affected resources, say how many ignored ones
  are left out of the list, and count them on Show ignored.
- Drop the checks' Last Updated, which is the time of their whole report.
- Leave out the row menu when no check has an action.
- Mark the Components tab with the shell's alert icon when there are
  errors, with their number in its tooltip, instead of a badge.

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

---------

Signed-off-by: Jack Yu <jack.yu@suse.com>
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
Co-authored-by: Jack Yu <jack.yu@suse.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 16:56:15 +01:00

50 lines
1.6 KiB
JavaScript

import { mount, flushPromises } from '@vue/test-utils';
import Banner from '@components/Banner/Banner.vue';
import PromptRemoveIgnoreRule from '../health.harvesterhci.io.healthignorerule.vue';
function ruleOf(checkKey, remove = () => {}) {
return { spec: { componentHealth: 'h', checkKey }, remove: jest.fn(remove) };
}
function mountPrompt(value) {
const close = jest.fn();
const prompt = mount(PromptRemoveIgnoreRule, {
props: { value, close },
global: { mocks: { $store: { getters: { 'i18n/t': (key) => key } } } },
});
return { prompt, close };
}
describe('promptRemove: health.harvesterhci.io.healthignorerule', () => {
it('names the check the rule is for, not the rule', () => {
const { prompt } = mountPrompt([ruleOf('ImageRetryLimitExceeded')]);
expect(prompt.find('b').text()).toBe('ImageRetryLimitExceeded');
});
it('deletes the rule and closes', async() => {
const rule = ruleOf('a');
const { prompt, close } = mountPrompt([rule]);
const buttonDone = jest.fn();
await prompt.vm.remove(buttonDone);
expect(rule.remove).toHaveBeenCalledTimes(1);
expect(close).toHaveBeenCalledWith(buttonDone);
expect(buttonDone).not.toHaveBeenCalled();
});
it('shows why the rule could not be deleted and stays open', async() => {
const { prompt, close } = mountPrompt([ruleOf('a', () => Promise.reject(new Error('forbidden')))]);
const buttonDone = jest.fn();
await prompt.vm.remove(buttonDone);
await flushPromises();
expect(prompt.findComponent(Banner).text()).toContain('forbidden');
expect(close).not.toHaveBeenCalled();
expect(buttonDone).toHaveBeenCalledWith(false);
});
});