mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Make Timeline a modal (#170853)
## Summary Issue: https://github.com/elastic/kibana/issues/169021 Changes the Timeline layout to look like a modal. These are only visual changes, all existing behaviors in the Timeline will stay the same. The Timeline styles are custom, `EuiOverlayMask` styles have been applied to the wrapper, and `margin` to the timeline content, to have a modal-like look and feel, as defined. Since the Timeline is always rendered and just "hidden" when closed, using `EuiModal` or `EuiOverlayMask` directly resulted in a more complex implementation for different reasons, especially because they set `overflow: hidden` globally to the document `body` when rendered. This PR does the same thing but only when the timeline is "visible" ### Test 1. Open the Security app (Serverless or ESS) 2. Open Timeline, it should look as in the design specification. 3. All the existing behaviors in the Timeline should keep working the same way. ## Screenshots Serverless: <img width="1717" alt="Captura de pantalla 2023-11-08 a les 13 10 22" src="a6667c29
-f2df-43fc-90f1-716c74468d71"> ESS: <img width="1717" alt="Captura de pantalla 2023-11-08 a les 13 15 06" src="e6973cca
-111f-4715-b053-732207c5f399"> <img width="1717" alt="Captura de pantalla 2023-11-08 a les 13 15 18" src="a2956475
-cca2-43a8-8f83-6672ad68582b"> --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d727eae163
commit
bf054059c8
15 changed files with 344 additions and 132 deletions
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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 { render, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { Pane } from '.';
|
||||
import { useGetUserCasesPermissions } from '../../../../common/lib/kibana';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
const originalKibanaLib = jest.requireActual('../../../../common/lib/kibana');
|
||||
jest.mock('@kbn/i18n-react', () => {
|
||||
const originalModule = jest.requireActual('@kbn/i18n-react');
|
||||
const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago');
|
||||
|
||||
return {
|
||||
...originalModule,
|
||||
FormattedRelative,
|
||||
};
|
||||
});
|
||||
|
||||
// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object
|
||||
// The returned permissions object will indicate that the user does not have permissions by default
|
||||
const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock;
|
||||
mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions);
|
||||
|
||||
jest.mock('../../../../common/utils/normalize_time_range');
|
||||
|
||||
jest.mock('../../../../common/hooks/use_resolve_conflict', () => {
|
||||
return {
|
||||
useResolveConflict: jest.fn().mockImplementation(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/168026
|
||||
describe.skip('Pane', () => {
|
||||
test('renders with display block by default', async () => {
|
||||
const EmptyComponent = render(
|
||||
<TestProviders>
|
||||
<Pane timelineId={TimelineId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: block');
|
||||
});
|
||||
});
|
||||
|
||||
test.skip('renders with display none when visibility is set to false', async () => {
|
||||
const EmptyComponent = render(
|
||||
<TestProviders>
|
||||
<Pane timelineId={TimelineId.test} visible={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(EmptyComponent.getByTestId('timeline-flyout')).toHaveStyle('display: none');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { Pane } from './pane';
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Global } from '@emotion/react';
|
||||
import {
|
||||
useEuiTheme,
|
||||
euiAnimFadeIn,
|
||||
transparentize,
|
||||
euiBackgroundColor,
|
||||
euiCanAnimate,
|
||||
euiAnimSlideInUp,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export const usePaneStyles = () => {
|
||||
const EuiTheme = useEuiTheme();
|
||||
const { euiTheme } = EuiTheme;
|
||||
return css`
|
||||
// euiOverlayMask styles
|
||||
position: fixed;
|
||||
top: var(--euiFixedHeadersOffset, 0);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: ${transparentize(euiTheme.colors.ink, 0.5)};
|
||||
z-index: ${euiTheme.levels.flyout};
|
||||
|
||||
${euiCanAnimate} {
|
||||
animation: ${euiAnimFadeIn} ${euiTheme.animation.fast} ease-in;
|
||||
}
|
||||
|
||||
&.timeline-wrapper--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timeline-flyout {
|
||||
min-width: 150px;
|
||||
height: inherit;
|
||||
position: fixed;
|
||||
top: var(--euiFixedHeadersOffset, 0);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: ${euiBackgroundColor(EuiTheme, 'plain')};
|
||||
${euiCanAnimate} {
|
||||
animation: ${euiAnimSlideInUp(euiTheme.size.xxl)} ${euiTheme.animation.normal}
|
||||
cubic-bezier(0.39, 0.575, 0.565, 1);
|
||||
}
|
||||
|
||||
.timeline-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.timeline-wrapper--full-screen) .timeline-flyout {
|
||||
margin: ${euiTheme.size.m};
|
||||
border-radius: ${euiTheme.border.radius.medium};
|
||||
|
||||
.timeline-template-badge {
|
||||
border-radius: ${euiTheme.border.radius.medium} ${euiTheme.border.radius.medium} 0 0; // top corners only
|
||||
}
|
||||
.timeline-body {
|
||||
padding: 0 ${euiTheme.size.s};
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
export const OverflowHiddenGlobalStyles = () => {
|
||||
return <Global styles={'body { overflow: hidden }'} />;
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { Pane } from '.';
|
||||
|
||||
jest.mock('../../timeline', () => ({
|
||||
StatefulTimeline: () => <div data-test-subj="StatefulTimelineMock" />,
|
||||
}));
|
||||
|
||||
const mockIsFullScreen = jest.fn(() => false);
|
||||
jest.mock('../../../../common/store/selectors', () => ({
|
||||
inputsSelectors: { timelineFullScreenSelector: () => mockIsFullScreen() },
|
||||
}));
|
||||
|
||||
describe('Pane', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render the timeline', async () => {
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<Pane timelineId={TimelineId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.getByTestId('StatefulTimelineMock')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render without fullscreen className', async () => {
|
||||
mockIsFullScreen.mockReturnValue(false);
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<Pane timelineId={TimelineId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.getByTestId('timeline-wrapper')).not.toHaveClass(
|
||||
'timeline-wrapper--full-screen'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with fullscreen className', async () => {
|
||||
mockIsFullScreen.mockReturnValue(true);
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<Pane timelineId={TimelineId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.getByTestId('timeline-wrapper')).toHaveClass('timeline-wrapper--full-screen');
|
||||
});
|
||||
});
|
|
@ -5,16 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { useEuiBackgroundColor, useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { StatefulTimeline } from '../../timeline';
|
||||
import type { TimelineId } from '../../../../../common/types/timeline';
|
||||
import * as i18n from './translations';
|
||||
import { defaultRowRenderers } from '../../timeline/body/renderers';
|
||||
import { DefaultCellRenderer } from '../../timeline/cell_rendering/default_cell_renderer';
|
||||
import { EuiPortal } from './custom_portal';
|
||||
import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { inputsSelectors } from '../../../../common/store/selectors';
|
||||
import { usePaneStyles, OverflowHiddenGlobalStyles } from './pane.styles';
|
||||
|
||||
interface FlyoutPaneComponentProps {
|
||||
timelineId: TimelineId;
|
||||
visible?: boolean;
|
||||
|
@ -24,41 +26,33 @@ const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({
|
|||
timelineId,
|
||||
visible = true,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const timeline = useMemo(
|
||||
() => (
|
||||
<StatefulTimeline
|
||||
renderCellValue={DefaultCellRenderer}
|
||||
rowRenderers={defaultRowRenderers}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
),
|
||||
[timelineId]
|
||||
);
|
||||
const isFullScreen = useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false;
|
||||
|
||||
const styles = usePaneStyles();
|
||||
const wrapperClassName = classNames('timeline-wrapper', styles, {
|
||||
'timeline-wrapper--full-screen': isFullScreen,
|
||||
'timeline-wrapper--hidden': !visible,
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-test-subj="flyout-pane" ref={ref}>
|
||||
<EuiPortal insert={{ sibling: !visible ? ref?.current : null, position: 'after' }}>
|
||||
<div
|
||||
aria-label={i18n.TIMELINE_DESCRIPTION}
|
||||
data-test-subj="timeline-flyout"
|
||||
css={css`
|
||||
min-width: 150px;
|
||||
height: inherit;
|
||||
bottom: 0;
|
||||
top: var(--euiFixedHeadersOffset, 0);
|
||||
left: 0;
|
||||
background: ${useEuiBackgroundColor('plain')};
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: ${euiTheme.levels.flyout};
|
||||
display: ${visible ? 'block' : 'none'};
|
||||
`}
|
||||
>
|
||||
{timeline}
|
||||
<div data-test-subj="timeline-wrapper" className={wrapperClassName}>
|
||||
<div
|
||||
aria-label={i18n.TIMELINE_DESCRIPTION}
|
||||
data-test-subj="timeline-flyout"
|
||||
className="timeline-flyout"
|
||||
>
|
||||
<StatefulTimeline
|
||||
renderCellValue={DefaultCellRenderer}
|
||||
rowRenderers={defaultRowRenderers}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</EuiPortal>
|
||||
{visible && <OverflowHiddenGlobalStyles />}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -193,27 +193,31 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
|||
>
|
||||
<TimelineSavingProgress timelineId={timelineId} />
|
||||
{timelineType === TimelineType.template && (
|
||||
<TimelineTemplateBadge>{i18n.TIMELINE_TEMPLATE}</TimelineTemplateBadge>
|
||||
<TimelineTemplateBadge className="timeline-template-badge">
|
||||
{i18n.TIMELINE_TEMPLATE}
|
||||
</TimelineTemplateBadge>
|
||||
)}
|
||||
{resolveConflictComponent}
|
||||
<HideShowContainer
|
||||
$isVisible={!timelineFullScreen}
|
||||
data-test-subj="timeline-hide-show-container"
|
||||
>
|
||||
<FlyoutHeaderPanel timelineId={timelineId} />
|
||||
<FlyoutHeader timelineId={timelineId} />
|
||||
</HideShowContainer>
|
||||
<div className="timeline-body" data-test-subj="timeline-body">
|
||||
{resolveConflictComponent}
|
||||
<HideShowContainer
|
||||
$isVisible={!timelineFullScreen}
|
||||
data-test-subj="timeline-hide-show-container"
|
||||
>
|
||||
<FlyoutHeaderPanel timelineId={timelineId} />
|
||||
<FlyoutHeader timelineId={timelineId} />
|
||||
</HideShowContainer>
|
||||
|
||||
<TabsContent
|
||||
graphEventId={graphEventId}
|
||||
sessionViewConfig={sessionViewConfig}
|
||||
renderCellValue={renderCellValue}
|
||||
rowRenderers={rowRenderers}
|
||||
timelineId={timelineId}
|
||||
timelineType={timelineType}
|
||||
timelineDescription={description}
|
||||
timelineFullScreen={timelineFullScreen}
|
||||
/>
|
||||
<TabsContent
|
||||
graphEventId={graphEventId}
|
||||
sessionViewConfig={sessionViewConfig}
|
||||
renderCellValue={renderCellValue}
|
||||
rowRenderers={rowRenderers}
|
||||
timelineId={timelineId}
|
||||
timelineType={timelineType}
|
||||
timelineDescription={description}
|
||||
timelineFullScreen={timelineFullScreen}
|
||||
/>
|
||||
</div>
|
||||
</TimelineContainer>
|
||||
</TimelineContext.Provider>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ import { kqlSearch } from '../../../tasks/security_header';
|
|||
|
||||
import { hostsUrl } from '../../../urls/navigation';
|
||||
import { resetFields } from '../../../tasks/timeline';
|
||||
import { DATA_GRID_EMPTY_STATE } from '../../../screens/events_viewer';
|
||||
|
||||
const defaultHeadersInDefaultEcsCategory = [
|
||||
{ id: '@timestamp' },
|
||||
|
@ -118,12 +119,10 @@ describe('Events Viewer', { tags: ['@ess', '@serverless'] }, () => {
|
|||
|
||||
it('filters the events by applying filter criteria from the search bar at the top of the page', () => {
|
||||
const filterInput = 'aa7ca589f1b8220002f2fc61c64cfbf1'; // this will never match real data
|
||||
cy.get(SERVER_SIDE_EVENT_COUNT)
|
||||
.invoke('text')
|
||||
.then((initialNumberOfEvents) => {
|
||||
kqlSearch(`${filterInput}{enter}`);
|
||||
cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', initialNumberOfEvents);
|
||||
});
|
||||
cy.get(SERVER_SIDE_EVENT_COUNT).should('exist');
|
||||
kqlSearch(`${filterInput}{enter}`);
|
||||
cy.get(SERVER_SIDE_EVENT_COUNT).should('not.exist');
|
||||
cy.get(DATA_GRID_EMPTY_STATE).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,16 +21,21 @@ import {
|
|||
import { login } from '../../../tasks/login';
|
||||
import { visitWithTimeRange } from '../../../tasks/navigation';
|
||||
import { closeTimelineUsingToggle } from '../../../tasks/security_main';
|
||||
import {
|
||||
navigateToHostsUsingBreadcrumb,
|
||||
navigateToExploreUsingBreadcrumb,
|
||||
navigateToAlertsPageInServerless,
|
||||
navigateToDiscoverPageInServerless,
|
||||
navigateToExplorePageInServerless,
|
||||
} from '../../../tasks/serverless/navigation';
|
||||
import {
|
||||
addNameToTimelineAndSave,
|
||||
createNewTimeline,
|
||||
populateTimeline,
|
||||
} from '../../../tasks/timeline';
|
||||
import { hostsUrl, MANAGE_URL } from '../../../urls/navigation';
|
||||
import { EXPLORE_URL, hostsUrl, MANAGE_URL } from '../../../urls/navigation';
|
||||
|
||||
// https://github.com/elastic/kibana/issues/169021
|
||||
|
||||
describe('Save Timeline Prompts', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
|
||||
describe('Save Timeline Prompts', { tags: ['@ess'] }, () => {
|
||||
before(() => {
|
||||
login();
|
||||
/*
|
||||
|
@ -133,3 +138,90 @@ describe('Save Timeline Prompts', { tags: ['@ess', '@serverless', '@brokenInServ
|
|||
cy.url().should('not.contain', MANAGE_URL);
|
||||
});
|
||||
});
|
||||
|
||||
// In serverless it is not possible to use the navigation menu without closing the timeline
|
||||
describe('Save Timeline Prompts', { tags: ['@serverless'] }, () => {
|
||||
before(() => {
|
||||
login();
|
||||
/*
|
||||
* When timeline changes are pending, chrome would popup with
|
||||
* a confirm dialog stating that `you can lose unsaved changed.
|
||||
* Below changes will disable that.
|
||||
*
|
||||
* */
|
||||
cy.window().then((win) => {
|
||||
win.onbeforeunload = null;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
visitWithTimeRange(hostsUrl('allHosts'));
|
||||
createNewTimeline();
|
||||
});
|
||||
|
||||
it('unchanged & unsaved timeline should NOT prompt when it is closed and navigate to any page', () => {
|
||||
closeTimelineUsingToggle();
|
||||
|
||||
navigateToAlertsPageInServerless(); // security page with timelines enabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
navigateToExplorePageInServerless(); // security page with timelines disabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
navigateToDiscoverPageInServerless(); // external page
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
});
|
||||
|
||||
it('Changed & unsaved timeline should prompt when it is closed and navigate to Security page without timeline', () => {
|
||||
populateTimeline();
|
||||
closeTimelineUsingToggle();
|
||||
|
||||
navigateToAlertsPageInServerless(); // security page with timelines enabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
navigateToExplorePageInServerless(); // security page with timelines disabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('be.visible');
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
});
|
||||
|
||||
it('Changed & unsaved timeline should prompt when it is closed and navigate to external page', () => {
|
||||
populateTimeline();
|
||||
closeTimelineUsingToggle();
|
||||
|
||||
navigateToDiscoverPageInServerless();
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('be.visible');
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
});
|
||||
|
||||
it('Changed & saved timeline should NOT prompt when it is closed', () => {
|
||||
populateTimeline();
|
||||
addNameToTimelineAndSave('Test');
|
||||
closeTimelineUsingToggle();
|
||||
|
||||
navigateToAlertsPageInServerless(); // security page with timelines enabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
navigateToExplorePageInServerless(); // security page with timelines disabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
navigateToDiscoverPageInServerless(); // external page
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
});
|
||||
|
||||
it('Changed & unsaved timeline should NOT prompt when navigate to page with timeline using breadcrumbs', () => {
|
||||
populateTimeline();
|
||||
navigateToHostsUsingBreadcrumb(); // hosts has timelines enabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
});
|
||||
|
||||
it('Changed & unsaved timeline should NOT prompt when navigate to page without timeline using breadcrumbs', () => {
|
||||
populateTimeline();
|
||||
navigateToExploreUsingBreadcrumb(); // explore has timelines disabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('be.visible');
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
cy.url().should('contain', EXPLORE_URL);
|
||||
});
|
||||
|
||||
it('Changed & saved timeline should NOT prompt when user navigates within security solution where timelines are disabled', () => {
|
||||
populateTimeline();
|
||||
addNameToTimelineAndSave('Test');
|
||||
navigateToExploreUsingBreadcrumb(); // explore has timelines disabled
|
||||
cy.get(APP_LEAVE_CONFIRM_MODAL).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,7 +50,6 @@ import { hostsUrl } from '../../urls/navigation';
|
|||
import { ABSOLUTE_DATE_RANGE } from '../../urls/state';
|
||||
|
||||
import { getTimeline } from '../../objects/timeline';
|
||||
import { TIMELINE } from '../../screens/create_new_case';
|
||||
import {
|
||||
GLOBAL_SEARCH_BAR_FILTER_ITEM_AT,
|
||||
GLOBAL_SEARCH_BAR_PINNED_FILTER,
|
||||
|
@ -308,7 +307,6 @@ describe('url state', { tags: ['@ess', '@brokenInServerless'] }, () => {
|
|||
visitWithTimeRange(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`);
|
||||
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('exist');
|
||||
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('not.have.text', 'Updating');
|
||||
cy.get(TIMELINE).should('be.visible');
|
||||
cy.get(TIMELINE_TITLE).should('be.visible');
|
||||
cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
|
||||
});
|
||||
|
|
|
@ -21,6 +21,6 @@ export const SUBMIT_BTN = '[data-test-subj="create-case-submit"]';
|
|||
|
||||
export const TAGS_INPUT = '[data-test-subj="caseTags"] [data-test-subj="comboBoxSearchInput"]';
|
||||
|
||||
export const TIMELINE = '[data-test-subj="timeline"]';
|
||||
export const TIMELINE = '[data-test-subj="selectable-input"] [data-test-subj="timeline"]';
|
||||
|
||||
export const TITLE_INPUT = '[data-test-subj="caseTitle"] [data-test-subj="input"]';
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const DATA_GRID_EMPTY_STATE = '[data-test-subj="tGridEmptyState"]';
|
|
@ -25,7 +25,8 @@ export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]';
|
|||
export const INSPECT_QUERY =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="inspect-icon-button"]';
|
||||
|
||||
export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]';
|
||||
export const SERVER_SIDE_EVENT_COUNT =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="server-side-event-count"]';
|
||||
|
||||
export const EVENT_VIEWER_CHECKBOX =
|
||||
'[data-test-subj="dataGridHeaderCell-checkbox-control-column"]';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const EXPLORE_BREADCRUMB =
|
||||
'[data-test-subj*="breadcrumb-deepLinkId-securitySolutionUI:explore"]';
|
||||
export const HOSTS_BREADCRUMB =
|
||||
'[data-test-subj*="breadcrumb-deepLinkId-securitySolutionUI:hosts"]';
|
|
@ -80,7 +80,7 @@ export const attachTimeline = (newCase: TestCase) => {
|
|||
},
|
||||
{ interval: 500, timeout: 12000 }
|
||||
);
|
||||
cy.get(TIMELINE).eq(1).click();
|
||||
cy.get(TIMELINE).first().click();
|
||||
};
|
||||
|
||||
export const createCase = () => {
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERTS } from '../../screens/serverless_security_header';
|
||||
import { ALERTS, DISCOVER, EXPLORE } from '../../screens/serverless_security_header';
|
||||
import {
|
||||
EXPLORE_BREADCRUMB,
|
||||
HOSTS_BREADCRUMB,
|
||||
} from '../../screens/serverless_security_breadcrumbs';
|
||||
|
||||
const navigateTo = (page: string) => {
|
||||
cy.get(page).click();
|
||||
|
@ -14,3 +18,19 @@ const navigateTo = (page: string) => {
|
|||
export const navigateToAlertsPageInServerless = () => {
|
||||
navigateTo(ALERTS);
|
||||
};
|
||||
|
||||
export const navigateToDiscoverPageInServerless = () => {
|
||||
navigateTo(DISCOVER);
|
||||
};
|
||||
|
||||
export const navigateToExplorePageInServerless = () => {
|
||||
navigateTo(EXPLORE);
|
||||
};
|
||||
|
||||
export const navigateToHostsUsingBreadcrumb = () => {
|
||||
cy.get(HOSTS_BREADCRUMB).click();
|
||||
};
|
||||
|
||||
export const navigateToExploreUsingBreadcrumb = () => {
|
||||
cy.get(EXPLORE_BREADCRUMB).click();
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue