From bf7412dddf78ae75094e8cd6bdec479b59ce41d0 Mon Sep 17 00:00:00 2001 From: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:02:29 -0300 Subject: [PATCH] Merge 5.0.0 into 5.0.1 (#1518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Report skipped bumps in the repository bumper workflow (#1492) Improve repository bumper error handling * Recalculate flyout position when the sidecar is opened (#1503) * Adapt flyout position in relation to the sidecar * Add changelog * Change resizable button emphasis style from :focus to :active (#1508) * fix(sidecar): change resizable button emphasis style from :focus to :active Update the .sidecar-resizableButton CSS to emphasize the "grab" icon on :active instead of :focus, matching the intended interaction feedback when dragging the resizer. Closes https://github.com/wazuh/wazuh-dashboard-plugins/issues/8989 Co-Authored-By: Claude Signed-off-by: Antonio David Gutiérrez * fix(sidecar): remove leftover native focus outline on resizable button The resizable button programmatically calls .focus() on every click, so after removing the custom :focus emphasis in favor of :active, the browser's default focus outline was left showing as an unstyled colored border until focus moved elsewhere. Suppress it for mouse/touch interaction while keeping it for keyboard users via :focus-visible. Co-Authored-By: Claude Signed-off-by: Antonio David Gutiérrez * fix(sidecar): opt out resizable button from the global focus-ring animation The resizable button programmatically calls .focus() on every click, which triggered OSD's global brute-force focus-ring animation (:focus:not([class^="eui"]):not(.osd-resetFocusState) in _base.scss) since the button isn't an EUI component. That showed as a lingering colored border after dragging, independent from the button's own :active styling. Opt out via the "osd-resetFocusState" class, which is the mechanism _base.scss already documents for components with hand-crafted focus states, instead of fighting the rule's !important/specificity locally. Supersedes the previous outline:none attempt, which didn't target the actual animation. Co-Authored-By: Claude Signed-off-by: Antonio David Gutiérrez * feat(sidecar): allow configuring the resizable button className Add an optional className prop to ResizableButton, and forward it from a new classNameButton option on OverlaySidecarOpenOptions/Sidecar, so callers of overlays.sidecar.open() can style/opt-out the resizer button (e.g. pass "osd-resetFocusState" to drop the global focus-ring animation) without hardcoding it in the component. Co-Authored-By: Claude Signed-off-by: Antonio David Gutiérrez --------- Signed-off-by: Antonio David Gutiérrez Co-authored-by: Claude * Change top bar health-check icon (#1505) * feat: update health check indicator * Change top bar health-check icon to a pulse shown only when attention is needed Co-Authored-By: Claude Opus 4.8 Signed-off-by: Rodrigo Lopez * fix(core): use kebab-case for sidecar docked-mode CSS variable Rename --osdSidecarSize to --osd-sidecar-size and switch attribute selectors to double quotes to satisfy the repo's stylelint rules (custom-property-pattern, string-quotes), registering sidecar.scss in the global selectors allowlist for its .euiFlyout references. Co-Authored-By: Claude Signed-off-by: Antonio David Gutiérrez * chore: use constant * Rework health-check popover * Rework health-check popover * List failing health checks in the nav-button popover Show the enabled non-green checks as an EuiHealth list in the health-check popover, labelled by the check name and with the error surfaced in a tooltip. Use the shared mapTaskStatusToHealthColor helper and TASK.RUN_RESULT constants instead of magic strings. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Rodrigo Lopez * Fix popover closes when clicking inside * Close popover after navigating to the health check --------- Signed-off-by: Rodrigo Lopez Signed-off-by: Antonio David Gutiérrez Co-authored-by: Claude Opus 4.8 Co-authored-by: Antonio David Gutiérrez Co-authored-by: Federico Rodriguez * Bump 5.0.0 branch (#1513) feat: bump 5.0.0 * Fix RPM changelog entry order causing build failure (#1516) Fix RPM changelog entry order for 5.0.0 The 5.0.0 changelog entry was placed out of descending chronological order, causing rpmbuild to fail while parsing %changelog. Signed-off-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 * Merge 4.14.9 into 5.0.0 (#1517) Remove unnecessary debhelper install-time dependency (#1484) fix: remove unnecessary debhelper install-time dependency debhelper is only required to build the .deb package (declared under Build-Depends in dev-tools/build-packages/deb/debian/control), not to install a pre-built one. Remove it from the apt-get install steps used to test package install/upgrade. Signed-off-by: Antonio David Gutiérrez Co-authored-by: Antonio <34042064+Desvelao@users.noreply.github.com> Co-authored-by: Claude --------- Signed-off-by: Antonio David Gutiérrez Signed-off-by: Rodrigo Lopez Signed-off-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Co-authored-by: Federico Rodriguez Co-authored-by: Antonio <34042064+Desvelao@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: Rodrigo López <37187963+rodrigofez@users.noreply.github.com> Co-authored-by: Antonio David Gutiérrez Co-authored-by: Wazuh CI <22834044+wazuhci@users.noreply.github.com> --- .github/workflows/5_bumper_repository.yml | 18 +- CHANGELOG.md | 1 + dev-tools/build-packages/deb/debian/changelog | 2 +- .../build-packages/rpm/wazuh-dashboard.spec | 4 +- .../config/global_selectors.json | 9 +- .../sidecar/components/resizable_button.scss | 10 +- .../sidecar/components/resizable_button.tsx | 15 +- .../overlays/sidecar/components/sidecar.scss | 16 ++ .../overlays/sidecar/components/sidecar.tsx | 30 ++- .../overlays/sidecar/sidecar_service.tsx | 1 + .../health_check_nav_button.test.tsx | 204 ++++++++++++++++++ .../button_header/health_check_nav_button.tsx | 120 +++++++---- 12 files changed, 369 insertions(+), 61 deletions(-) create mode 100644 src/plugins/healthcheck/public/components/button_header/health_check_nav_button.test.tsx diff --git a/.github/workflows/5_bumper_repository.yml b/.github/workflows/5_bumper_repository.yml index 2929153f4735..fc44fc50befc 100644 --- a/.github/workflows/5_bumper_repository.yml +++ b/.github/workflows/5_bumper_repository.yml @@ -187,11 +187,14 @@ jobs: PR_NUMBER=$(gh pr list --head "$BUMP_BRANCH" --base "${{ github.ref_name }}" --state merged --json number --jq '.[0].number') if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" == "null" ]; then - echo "Error: The original PR for the bump was not found" + echo "The original PR for the bump was not found" echo "Searching merged PR from: $BUMP_BRANCH to ${{ github.ref_name }}" - exit 1 + echo "pr_found=false" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + exit 0 fi + echo "pr_found=true" >> $GITHUB_OUTPUT echo "Original PR found: #$PR_NUMBER" MERGE_COMMIT=$(gh pr view $PR_NUMBER --json mergeCommit --jq '.mergeCommit.oid') @@ -244,6 +247,17 @@ jobs: run: | gh pr merge "${{ steps.create_pr.outputs.pull_request_url }}" --squash --admin + - name: 'Result: original bump pull request not found' + if: inputs.revert == true && steps.revert_step.outputs.pr_found == 'false' + run: echo "There is no original bump pull request to revert, nothing to do." + + - name: 'Result: no changes to commit' + if: >- + (inputs.revert != true && steps.bump_changes.outputs.has_changes == 'false') || + (inputs.revert == true && steps.revert_step.outputs.pr_found == 'true' && + steps.revert_step.outputs.has_changes == 'false') + run: echo "The bump produced no changes, no pull request was created." + - name: Show logs if: inputs.revert != true run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7178601b8283..72c6e6b5efda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## Prior versions +- [v4.14.7](https://github.com/wazuh/wazuh-dashboard/blob/v4.14.7/CHANGELOG.md) - [v4.14.6](https://github.com/wazuh/wazuh-dashboard/blob/v4.14.6/CHANGELOG.md) - [v4.14.5](https://github.com/wazuh/wazuh-dashboard/blob/v4.14.5/CHANGELOG.md) - [v4.14.4](https://github.com/wazuh/wazuh-dashboard/blob/v4.14.4/CHANGELOG.md) diff --git a/dev-tools/build-packages/deb/debian/changelog b/dev-tools/build-packages/deb/debian/changelog index 778353e91bfd..7a2ff098bd2f 100644 --- a/dev-tools/build-packages/deb/debian/changelog +++ b/dev-tools/build-packages/deb/debian/changelog @@ -8,7 +8,7 @@ wazuh-dashboard (5.0.0-RELEASE) stable; urgency=low * More info: https://documentation.wazuh.com/current/release-notes/release-5-0-0.html - -- Wazuh, Inc Tue, 04 Aug 2026 12:00:00 +0000 + -- Wazuh, Inc Wed, 09 Sep 2026 12:00:00 +0000 wazuh-dashboard (4.14.9-RELEASE) stable; urgency=low diff --git a/dev-tools/build-packages/rpm/wazuh-dashboard.spec b/dev-tools/build-packages/rpm/wazuh-dashboard.spec index 6814ca25ecb2..8a3e0a6da72f 100644 --- a/dev-tools/build-packages/rpm/wazuh-dashboard.spec +++ b/dev-tools/build-packages/rpm/wazuh-dashboard.spec @@ -483,12 +483,12 @@ rm -fr %{buildroot} - More info: https://documentation.wazuh.com/current/release-notes/release-4-14-9.html * Wed Sep 09 2026 support - 5.0.1 - More info: https://documentation.wazuh.com/current/release-notes/release-5-0-1.html +* Wed Sep 09 2026 support - 5.0.0 +- More info: https://documentation.wazuh.com/current/release-notes/release-5-0-0.html * Thu Sep 03 2026 support - 4.10.5 - More info: https://documentation.wazuh.com/current/release-notes/release-4-10-5.html * Wed Sep 02 2026 support - 4.14.8 - More info: https://documentation.wazuh.com/current/release-notes/release-4-14-8.html -* Tue Aug 04 2026 support - 5.0.0 -- More info: https://documentation.wazuh.com/current/release-notes/release-5-0-0.html * Wed Jul 29 2026 support - 4.14.7 - More info: https://documentation.wazuh.com/current/release-notes/release-4-14-7.html * Wed Jul 01 2026 support - 4.14.6 diff --git a/packages/osd-stylelint-config/config/global_selectors.json b/packages/osd-stylelint-config/config/global_selectors.json index 9cc07f14725a..74680c0c67c3 100644 --- a/packages/osd-stylelint-config/config/global_selectors.json +++ b/packages/osd-stylelint-config/config/global_selectors.json @@ -25,11 +25,16 @@ "src/plugins/data/public/ui/query_string_input/_query_bar.scss", "src/plugins/data/public/ui/query_editor/_query_editor.scss", "src/plugins/data/public/ui/dataset_selector/_dataset_selector.scss", - "src/plugins/explore/public/components/data_table/table_cell/table_source_cell.scss" + "src/plugins/explore/public/components/data_table/table_cell/table_source_cell.scss", + "src/core/public/overlays/sidecar/components/sidecar.scss" ] }, "/\\.explore/": { "explanation": "\"explore\" prefix is preserved for Explore plugin", - "approved": ["src/plugins/explore/", "src/plugins/agent_traces/", "examples/expressions_example/public/index.scss"] + "approved": [ + "src/plugins/explore/", + "src/plugins/agent_traces/", + "examples/expressions_example/public/index.scss" + ] } } diff --git a/src/core/public/overlays/sidecar/components/resizable_button.scss b/src/core/public/overlays/sidecar/components/resizable_button.scss index 2473b49455f6..0485c8cdfbfb 100644 --- a/src/core/public/overlays/sidecar/components/resizable_button.scss +++ b/src/core/public/overlays/sidecar/components/resizable_button.scss @@ -74,8 +74,9 @@ } } - // Add a transparent background to the container and emphasis the "grab" icon with primary color on :focus - &:focus:not(:disabled) { + // Wazuh: changed from :focus to :active (upstream OSD used :focus here) + // Add a transparent background to the container and emphasis the "grab" icon with primary color on :active + &:active:not(:disabled) { background-color: transparentize($euiColorPrimary, 0.9); &::before, @@ -91,9 +92,10 @@ } } - // Morph the "grab" icon into a fluid 2px straight line on :hover and :focus + // Wazuh: changed from :focus to :active (upstream OSD used :focus here) + // Morph the "grab" icon into a fluid 2px straight line on :hover and :active &:hover:not(:disabled), - &:focus:not(:disabled) { + &:active:not(:disabled) { &.resizableButton--horizontal { &::before, &::after { diff --git a/src/core/public/overlays/sidecar/components/resizable_button.tsx b/src/core/public/overlays/sidecar/components/resizable_button.tsx index 9a2d492da701..3a54e3280177 100644 --- a/src/core/public/overlays/sidecar/components/resizable_button.tsx +++ b/src/core/public/overlays/sidecar/components/resizable_button.tsx @@ -14,17 +14,22 @@ interface Props { onResize: (size: number) => void; flyoutSize: number; dockedMode: ISidecarConfig['dockedMode'] | undefined; + className?: string; } const RESIZE_DEBOUNCE_DELAY = 50; -export const ResizableButton = ({ dockedMode, onResize, flyoutSize }: Props) => { +export const ResizableButton = ({ dockedMode, onResize, flyoutSize, className }: Props) => { const isHorizontal = dockedMode !== SIDECAR_DOCKED_MODE.TAKEOVER; - const classes = classNames('sidecar-resizableButton', { - 'resizableButton--vertical': !isHorizontal, - 'resizableButton--horizontal': isHorizontal, - }); + const classes = classNames( + 'sidecar-resizableButton', + { + 'resizableButton--vertical': !isHorizontal, + 'resizableButton--horizontal': isHorizontal, + }, + className + ); const initialMouseXorY = useRef(0); const initialFlyoutSize = useRef(flyoutSize); diff --git a/src/core/public/overlays/sidecar/components/sidecar.scss b/src/core/public/overlays/sidecar/components/sidecar.scss index 7d4a1c65e69d..2e1efb7f69d2 100644 --- a/src/core/public/overlays/sidecar/components/sidecar.scss +++ b/src/core/public/overlays/sidecar/components/sidecar.scss @@ -45,3 +45,19 @@ height: 100%; } } + +// WAZUH +// EuiFlyout is `position: fixed` and portaled to , so it sits outside +// the app-wrapper subtree that the sidecar pushes over via padding (see +// getOsdSidecarPaddingStyle). Left to itself it keeps `right: 0` / `left: 0` +// and ends up underneath the sidecar instead of beside it. `Sidecar` keeps +// these attributes/custom property in sync with its own config while docked +// left or right (not for `--dockedTakeover`, which is meant to cover +// everything), so displace any open flyout by that same amount here. +body[data-osd-sidecar-docked-mode="right"] .euiFlyout:not(.euiFlyout--left) { + right: var(--osd-sidecar-size, 0); +} + +body[data-osd-sidecar-docked-mode="left"] .euiFlyout--left { + left: var(--osd-sidecar-size, 0); +} diff --git a/src/core/public/overlays/sidecar/components/sidecar.tsx b/src/core/public/overlays/sidecar/components/sidecar.tsx index 7eec0f666412..e448243c81d0 100644 --- a/src/core/public/overlays/sidecar/components/sidecar.tsx +++ b/src/core/public/overlays/sidecar/components/sidecar.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject } from 'rxjs'; import classNames from 'classnames'; @@ -57,6 +57,33 @@ export const Sidecar = ({ sidecarConfig$, options, setSidecarConfig, i18n, mount [sidecarConfig] ); + // WAZUH + // Other overlays (e.g. EuiFlyout) are rendered outside this component's DOM + // subtree, so they can't be shifted via a padding/width style the way the + // app-wrapper is. Expose the docked side and size on `document.body` so + // global CSS can displace them instead of letting the sidecar cover them. + useEffect(() => { + const { body } = document; + const isSideDocked = + sidecarConfig && + !sidecarConfig.isHidden && + (sidecarConfig.dockedMode === SIDECAR_DOCKED_MODE.LEFT || + sidecarConfig.dockedMode === SIDECAR_DOCKED_MODE.RIGHT); + + if (isSideDocked) { + body.dataset.osdSidecarDockedMode = sidecarConfig!.dockedMode; + body.style.setProperty('--osd-sidecar-size', `${sidecarConfig!.paddingSize}px`); + } else { + delete body.dataset.osdSidecarDockedMode; + body.style.removeProperty('--osd-sidecar-size'); + } + + return () => { + delete body.dataset.osdSidecarDockedMode; + body.style.removeProperty('--osd-sidecar-size'); + }; + }, [sidecarConfig]); + return (
@@ -64,6 +91,7 @@ export const Sidecar = ({ sidecarConfig$, options, setSidecarConfig, i18n, mount onResize={handleResize} dockedMode={sidecarConfig?.dockedMode} flyoutSize={sidecarConfig?.paddingSize ?? 0} + className={options.classNameButton} />
diff --git a/src/core/public/overlays/sidecar/sidecar_service.tsx b/src/core/public/overlays/sidecar/sidecar_service.tsx index 13410bf2062a..79cba1d9bff7 100644 --- a/src/core/public/overlays/sidecar/sidecar_service.tsx +++ b/src/core/public/overlays/sidecar/sidecar_service.tsx @@ -97,6 +97,7 @@ export interface OverlaySidecarStart { */ export interface OverlaySidecarOpenOptions { className?: string; + classNameButton?: string; 'data-test-subj'?: string; config: ISidecarConfig; } diff --git a/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.test.tsx b/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.test.tsx new file mode 100644 index 000000000000..c4f6713f2ee6 --- /dev/null +++ b/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.test.tsx @@ -0,0 +1,204 @@ +/* + * Copyright Wazuh + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { BehaviorSubject } from 'rxjs'; +import { HealthCheckStatus, TaskInfo } from 'src/core/common/healthcheck'; +import { HealthCheckNavButton, HealthCheckNavButtonProps } from './health_check_nav_button'; +import { setCore } from '../../dashboards_services'; +import { PLUGIN_NAME } from '../../../common'; + +jest.mock('@elastic/eui', () => { + const actual = jest.requireActual('@elastic/eui'); + return { + ...actual, + EuiIcon: ({ type, color }: { type: string; color?: string }) => ( + + ), + }; +}); + +const coreStart = { + uiSettings: { get: jest.fn().mockReturnValue(false) }, + application: { + getUrlForApp: jest.fn().mockReturnValue('/app/healthcheck'), + navigateToUrl: jest.fn(), + // Needed by RedirectAppLinks, which is only mounted once the popover opens. + currentAppId$: new BehaviorSubject('healthcheck'), + }, +} as any; + +const buildCheck = (name: string, result: TaskInfo['result']): TaskInfo => ({ + name, + status: 'finished', + result, + createdAt: null, + startedAt: null, + finishedAt: null, + duration: null, + data: null, + error: `${name} failed`, + enabled: true, + critical: false, +}); + +const buildStatus = ( + status: HealthCheckStatus['status'], + checks: TaskInfo[] = [] +): HealthCheckStatus => ({ + status, + checks, +}); + +const renderButton = (status: HealthCheckStatus['status'], checks: TaskInfo[] = []) => { + const props: HealthCheckNavButtonProps = { + coreStart, + status$: new BehaviorSubject(buildStatus(status, checks)), + fetch: jest.fn().mockResolvedValue(undefined), + getConfig: jest.fn().mockResolvedValue({ interval: 0 }), + }; + return render(); +}; + +describe('HealthCheckNavButton', () => { + beforeEach(() => { + setCore(coreStart); + jest.clearAllMocks(); + }); + + it('does not render when the overall status is green', () => { + const { queryByTestId } = renderButton('green'); + expect(queryByTestId('healthcheck-icon')).toBeNull(); + }); + + it('does not render when the overall status is gray', () => { + const { queryByTestId } = renderButton('gray'); + expect(queryByTestId('healthcheck-icon')).toBeNull(); + }); + + it('renders a pulse icon when the overall status is yellow', () => { + const { getByTestId } = renderButton('yellow'); + const icon = getByTestId('healthcheck-icon'); + expect(icon).toHaveAttribute('data-icon-type', 'pulse'); + expect(icon).toHaveAttribute('data-icon-color', 'warning'); + }); + + it('renders a pulse icon when the overall status is red', () => { + const { getByTestId } = renderButton('red'); + const icon = getByTestId('healthcheck-icon'); + expect(icon).toHaveAttribute('data-icon-type', 'pulse'); + expect(icon).toHaveAttribute('data-icon-color', 'danger'); + }); + + describe('popover', () => { + const checks = [buildCheck('server-api:run-as', 'yellow')]; + + // `aria-expanded` mirrors the open state synchronously. The panel itself stays + // mounted while OuiPopover plays its closing transition, so its presence in the + // DOM is not a reliable signal that the popover is still open. + it('opens the popover when the button is clicked', async () => { + const { getByTestId, queryByRole } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + expect(queryByRole('dialog')).toBeNull(); + + fireEvent.click(trigger); + + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + expect(await waitFor(() => queryByRole('dialog'))).toBeInTheDocument(); + }); + + it('closes the popover when the button is clicked again', async () => { + const { getByTestId, queryByRole } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(trigger); + + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + await waitFor(() => expect(queryByRole('dialog')).toBeNull()); + }); + + // The panel is rendered in a portal, but React events bubble along the React tree, + // so a toggle handler on an ancestor of OuiPopover also receives clicks coming from + // the panel and closes it. The trigger must own the toggle instead. See #1504. + it('keeps the popover open when clicking inside the panel', () => { + const { getByRole, getByTestId, getByText } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByText('run-as')); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByRole('dialog')); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByText('For more details, go to the', { exact: false })); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + }); + + it('closes the popover when the Health Check link is clicked', async () => { + const { getByRole, getByTestId, queryByRole } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByRole('link', { name: PLUGIN_NAME })); + + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + await waitFor(() => expect(queryByRole('dialog')).toBeNull()); + }); + + // A modified click opens the link in a new tab, so the current page — and the + // popover on it — should stay as it is. + it('keeps the popover open when the link is opened in a new tab', () => { + const { getByRole, getByTestId } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByRole('link', { name: PLUGIN_NAME }), { ctrlKey: true }); + + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + }); + }); + + describe('popover when placed in the left nav', () => { + const checks = [buildCheck('server-api:run-as', 'yellow')]; + + beforeEach(() => { + coreStart.uiSettings.get.mockReturnValue(true); + }); + + afterEach(() => { + coreStart.uiSettings.get.mockReturnValue(false); + }); + + it('toggles the popover from the button and ignores clicks inside the panel', () => { + const { getByRole, getByTestId, getByText } = renderButton('yellow', checks); + const trigger = getByTestId('healthcheckNavButton'); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByText('run-as')); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(getByRole('dialog')); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + fireEvent.click(trigger); + expect(trigger).toHaveAttribute('aria-expanded', 'false'); + }); + }); +}); diff --git a/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.tsx b/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.tsx index 50598d3e68d6..b93bd885f43f 100644 --- a/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.tsx +++ b/src/plugins/healthcheck/public/components/button_header/health_check_nav_button.tsx @@ -12,8 +12,11 @@ import { EuiIcon, EuiPopover, EuiToolTip, - EuiText, EuiLink, + EuiHealth, + OuiDescriptionList, + OuiDescriptionListTitle, + EuiHorizontalRule, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject, interval, Subscription } from 'rxjs'; @@ -24,7 +27,7 @@ import { mapTaskStatusToHealthColor } from '../services/health'; import { RedirectAppLinks } from '../../../../opensearch_dashboards_react/public'; import { getCore } from '../../dashboards_services'; import { PLUGIN_ID, PLUGIN_NAME } from '../../../common'; -import { BadgeResults } from '../utils/badge_results'; +import { TASK } from '../../constants'; export interface HealthCheckNavButtonProps { coreStart: CoreStart; @@ -58,6 +61,9 @@ export const HealthCheckNavButton = ({ const isPlacedInLeftNav = coreStart.uiSettings.get('home:useNewHomePage'); + const shouldRenderIndicator = + status === TASK.RUN_RESULT.YELLOW.value || status === TASK.RUN_RESULT.RED.value; + const overallStatusIndicator = ( @@ -88,47 +94,89 @@ export const HealthCheckNavButton = ({ {overallStatusIndicator} ); + const switchPopover = () => setPopoverOpen((prevState) => !prevState); + const innerElement = isPlacedInLeftNav ? ( {button} ) : ( - button + + {button} + ); const contextMenuPanel = ( - -

- - check.enabled)} /> -

- - - + {checks + .filter( + (check) => + check.enabled && + (check.result === TASK.RUN_RESULT.RED.value || + check.result === TASK.RUN_RESULT.YELLOW.value) + ) + .map((check) => { + const [category, name] = check.name.split(':'); + return ( + + + + + {category}: {name} + + + + + ); + })} + + + + { + if (event.defaultPrevented) { + setPopoverOpen(false); + } + }} + > {PLUGIN_NAME} - ), - }} - /> - -
+
+ ), + }} + /> + ); @@ -149,25 +197,9 @@ export const HealthCheckNavButton = ({ ); - const switchPopover = () => setPopoverOpen((prevState) => !prevState); + if (!shouldRenderIndicator) { + return null; + } - return ( - -
{ - if (e.key === 'Enter' || e.key === ' ') switchPopover(); - }} - > - {isPlacedInLeftNav ? ( - popover - ) : ( - {popover} - )} -
-
- ); + return {popover}; };