From df855bad168fb07eedfe9d6ac8a612d349a360be Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 24 Jan 2024 13:22:34 -0600 Subject: [PATCH 1/4] [Console] disable access to embedded console without dev tools capability (#175321) ## Summary Added a check for the dev tools capability in kibana before making the render of the embedded console available to other plugins. --- src/plugins/console/public/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts index 0ad80014fce04..ea4ddc7099fc4 100644 --- a/src/plugins/console/public/plugin.ts +++ b/src/plugins/console/public/plugin.ts @@ -105,7 +105,8 @@ export class ConsoleUIPlugin implements Plugin(); - if (isConsoleUiEnabled) { + + if (isConsoleUiEnabled && core.application.capabilities?.dev_tools?.show === true) { return { renderEmbeddableConsole: (props: EmbeddableConsoleProps) => { const consoleDeps: EmbeddableConsoleDependencies = { From 98cb0c2eaac63db92d8624ed98e9fe423c935d2d Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 24 Jan 2024 13:22:56 -0600 Subject: [PATCH 2/4] [Embedded Console] Introduce kbnSolutionNavOffset CSS variable (#175348) ## Summary This PR updates the Kibana `SolutionNav` component to set a global CSS variable `--kbnSolutionNavOffset` with it's current width. This is modeled off of the EUI CSS variable `--euiCollapsibleNavOffset` that can be used in Serverless to get the current width of the side nav. This will be used by the embedded dev console to ensure it is not rendered over the page side navigation, at least for pages that use the `KibanaPageTemplate` & `SolutionNav`. ### Testing The easiest way to verify this change is to check the values of `--kbnSolutionNavOffset` in the browser console, which can be done with the following command: `getComputedStyle(document.documentElement).getPropertyValue('--kbnSolutionNavOffset')` You can check this with the solution nav open and closed or on pages without a solution nav at all (`/app/home` for example) --- packages/kbn-optimizer/limits.yml | 2 +- .../page/solution_nav/src/_variables.scss | 4 +++ .../solution_nav/src/collapse_button.scss | 8 +++-- .../page/solution_nav/src/solution_nav.scss | 3 +- .../page/solution_nav/src/solution_nav.tsx | 31 ++++++++++++++++++- .../embeddable/_embeddable_console.scss | 13 +++++++- .../embeddable/embeddable_console.tsx | 5 +++ 7 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 packages/shared-ux/page/solution_nav/src/_variables.scss diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 35714e5e33ad2..e9f0ac0f7ff37 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -32,7 +32,7 @@ pageLoadAssetSize: datasetQuality: 50624 dataViewEditor: 28082 dataViewFieldEditor: 27000 - dataViewManagement: 5136 + dataViewManagement: 5176 dataViews: 51000 dataVisualizer: 27530 devTools: 38637 diff --git a/packages/shared-ux/page/solution_nav/src/_variables.scss b/packages/shared-ux/page/solution_nav/src/_variables.scss new file mode 100644 index 0000000000000..ac69606f7cc93 --- /dev/null +++ b/packages/shared-ux/page/solution_nav/src/_variables.scss @@ -0,0 +1,4 @@ +// This size is also tracked with a variable in solution_nav.tsx, if updated +// update there as well +$solutionNavWidth: 248px; +$solutionNavCollapsedWidth: $euiSizeXXL; diff --git a/packages/shared-ux/page/solution_nav/src/collapse_button.scss b/packages/shared-ux/page/solution_nav/src/collapse_button.scss index fa6f0864e71d6..a1fe7e5ed55a4 100644 --- a/packages/shared-ux/page/solution_nav/src/collapse_button.scss +++ b/packages/shared-ux/page/solution_nav/src/collapse_button.scss @@ -1,7 +1,9 @@ +@import 'variables'; + .kbnSolutionNavCollapseButton { position: absolute; opacity: 0; - left: 248px - $euiSize; + left: $solutionNavWidth - $euiSize; top: $euiSizeL; z-index: 2; @@ -18,7 +20,7 @@ &:hover, &:focus { opacity: 1; - left: 248px - $euiSizeL; + left: $solutionNavWidth - $euiSizeL; } .kbnSolutionNav__sidebar:hover & { @@ -39,7 +41,7 @@ top: 0; bottom: 0; height: 100%; - width: $euiSizeXXL; + width: $solutionNavCollapsedWidth; border-radius: 0; // Keep the icon at the top instead of it getting shifted to the center of the page padding-top: $euiSizeL + $euiSizeS; diff --git a/packages/shared-ux/page/solution_nav/src/solution_nav.scss b/packages/shared-ux/page/solution_nav/src/solution_nav.scss index 28cef100e174a..9db81e1b1a062 100644 --- a/packages/shared-ux/page/solution_nav/src/solution_nav.scss +++ b/packages/shared-ux/page/solution_nav/src/solution_nav.scss @@ -1,5 +1,6 @@ $euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7); @import '@elastic/eui/src/components/side_nav/mixins'; +@import 'variables'; // Put the page background color in the flyout version too .kbnSolutionNav__flyout { @@ -14,7 +15,7 @@ $euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7); flex-direction: column; @include euiBreakpoint('m', 'l', 'xl') { - width: 248px; + width: $solutionNavWidth; padding: $euiSizeL; } diff --git a/packages/shared-ux/page/solution_nav/src/solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/solution_nav.tsx index f88f98afa349a..000176eb371b1 100644 --- a/packages/shared-ux/page/solution_nav/src/solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/solution_nav.tsx @@ -7,7 +7,7 @@ */ import './solution_nav.scss'; -import React, { FC, useState, useMemo } from 'react'; +import React, { FC, useState, useMemo, useEffect } from 'react'; import classNames from 'classnames'; import { EuiAvatarProps, @@ -23,6 +23,8 @@ import { htmlIdGenerator, useIsWithinBreakpoints, useIsWithinMinBreakpoint, + useEuiTheme, + useEuiThemeCSSVariables, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -71,6 +73,7 @@ export type SolutionNavProps = Omit, 'children' | 'items' | }; const FLYOUT_SIZE = 248; +const FLYOUT_SIZE_CSS = `${FLYOUT_SIZE}px`; const setTabIndex = (items: Array>, isHidden: boolean) => { return items.map((item) => { @@ -174,6 +177,32 @@ export const SolutionNav: FC = ({ ); }, [children, headingID, isCustomSideNav, isHidden, items, rest]); + const { euiTheme } = useEuiTheme(); + const navWidth = useMemo(() => { + if (isLargerBreakpoint) { + return isOpenOnDesktop ? FLYOUT_SIZE_CSS : euiTheme.size.xxl; + } + if (isMediumBreakpoint) { + return isSideNavOpenOnMobile || !canBeCollapsed ? FLYOUT_SIZE_CSS : euiTheme.size.xxl; + } + return '0'; + }, [ + euiTheme, + isOpenOnDesktop, + isSideNavOpenOnMobile, + canBeCollapsed, + isMediumBreakpoint, + isLargerBreakpoint, + ]); + const { setGlobalCSSVariables } = useEuiThemeCSSVariables(); + // Setting a global CSS variable with the nav width + // so that other pages have it available when needed. + useEffect(() => { + setGlobalCSSVariables({ + '--kbnSolutionNavOffset': navWidth, + }); + }, [navWidth, setGlobalCSSVariables]); + return ( <> {isSmallerBreakpoint && ( diff --git a/src/plugins/console/public/application/containers/embeddable/_embeddable_console.scss b/src/plugins/console/public/application/containers/embeddable/_embeddable_console.scss index 206ab6459c05a..9c8fddd65b387 100644 --- a/src/plugins/console/public/application/containers/embeddable/_embeddable_console.scss +++ b/src/plugins/console/public/application/containers/embeddable/_embeddable_console.scss @@ -8,7 +8,6 @@ box-shadow: inset 0 $embeddableConsoleInitialHeight 0 $embeddableConsoleBackground, inset 0 600rem 0 $euiPageBackgroundColor; bottom: 0; right: 0; - left: var(--euiCollapsibleNavOffset, 0); transform: translateY(0); height: $embeddableConsoleInitialHeight; max-height: $embeddableConsoleMaxHeight; @@ -18,6 +17,18 @@ z-index: $euiZLevel1; } + &--projectChrome { + left: var(--euiCollapsibleNavOffset, 0); + } + + &--classicChrome { + left: var(--kbnSolutionNavOffset, 0); + } + + &--unknownChrome { + left: 0; + } + &-isOpen { animation-duration: $euiAnimSpeedNormal; animation-timing-function: $euiAnimSlightResistance; diff --git a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx index 2ec249b1eb3ee..2577c9d4841d7 100644 --- a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx +++ b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx @@ -8,6 +8,7 @@ import React, { useState } from 'react'; import classNames from 'classnames'; +import useObservable from 'react-use/lib/useObservable'; import { EuiButton, EuiFocusTrap, @@ -38,6 +39,7 @@ export const EmbeddableConsole = ({ }: EmbeddableConsoleProps & EmbeddableConsoleDependencies) => { const [isConsoleOpen, setIsConsoleOpen] = useState(false); const toggleConsole = () => setIsConsoleOpen(!isConsoleOpen); + const chromeStyle = useObservable(core.chrome.getChromeStyle$()); const onKeyDown = (event: any) => { if (event.key === keys.ESCAPE) { @@ -52,6 +54,9 @@ export const EmbeddableConsole = ({ 'embeddableConsole--large': size === 'l', 'embeddableConsole--medium': size === 'm', 'embeddableConsole--small': size === 's', + 'embeddableConsole--classicChrome': chromeStyle === 'classic', + 'embeddableConsole--projectChrome': chromeStyle === 'project', + 'embeddableConsole--unknownChrome': chromeStyle === undefined, 'embeddableConsole--fixed': true, 'embeddableConsole--showOnMobile': false, }); From 5d254068ace8e983a2dc360a73847f35c21feba6 Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Wed, 24 Jan 2024 13:30:19 -0600 Subject: [PATCH 3/4] [Security Solution][Timeline] refactor timeline modal open timeline button (#175335) --- .../components/flyout/action_menu/index.tsx | 4 +- .../flyout/action_menu/translations.ts | 50 ------------ .../actions/open_timeline_button.test.tsx | 80 +++++++++++++++++++ .../actions/open_timeline_button.tsx} | 18 ++--- .../components/modal/actions/translations.ts | 22 +++++ .../cypress/screens/timeline.ts | 2 +- 6 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.test.tsx rename x-pack/plugins/security_solution/public/timelines/components/{flyout/action_menu/open_timeline.tsx => modal/actions/open_timeline_button.tsx} (64%) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/index.tsx index 2208b6ee706b8..b1be7562fcf97 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/index.tsx @@ -16,7 +16,7 @@ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { AddToCaseButton } from '../add_to_case_button'; import { NewTimelineAction } from './new_timeline'; import { SaveTimelineButton } from './save_timeline_button'; -import { OpenTimelineAction } from './open_timeline'; +import { OpenTimelineButton } from '../../modal/actions/open_timeline_button'; import { TIMELINE_TOUR_CONFIG_ANCHORS } from '../../timeline/tour/step_config'; interface TimelineActionMenuProps { @@ -54,7 +54,7 @@ const TimelineActionMenuComponent = ({ - + { + const origin = jest.requireActual('react-redux'); + return { + ...origin, + useDispatch: jest.fn(), + }; +}); +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { + ...actual, + useParams: jest.fn(), + }; +}); +jest.mock('../../../../common/lib/kibana', () => { + const actual = jest.requireActual('../../../../common/lib/kibana'); + return { + ...actual, + useNavigation: () => ({ + navigateTo: jest.fn(), + }), + }; +}); + +const renderOpenTimelineButton = () => + render( + + + + ); + +describe('OpenTimelineButton', () => { + it('should render the button', () => { + const { getByTestId, queryByTestId } = renderOpenTimelineButton(); + + expect(getByTestId('timeline-modal-open-timeline-button')).toBeInTheDocument(); + expect(getByTestId('timeline-modal-open-timeline-button')).toHaveTextContent('Open'); + + expect(queryByTestId('open-timeline-modal')).not.toBeInTheDocument(); + }); + + it('should open the modal after clicking on the button', async () => { + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.template }); + (useStartTransaction as jest.Mock).mockReturnValue({ startTransaction: jest.fn() }); + (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: '', selectedPatterns: [] }); + (useTimelineStatus as jest.Mock).mockReturnValue({ + timelineStatus: 'active', + templateTimelineFilter: null, + installPrepackagedTimelines: jest.fn(), + }); + + const { getByTestId } = renderOpenTimelineButton(); + + getByTestId('timeline-modal-open-timeline-button').click(); + + await waitFor(() => { + expect(getByTestId('open-timeline-modal')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.tsx similarity index 64% rename from x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/open_timeline.tsx rename to x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.tsx index f5a7a51dc75e8..f5cf939791944 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/action_menu/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.tsx @@ -13,28 +13,28 @@ import * as i18n from './translations'; const actionTimelineToHide: ActionTimelineToShow[] = ['createFrom']; -export const OpenTimelineAction = React.memo(() => { +/** + * Renders a button that opens the `OpenTimelineModal` to allow users to select a saved timeline to open + */ +export const OpenTimelineButton = React.memo(() => { const [showTimelineModal, setShowTimelineModal] = useState(false); - const onCloseTimelineModal = useCallback(() => setShowTimelineModal(false), []); - const onOpenTimelineModal = useCallback(() => { - setShowTimelineModal(true); - }, []); + const toggleTimelineModal = useCallback(() => setShowTimelineModal((prev) => !prev), []); return ( <> {i18n.OPEN_TIMELINE_BTN} {showTimelineModal ? ( - + ) : null} ); }); -OpenTimelineAction.displayName = 'OpenTimelineAction'; +OpenTimelineButton.displayName = 'OpenTimelineButton'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts new file mode 100644 index 0000000000000..364e7eeb8c9a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const OPEN_TIMELINE_BTN = i18n.translate( + 'xpack.securitySolution.timeline.modal.openTimelineBtn', + { + defaultMessage: 'Open', + } +); + +export const OPEN_TIMELINE_BTN_LABEL = i18n.translate( + 'xpack.securitySolution.timeline.modal.openTimelineBtnLabel', + { + defaultMessage: 'Open Existing Timeline', + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts index 733c7eaa338ff..fb2859842842b 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts @@ -81,7 +81,7 @@ export const DELETE_NOTE = '[data-test-subj="delete-note"]'; export const MARKDOWN_INVESTIGATE_BUTTON = '[data-test-subj="insight-investigate-in-timeline-button"]'; -export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]'; +export const OPEN_TIMELINE_ICON = '[data-test-subj="timeline-modal-open-timeline-button"]'; export const OPEN_TIMELINE_MODAL = '[data-test-subj="open-timeline-modal"]'; From ee3934b7b7a2e8553da8b19ad7e7a1f62a1c281d Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 24 Jan 2024 19:49:55 +0000 Subject: [PATCH 4/4] skip flaky suite (#175407) --- .../custom_threshold_rule/documents_count_fired.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts index 9025cb80ff47d..133ced9b239d1 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -96,7 +96,8 @@ export default function ({ getService }: FtrProviderContext) { await cleanup({ client: esClient, config: dataForgeConfig, logger }); }); - describe('Rule creation', () => { + // FLAKY: https://github.com/elastic/kibana/issues/175407 + describe.skip('Rule creation', () => { it('creates rule successfully', async () => { actionId = await createIndexConnector({ supertest,