Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Data ingestion hub header cards #190696

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
65c4cf0
data ingestion hub header cards + links
agusruidiazgd Aug 19, 2024
eb8ef05
cards + video
agusruidiazgd Sep 2, 2024
71f58d3
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 2, 2024
dba8cef
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 3, 2024
c9ece05
fixing build
agusruidiazgd Sep 3, 2024
bc0741c
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 4, 2024
3f89d8e
change userSettingsUrl for usersUrl observable
agusruidiazgd Sep 4, 2024
45da00d
pr comments
agusruidiazgd Sep 5, 2024
2e17e3c
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 6, 2024
caab95d
pr comments + feedback
agusruidiazgd Sep 6, 2024
5140641
remove video card
agusruidiazgd Sep 9, 2024
5ec80b0
Merge branch 'main' into feat/data-ingestion-hub-header-cards
elasticmachine Sep 9, 2024
eba2501
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 9, 2024
a1074e0
fix tests
agusruidiazgd Sep 9, 2024
485a83b
fix FTR Test
agusruidiazgd Sep 9, 2024
510a8c4
fixing the isOnboardingVisited storage key and tests added
agusruidiazgd Sep 9, 2024
2f5db37
pr comments
agusruidiazgd Sep 10, 2024
2ff630e
Update x-pack/plugins/security_solution/public/common/components/land…
agusruidiazgd Sep 10, 2024
b100d3a
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 10, 2024
639f044
pr styles changes
agusruidiazgd Sep 10, 2024
8110f9e
Merge branch 'main' into feat/data-ingestion-hub-header-cards
agusruidiazgd Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,26 @@ import type { StepId } from '../../../common/components/landing_page/onboarding/
export class OnboardingPageService {
private productTypesSubject$: BehaviorSubject<SecurityProductTypes | undefined>;
private projectsUrlSubject$: BehaviorSubject<string | undefined>;
private usersUrlSubject$: BehaviorSubject<string | undefined>;
private projectFeaturesUrlSubject$: BehaviorSubject<string | undefined>;
private availableStepsSubject$: BehaviorSubject<StepId[]>;

public productTypes$: Observable<SecurityProductTypes | undefined>;
public projectsUrl$: Observable<string | undefined>;
public usersUrl$: Observable<string | undefined>;
public projectFeaturesUrl$: Observable<string | undefined>;
public availableSteps$: Observable<StepId[]>;

constructor() {
this.productTypesSubject$ = new BehaviorSubject<SecurityProductTypes | undefined>(undefined);
this.projectsUrlSubject$ = new BehaviorSubject<string | undefined>(undefined);
this.usersUrlSubject$ = new BehaviorSubject<string | undefined>(undefined);
this.projectFeaturesUrlSubject$ = new BehaviorSubject<string | undefined>(undefined);
this.availableStepsSubject$ = new BehaviorSubject<StepId[]>([]);

this.productTypes$ = this.productTypesSubject$.asObservable();
this.projectsUrl$ = this.projectsUrlSubject$.asObservable();
this.usersUrl$ = this.usersUrlSubject$.asObservable();
this.projectFeaturesUrl$ = this.projectFeaturesUrlSubject$.asObservable();
this.availableSteps$ = this.availableStepsSubject$.asObservable();
}
Expand All @@ -39,6 +43,9 @@ export class OnboardingPageService {
setProjectFeaturesUrl(projectFeaturesUrl: string | undefined) {
this.projectFeaturesUrlSubject$.next(projectFeaturesUrl);
}
setUsersUrl(userUrl: string | undefined) {
this.usersUrlSubject$.next(userUrl);
}
setProjectsUrl(projectsUrl: string | undefined) {
this.projectsUrlSubject$.next(projectsUrl);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { render } from '@testing-library/react';
import { CardItem } from './card_item';
import type { ExpandedCardSteps, StepId } from './types';

import { QuickStartSectionCardsId, SectionId, OverviewSteps } from './types';
import { SectionId, ViewDashboardSteps, AddAndValidateYourDataCardsId } from './types';
jest.mock('./card_step');

describe('CardItemComponent', () => {
const finishedSteps = new Set([]) as Set<StepId>;
const onStepClicked = jest.fn();
const toggleTaskCompleteStatus = jest.fn();
const expandedCardSteps = {
[QuickStartSectionCardsId.watchTheOverviewVideo]: {
[AddAndValidateYourDataCardsId.viewDashboards]: {
isExpanded: false,
expandedSteps: [] as StepId[],
},
Expand All @@ -26,30 +26,30 @@ describe('CardItemComponent', () => {
it('should render card', () => {
const { getByTestId } = render(
<CardItem
activeStepIds={[OverviewSteps.getToKnowElasticSecurity]}
cardId={QuickStartSectionCardsId.watchTheOverviewVideo}
activeStepIds={[ViewDashboardSteps.analyzeData]}
cardId={AddAndValidateYourDataCardsId.viewDashboards}
expandedCardSteps={expandedCardSteps}
finishedSteps={finishedSteps}
toggleTaskCompleteStatus={toggleTaskCompleteStatus}
onStepClicked={onStepClicked}
sectionId={SectionId.quickStart}
sectionId={SectionId.addAndValidateYourData}
/>
);

const cardTitle = getByTestId(QuickStartSectionCardsId.watchTheOverviewVideo);
const cardTitle = getByTestId(AddAndValidateYourDataCardsId.viewDashboards);
expect(cardTitle).toBeInTheDocument();
});

it('should not render card when no active steps', () => {
const { queryByText } = render(
<CardItem
activeStepIds={[]}
cardId={QuickStartSectionCardsId.watchTheOverviewVideo}
cardId={AddAndValidateYourDataCardsId.viewDashboards}
expandedCardSteps={expandedCardSteps}
finishedSteps={new Set([])}
toggleTaskCompleteStatus={toggleTaskCompleteStatus}
onStepClicked={onStepClicked}
sectionId={SectionId.quickStart}
sectionId={SectionId.addAndValidateYourData}
/>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useCallback } from 'react';
import { INGESTION_HUB_VIDEO_SOURCE } from '../../../../../constants';
import { WATCH_VIDEO_BUTTON_TITLE } from '../../translations';

const VIDEO_CONTENT_HEIGHT = 309;

const DataIngestionHubVideoComponent: React.FC = () => {
const ref = React.useRef<HTMLIFrameElement>(null);
const [isVideoPlaying, setIsVideoPlaying] = React.useState(false);
const { euiTheme } = useEuiTheme();

const onVideoClicked = useCallback(() => {
setIsVideoPlaying(true);
}, []);

return (
<div
css={css`
border-radius: 0px;
`}
>
<div
css={css`
height: ${VIDEO_CONTENT_HEIGHT}px;
`}
>
{isVideoPlaying && (
<EuiFlexGroup
css={css`
background-color: ${euiTheme.colors.fullShade};
height: 100%;
width: 100%;
position: absolute;
z-index: 1;
cursor: pointer;
`}
gutterSize="none"
justifyContent="center"
alignItems="center"
onClick={onVideoClicked}
>
<EuiFlexItem grow={false}>
<EuiIcon type="playFilled" size="xxl" color={euiTheme.colors.emptyShade} />
</EuiFlexItem>
</EuiFlexGroup>
)}
{!isVideoPlaying && (
<iframe
ref={ref}
allowFullScreen
className="vidyard_iframe"
frameBorder="0"
height="100%"
width="100%"
referrerPolicy="no-referrer"
sandbox="allow-scripts allow-same-origin"
scrolling="no"
allow={isVideoPlaying ? 'autoplay;' : undefined}
src={`${INGESTION_HUB_VIDEO_SOURCE}${isVideoPlaying ? '?autoplay=1' : ''}`}
title={WATCH_VIDEO_BUTTON_TITLE}
/>
)}
</div>
</div>
);
};

export const DataIngestionHubVideo = React.memo(DataIngestionHubVideoComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,52 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { render } from '@testing-library/react';
import { StepContent } from './step_content';
import { QuickStartSectionCardsId, SectionId } from '../types';
import { overviewVideoSteps } from '../sections';
import { AddAndValidateYourDataCardsId, SectionId } from '../types';
import { viewDashboardSteps } from '../sections';
import { mountWithIntl } from '@kbn/test-jest-helpers';

jest.mock('../context/step_context');
jest.mock('../../../../lib/kibana');
jest.mock('@kbn/security-solution-navigation/src/context');
jest.mock('../../../../lib/kibana', () => ({
useKibana: () => ({
services: {
notifications: {
toasts: {
addError: jest.fn(),
},
},
},
}),
}));

describe('StepContent', () => {
const toggleTaskCompleteStatus = jest.fn();

const props = {
cardId: QuickStartSectionCardsId.watchTheOverviewVideo,
cardId: AddAndValidateYourDataCardsId.viewDashboards,
indicesExist: false,
sectionId: SectionId.quickStart,
step: overviewVideoSteps[0],
sectionId: SectionId.addAndValidateYourData,
step: viewDashboardSteps[0],
toggleTaskCompleteStatus,
};

it('renders step content when hasStepContent is true and isExpandedStep is true', () => {
const mockProps = { ...props, hasStepContent: true, isExpandedStep: true };
const { getByTestId, getByText } = render(<StepContent {...mockProps} />);
const wrapper = mountWithIntl(<StepContent {...mockProps} />);

const splitPanelElement = getByTestId('split-panel');
const splitPanelElement = wrapper.find('[data-test-subj="split-panel"]');

expect(
getByText(
'Elastic Security unifies analytics, EDR, cloud security capabilities, and more into a SaaS solution that helps you improve your organization’s security posture, defend against a wide range of threats, and prevent breaches.'
)
).toBeInTheDocument();
expect(
getByText('To explore the platform’s core features, watch the video:')
).toBeInTheDocument();
expect(splitPanelElement.exists()).toBe(true);

expect(splitPanelElement).toBeInTheDocument();
expect(
wrapper
.text()
.includes(
'Use dashboards to visualize data and stay up-to-date with key information. Create your own, or use Elastic’s default dashboards — including alerts, user authentication events, known vulnerabilities, and more.'
)
).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { COLOR_MODES_STANDARD, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';

export const useCardStyles = () => {
const { euiTheme, colorMode } = useEuiTheme();
const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;

return css`
min-width: 315px;
&.headerCard:hover {
*:not(.headerCardContent) {
text-decoration: none;
}
.headerCardContent,
.headerCardContent * {
text-decoration: underline;
text-decoration-color: ${euiTheme.colors.primaryText};
}
}

${isDarkMode
? `
background-color: ${euiTheme.colors.lightestShade};
box-shadow: none;
border: 1px solid ${euiTheme.colors.mediumShade};
`
: ''}

.headerCardTitle {
font-size: ${euiTheme.base * 0.875}px;
font-weight: ${euiTheme.font.weight.semiBold};
line-height: ${euiTheme.size.l};
color: ${euiTheme.colors.title};
text-decoration: none;
}

.headerCardImage {
width: 64px;
height: 64px;
}

.headerCardDescription {
font-size: 12.25px;
font-weight: ${euiTheme.font.weight.regular};
line-height: ${euiTheme.base * 1.25}px;
color: ${euiTheme.colors.darkestShade};
}
`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 { render } from '@testing-library/react';
import { Card } from './card';

jest.mock('../../../../../lib/kibana', () => ({
useEuiTheme: jest.fn(() => ({ euiTheme: { colorTheme: 'DARK' } })),
}));

describe('DataIngestionHubHeaderCardComponent', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should render the title, description, and icon', () => {
const { getByTestId, getByText } = render(
<Card icon={'mockIcon.png'} title={'Mock Title'} description={'Mock Description'}>
<div>{'test'}</div>
</Card>
);

expect(getByText('Mock Title')).toBeInTheDocument();
expect(getByText('Mock Description')).toBeInTheDocument();
expect(getByTestId('data-ingestion-header-card-icon')).toHaveAttribute('src', 'mockIcon.png');
});

it('should apply dark mode styles when color mode is DARK', () => {
const { container } = render(
<Card icon={'mockIcon.png'} title={'Mock Title'} description={'Mock Description'}>
<div>{'test'}</div>
</Card>
);
const cardElement = container.querySelector('.euiCard');
expect(cardElement).toHaveStyle('background-color:rgb(255, 255, 255)');
});
});
Loading