mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[SecuritySolution] Get Started page UI (#172616)
## Summary Fix up for https://github.com/elastic/kibana/pull/171078 Test Env: https://p.elstc.co/paste/vmb8YG18#nCnDFTVE4HZxFK9M4TyHii3Gt4rq0YV25LQK33PqNly <img width="2556" alt="Screenshot 2023-12-05 at 19 00 06" src="ce6e3da7
-c169-4213-85a7-577625b8b350"> **- Add footer section:** https://www.figma.com/file/07wil4wWtUy90m4NTBxZxG/Updated-Security-GSH-Flows%3A?node-id=1574%3A161997&mode=dev <img width="748" alt="Screenshot 2023-12-05 at 18 42 36" src="596f1968
-f754-4bbc-a5a6-e6987bb96699"> **- Expand / Collapse task fix up:** 1. When no data integrated, clicking on `Add integrations step`from the callout should expand the step. 2. When visiting get started page with hash, it should expand the target step: e.g.: `/app/security/get_started#add_integrations` 3. All tasks should be collapsable.91f8fe94
-1c9d-48ef-be74-6f65bb63dfbd **- Designer review:** 1. Background color for task icons: ```Task not completed``` Background-grey on all states: Default, Hover, Expanded ```Task completed``` Background-green on all states: Default, Hover, Expanded  5. Remove shadow on create project image:  6. Change the gab between task to 16px:  7. Apply **bold** to completed task counts:  8. Update badge padding:  ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
9034cb6181
commit
45cbd2b743
39 changed files with 959 additions and 261 deletions
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const ContentWrapper = ({ children }: { children: React.ReactElement }) => <>{children}</>;
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 connectToDataSources from '../../images/connect_to_existing_sources.png';
|
||||
import { ADD_INTEGRATIONS_IMAGE_TITLE } from '../../translations';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const AddIntegrationsImageComponent = () => {
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<img
|
||||
src={connectToDataSources}
|
||||
alt={ADD_INTEGRATIONS_IMAGE_TITLE}
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
</ContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddIntegrationsImage = React.memo(AddIntegrationsImageComponent);
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { useStepContentStyles } from '../../styles/step_content.styles';
|
||||
|
||||
const ContentWrapperComponent: React.FC<{ children: React.ReactElement; shadow?: boolean }> = ({
|
||||
children,
|
||||
shadow = true,
|
||||
}) => {
|
||||
const { getRightContentStyles } = useStepContentStyles();
|
||||
const rightContentStyles = getRightContentStyles({ shadow });
|
||||
|
||||
return (
|
||||
<div className="right-panel-content" css={rightContentStyles}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContentWrapper = React.memo(ContentWrapperComponent);
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 createProjects from '../../images/create_projects.png';
|
||||
import { CREATE_PROJECT_TITLE } from '../../translations';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const CreateProjectImageComponent = () => (
|
||||
<ContentWrapper shadow={false}>
|
||||
<img src={createProjects} alt={CREATE_PROJECT_TITLE} height="100%" width="100%" />
|
||||
</ContentWrapper>
|
||||
);
|
||||
|
||||
export const CreateProjectImage = React.memo(CreateProjectImageComponent);
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 enablePrebuiltRules from '../../images/enable_prebuilt_rules.png';
|
||||
import { ENABLE_RULES } from '../../translations';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const EnableRuleImageComponent = () => (
|
||||
<ContentWrapper>
|
||||
<img src={enablePrebuiltRules} alt={ENABLE_RULES} height="100%" width="100%" />
|
||||
</ContentWrapper>
|
||||
);
|
||||
|
||||
export const EnableRuleImage = React.memo(EnableRuleImageComponent);
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { WATCH_VIDEO_DESCRIPTION1, WATCH_VIDEO_DESCRIPTION2 } from '../translations';
|
||||
import { WATCH_VIDEO_DESCRIPTION1, WATCH_VIDEO_DESCRIPTION2 } from '../../translations';
|
||||
|
||||
const OverviewVideoDescriptionComponent = () => (
|
||||
<>
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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, fireEvent, screen } from '@testing-library/react';
|
||||
import { Video } from './video';
|
||||
import { OverviewSteps, QuickStartSectionCardsId, SectionId } from '../../types';
|
||||
import type { EuiFlexGroupProps } from '@elastic/eui';
|
||||
import { useStepContext } from '../../context/step_context';
|
||||
import { WATCH_VIDEO_BUTTON_TITLE } from '../../translations';
|
||||
import { defaultExpandedCards } from '../../storage';
|
||||
|
||||
jest.mock('../../context/step_context');
|
||||
jest.mock('./content_wrapper');
|
||||
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
EuiFlexGroup: ({ children, onClick }: EuiFlexGroupProps) => {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||
<div data-test-subj="watch-video-overlay" onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
EuiFlexItem: ({ children }: { children: React.ReactElement }) => <div>{children}</div>,
|
||||
EuiIcon: () => <span data-test-subj="mock-play-icon" />,
|
||||
useEuiTheme: () => ({ euiTheme: { colors: { fullShade: '#000', emptyShade: '#fff' } } }),
|
||||
}));
|
||||
|
||||
describe('Video Component', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders overlay if step is not completed', () => {
|
||||
const { getByTestId } = render(<Video />);
|
||||
const overlay = getByTestId('watch-video-overlay');
|
||||
expect(overlay).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders video after clicking the overlay', () => {
|
||||
const { toggleTaskCompleteStatus } = useStepContext();
|
||||
const { getByTestId, queryByTestId } = render(<Video />);
|
||||
const overlay = getByTestId('watch-video-overlay');
|
||||
fireEvent.click(overlay);
|
||||
expect(toggleTaskCompleteStatus).toHaveBeenCalledWith({
|
||||
stepId: OverviewSteps.getToKnowElasticSecurity,
|
||||
cardId: QuickStartSectionCardsId.watchTheOverviewVideo,
|
||||
sectionId: SectionId.quickStart,
|
||||
undo: false,
|
||||
});
|
||||
|
||||
const iframe = screen.getByTitle(WATCH_VIDEO_BUTTON_TITLE);
|
||||
expect(iframe).toBeInTheDocument();
|
||||
|
||||
const overlayAfterClick = queryByTestId('watch-video-overlay');
|
||||
expect(overlayAfterClick).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders video if step is completed', () => {
|
||||
(useStepContext as jest.Mock).mockReturnValue({
|
||||
expandedCardSteps: defaultExpandedCards,
|
||||
finishedSteps: {
|
||||
[QuickStartSectionCardsId.watchTheOverviewVideo]: new Set([
|
||||
OverviewSteps.getToKnowElasticSecurity,
|
||||
]),
|
||||
},
|
||||
onStepClicked: jest.fn(),
|
||||
toggleTaskCompleteStatus: jest.fn(),
|
||||
});
|
||||
const { getByTitle, queryByTestId } = render(<Video />);
|
||||
const iframe = getByTitle(WATCH_VIDEO_BUTTON_TITLE);
|
||||
expect(iframe).toBeInTheDocument();
|
||||
|
||||
const overlay = queryByTestId('watch-video-overlay');
|
||||
expect(overlay).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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, useMemo } from 'react';
|
||||
import { useStepContext } from '../../context/step_context';
|
||||
import { WATCH_VIDEO_BUTTON_TITLE } from '../../translations';
|
||||
import { OverviewSteps, QuickStartSectionCardsId, SectionId } from '../../types';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const VideoComponent: React.FC = () => {
|
||||
const { toggleTaskCompleteStatus, finishedSteps } = useStepContext();
|
||||
const ref = React.useRef<HTMLIFrameElement>(null);
|
||||
const [isVideoPlaying, setIsVideoPlaying] = React.useState(false);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const cardId = QuickStartSectionCardsId.watchTheOverviewVideo;
|
||||
const isFinishedStep = useMemo(
|
||||
() => finishedSteps[cardId]?.has(OverviewSteps.getToKnowElasticSecurity),
|
||||
[finishedSteps, cardId]
|
||||
);
|
||||
|
||||
const onVideoClicked = useCallback(() => {
|
||||
toggleTaskCompleteStatus({
|
||||
stepId: OverviewSteps.getToKnowElasticSecurity,
|
||||
cardId: QuickStartSectionCardsId.watchTheOverviewVideo,
|
||||
sectionId: SectionId.quickStart,
|
||||
undo: false,
|
||||
});
|
||||
setIsVideoPlaying(true);
|
||||
}, [toggleTaskCompleteStatus]);
|
||||
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<>
|
||||
{!isVideoPlaying && !isFinishedStep && (
|
||||
<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 || isFinishedStep) && (
|
||||
<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={`//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html${
|
||||
isVideoPlaying ? '?autoplay=1' : ''
|
||||
}`}
|
||||
title={WATCH_VIDEO_BUTTON_TITLE}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</ContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Video = React.memo(VideoComponent);
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 viewAlerts from '../../images/view_alerts.png';
|
||||
import { VIEW_ALERTS_TITLE } from '../../translations';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const ViewAlertsImageComponent = () => (
|
||||
<ContentWrapper>
|
||||
<img src={viewAlerts} alt={VIEW_ALERTS_TITLE} height="100%" width="100%" />
|
||||
</ContentWrapper>
|
||||
);
|
||||
|
||||
export const ViewAlertsImage = React.memo(ViewAlertsImageComponent);
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 analyzeDataUsingDashboards from '../../images/analyze_data_using_dashboards.png';
|
||||
import { VIEW_DASHBOARDS_IMAGE_TITLE } from '../../translations';
|
||||
import { ContentWrapper } from './content_wrapper';
|
||||
|
||||
const ViewDashboardImageComponent = () => (
|
||||
<ContentWrapper>
|
||||
<img
|
||||
src={analyzeDataUsingDashboards}
|
||||
alt={VIEW_DASHBOARDS_IMAGE_TITLE}
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
</ContentWrapper>
|
||||
);
|
||||
|
||||
export const ViewDashboardImage = React.memo(ViewDashboardImageComponent);
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { autoCheckPrebuildRuleStepCompleted } from './helpers';
|
||||
import { fetchRuleManagementFilters } from '../apis';
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
|
||||
jest.mock('../apis');
|
||||
|
||||
describe('autoCheckPrebuildRuleStepCompleted', () => {
|
||||
const mockHttp = {} as HttpSetup;
|
||||
const mockAbortController = new AbortController();
|
||||
|
||||
it('should return true if there are enabled rules', async () => {
|
||||
(fetchRuleManagementFilters as jest.Mock).mockResolvedValue({ total: 1 });
|
||||
const result = await autoCheckPrebuildRuleStepCompleted({
|
||||
abortSignal: { current: mockAbortController },
|
||||
kibanaServicesHttp: mockHttp,
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should call onError and return false on error', async () => {
|
||||
const mockError = new Error('Test error');
|
||||
(fetchRuleManagementFilters as jest.Mock).mockRejectedValue(mockError);
|
||||
const mockOnError = jest.fn();
|
||||
|
||||
const result = await autoCheckPrebuildRuleStepCompleted({
|
||||
abortSignal: { current: mockAbortController },
|
||||
kibanaServicesHttp: mockHttp,
|
||||
onError: mockOnError,
|
||||
});
|
||||
|
||||
expect(mockOnError).toHaveBeenCalledWith(mockError);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should not call onError if the request is aborted', async () => {
|
||||
(fetchRuleManagementFilters as jest.Mock).mockRejectedValue({ name: 'AbortError' });
|
||||
const mockOnError = jest.fn();
|
||||
|
||||
mockAbortController.abort();
|
||||
|
||||
const result = await autoCheckPrebuildRuleStepCompleted({
|
||||
abortSignal: { current: mockAbortController },
|
||||
kibanaServicesHttp: mockHttp,
|
||||
onError: mockOnError,
|
||||
});
|
||||
|
||||
expect(mockOnError).not.toHaveBeenCalled();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 type { MutableRefObject } from 'react';
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { ENABLED_FIELD } from '@kbn/security-solution-plugin/common';
|
||||
import { fetchRuleManagementFilters } from '../apis';
|
||||
|
||||
export const autoCheckPrebuildRuleStepCompleted = async ({
|
||||
abortSignal,
|
||||
kibanaServicesHttp,
|
||||
onError,
|
||||
}: {
|
||||
abortSignal: MutableRefObject<AbortController>;
|
||||
kibanaServicesHttp: HttpSetup;
|
||||
onError?: (e: Error) => void;
|
||||
}) => {
|
||||
// Check if there are any rules installed and enabled
|
||||
try {
|
||||
const data = await fetchRuleManagementFilters({
|
||||
http: kibanaServicesHttp,
|
||||
signal: abortSignal.current.signal,
|
||||
query: {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
sort_field: 'enabled',
|
||||
sort_order: 'desc',
|
||||
filter: `${ENABLED_FIELD}: true`,
|
||||
},
|
||||
});
|
||||
return data?.total > 0;
|
||||
} catch (e) {
|
||||
if (!abortSignal.current.signal.aborted) {
|
||||
onError?.(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const autoCheckAddIntegrationsStepCompleted = async ({
|
||||
indicesExist,
|
||||
}: {
|
||||
indicesExist: boolean;
|
||||
}) => Promise.resolve(indicesExist);
|
|
@ -77,18 +77,19 @@ const CardStepComponent: React.FC<{
|
|||
const toggleStep = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const newStatus = !isExpandedStep;
|
||||
|
||||
if (hasStepContent) {
|
||||
// Toggle step and sync the expanded card step to storage & reducer
|
||||
onStepClicked({ stepId, cardId, sectionId, isExpanded: !isExpandedStep });
|
||||
onStepClicked({ stepId, cardId, sectionId, isExpanded: newStatus });
|
||||
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.landing,
|
||||
path: `#${stepId}`,
|
||||
path: newStatus ? `#${stepId}` : undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
[hasStepContent, onStepClicked, stepId, cardId, sectionId, isExpandedStep, navigateTo]
|
||||
[isExpandedStep, hasStepContent, onStepClicked, stepId, cardId, sectionId, navigateTo]
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -170,6 +171,4 @@ const CardStepComponent: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
CardStepComponent.displayName = 'CardStepComponent';
|
||||
|
||||
export const CardStep = React.memo(CardStepComponent);
|
||||
|
|
|
@ -84,7 +84,7 @@ const StepContentComponent = ({
|
|||
css={rightPanelStyles}
|
||||
>
|
||||
{splitPanel && (
|
||||
<div className="right-content-panel" css={rightPanelContentStyles}>
|
||||
<div className="right-panel-wrapper" css={rightPanelContentStyles}>
|
||||
{splitPanel}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,86 +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 { EuiFlexGroup, EuiFlexItem, EuiIcon, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useStepContext } from '../context/step_context';
|
||||
import { WATCH_VIDEO_BUTTON_TITLE } from '../translations';
|
||||
import { OverviewSteps, QuickStartSectionCardsId, SectionId } from '../types';
|
||||
|
||||
const VideoComponent: React.FC = () => {
|
||||
const { toggleTaskCompleteStatus, finishedSteps } = useStepContext();
|
||||
const ref = React.useRef<HTMLIFrameElement>(null);
|
||||
const [isVideoPlaying, setIsVideoPlaying] = React.useState(false);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const cardId = QuickStartSectionCardsId.watchTheOverviewVideo;
|
||||
const isFinishedStep = useMemo(
|
||||
() => finishedSteps[cardId]?.has(OverviewSteps.getToKnowElasticSecurity),
|
||||
[finishedSteps, cardId]
|
||||
);
|
||||
|
||||
const onVideoClicked = useCallback(() => {
|
||||
toggleTaskCompleteStatus({
|
||||
stepId: OverviewSteps.getToKnowElasticSecurity,
|
||||
cardId: QuickStartSectionCardsId.watchTheOverviewVideo,
|
||||
sectionId: SectionId.quickStart,
|
||||
undo: false,
|
||||
});
|
||||
setIsVideoPlaying(true);
|
||||
}, [toggleTaskCompleteStatus]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`}
|
||||
>
|
||||
{!isVideoPlaying && !isFinishedStep && (
|
||||
<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 || isFinishedStep) && (
|
||||
<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={`//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html${
|
||||
isVideoPlaying ? '?autoplay=1' : ''
|
||||
}`}
|
||||
title={WATCH_VIDEO_BUTTON_TITLE}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Video = React.memo(VideoComponent);
|
|
@ -9,19 +9,17 @@ import React from 'react';
|
|||
import { defaultExpandedCards } from '../../storage';
|
||||
import { CreateProjectSteps, QuickStartSectionCardsId } from '../../types';
|
||||
|
||||
export const mockOnStepClicked = jest.fn();
|
||||
export const mockToggleTaskCompleteStatus = jest.fn();
|
||||
export const StepContextProvider = ({ children }: { children: React.ReactElement }) => (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
export const useStepContext = () => {
|
||||
return {
|
||||
expandedCardSteps: defaultExpandedCards,
|
||||
finishedSteps: {
|
||||
[QuickStartSectionCardsId.createFirstProject]: new Set([
|
||||
CreateProjectSteps.createFirstProject,
|
||||
]),
|
||||
},
|
||||
onStepClicked: jest.fn(),
|
||||
toggleTaskCompleteStatus: jest.fn(),
|
||||
};
|
||||
};
|
||||
export const useStepContext = jest.fn(() => ({
|
||||
expandedCardSteps: defaultExpandedCards,
|
||||
finishedSteps: {
|
||||
[QuickStartSectionCardsId.createFirstProject]: new Set([CreateProjectSteps.createFirstProject]),
|
||||
},
|
||||
onStepClicked: mockOnStepClicked,
|
||||
toggleTaskCompleteStatus: mockToggleTaskCompleteStatus,
|
||||
}));
|
||||
|
|
|
@ -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 documentation from '../images/documentation.png';
|
||||
import forum from '../images/forum.png';
|
||||
import demo from '../images/demo.png';
|
||||
import labs from '../images/labs.png';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const footer = [
|
||||
{
|
||||
icon: documentation,
|
||||
key: 'documentation',
|
||||
title: i18n.FOOTER_DOCUMENTATION_TITLE,
|
||||
description: i18n.FOOTER_DOCUMENTATION_DESCRIPTION,
|
||||
link: {
|
||||
title: i18n.FOOTER_DOCUMENTATION_LINK_TITLE,
|
||||
href: 'https://docs.elastic.co/integrations/elastic-security-intro',
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: forum,
|
||||
key: 'forum',
|
||||
title: i18n.FOOTER_FORUM_TITLE,
|
||||
description: i18n.FOOTER_FORUM_DESCRIPTION,
|
||||
link: {
|
||||
title: i18n.FOOTER_FORUM_LINK_TITLE,
|
||||
href: 'https://discuss.elastic.co/c/security/83',
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: demo,
|
||||
key: 'demo',
|
||||
title: i18n.FOOTER_DEMO_TITLE,
|
||||
description: i18n.FOOTER_DEMO_DESCRIPTION,
|
||||
link: {
|
||||
title: i18n.FOOTER_DEMO_LINK_TITLE,
|
||||
href: 'https://www.elastic.co/demo-gallery?solutions=security&features=null',
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: labs,
|
||||
key: 'labs',
|
||||
title: i18n.FOOTER_LABS_TITLE,
|
||||
description: i18n.FOOTER_LABS_DESCRIPTION,
|
||||
link: {
|
||||
title: i18n.FOOTER_LABS_LINK_TITLE,
|
||||
href: 'https://www.elastic.co/security-labs',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const getFooter = () => footer;
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useFooterStyles } from '../styles/footer.styles';
|
||||
import { getFooter } from './footer';
|
||||
|
||||
const FooterComponent = () => {
|
||||
const { wrapperStyle, titleStyle, descriptionStyle, linkStyle } = useFooterStyles();
|
||||
const footer = useMemo(() => getFooter(), []);
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
gutterSize="none"
|
||||
css={wrapperStyle}
|
||||
>
|
||||
{footer.map((item) => (
|
||||
<EuiFlexItem key={`footer-${item.key}`}>
|
||||
<img src={item.icon} alt={item.title} height="64" width="64" />
|
||||
<EuiSpacer size="m" />
|
||||
<p css={titleStyle}>{item.title}</p>
|
||||
<p css={descriptionStyle}>{item.description}</p>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiLink href={item.link.href} external={true} target="_blank" css={linkStyle}>
|
||||
{item.link.title}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const Footer = React.memo(FooterComponent);
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 FOOTER_DOCUMENTATION_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.documentation.title',
|
||||
{
|
||||
defaultMessage: 'Browse documentation',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_DOCUMENTATION_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.documentation.description',
|
||||
{
|
||||
defaultMessage: 'In-depth guides on all Elastic features',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_DOCUMENTATION_LINK_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.documentation.link.title',
|
||||
{
|
||||
defaultMessage: 'Start reading',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_FORUM_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.forum.title',
|
||||
{
|
||||
defaultMessage: 'Explore forum',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_FORUM_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.forum.description',
|
||||
{
|
||||
defaultMessage: 'Exchange thoughts about Elastic',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_FORUM_LINK_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.forum.link.title',
|
||||
{
|
||||
defaultMessage: 'Discuss Forum',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_DEMO_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.demo.title',
|
||||
{
|
||||
defaultMessage: 'View demo project',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_DEMO_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.demo.description',
|
||||
{
|
||||
defaultMessage: 'Discover Elastic using sample data',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_DEMO_LINK_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.demo.link.title',
|
||||
{
|
||||
defaultMessage: 'Explore demo',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_LABS_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.labs.title',
|
||||
{
|
||||
defaultMessage: 'Elastic Security Labs',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_LABS_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.labs.description',
|
||||
{
|
||||
defaultMessage: 'Insights from security researchers',
|
||||
}
|
||||
);
|
||||
|
||||
export const FOOTER_LABS_LINK_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.getStarted.footer.labs.link.title',
|
||||
{
|
||||
defaultMessage: 'Learn more',
|
||||
}
|
||||
);
|
|
@ -18,6 +18,8 @@ import { Progress } from './progress_bar';
|
|||
import { StepContextProvider } from './context/step_context';
|
||||
import { CONTENT_WIDTH } from './helpers';
|
||||
import { WelcomeHeader } from './welcome_header';
|
||||
import { Footer } from './footer';
|
||||
import { useScrollToHash } from './hooks/use_scroll';
|
||||
|
||||
export interface GetStartedProps {
|
||||
indicesExist?: boolean;
|
||||
|
@ -42,6 +44,8 @@ export const GetStartedComponent: React.FC<GetStartedProps> = ({ productTypes, i
|
|||
(product) => product.product_line === ProductLine.security
|
||||
)?.product_tier;
|
||||
|
||||
useScrollToHash();
|
||||
|
||||
return (
|
||||
<KibanaPageTemplate
|
||||
restrictWidth={false}
|
||||
|
@ -88,7 +92,7 @@ export const GetStartedComponent: React.FC<GetStartedProps> = ({ productTypes, i
|
|||
restrictWidth={CONTENT_WIDTH}
|
||||
paddingSize="none"
|
||||
css={css`
|
||||
padding: 0 ${euiTheme.size.xxl} ${euiTheme.base * 3.5}px;
|
||||
padding: 0 ${euiTheme.size.xxl} ${euiTheme.size.xxxl};
|
||||
background-color: ${euiTheme.colors.lightestShade};
|
||||
`}
|
||||
>
|
||||
|
@ -102,6 +106,9 @@ export const GetStartedComponent: React.FC<GetStartedProps> = ({ productTypes, i
|
|||
<TogglePanel activeProducts={activeProducts} activeSections={activeSections} />
|
||||
</StepContextProvider>
|
||||
</KibanaPageTemplate.Section>
|
||||
<KibanaPageTemplate.Section grow={true} restrictWidth={CONTENT_WIDTH} paddingSize="none">
|
||||
<Footer />
|
||||
</KibanaPageTemplate.Section>
|
||||
</KibanaPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,16 @@
|
|||
|
||||
import type { ProductLine } from '../../common/product';
|
||||
import { getSections } from './sections';
|
||||
import type { ActiveCard, ActiveSections, Card, CardId, SectionId, Step, StepId } from './types';
|
||||
import type {
|
||||
ActiveCard,
|
||||
ActiveSections,
|
||||
Card,
|
||||
CardId,
|
||||
Section,
|
||||
SectionId,
|
||||
Step,
|
||||
StepId,
|
||||
} from './types';
|
||||
import { CreateProjectSteps, QuickStartSectionCardsId } from './types';
|
||||
|
||||
export const CONTENT_WIDTH = 1150;
|
||||
|
@ -53,14 +62,30 @@ const getfinishedActiveSteps = (
|
|||
return new Set(finishedActiveSteps);
|
||||
};
|
||||
|
||||
export const findCardByStepId = (
|
||||
export const findCardSectionByStepId = (
|
||||
stepId: string
|
||||
): { matchedCard: Card | null; matchedStep: Step | null } => {
|
||||
): { matchedCard: Card | null; matchedStep: Step | null; matchedSection: Section | null } => {
|
||||
const cards = getSections().flatMap((s) => s.cards);
|
||||
const matchedStep: Step | null = null;
|
||||
const matchedCard = cards.find((c) => !!c.steps?.find((step) => stepId === step.id)) ?? null;
|
||||
let matchedStep: Step | null = null;
|
||||
|
||||
return { matchedCard, matchedStep };
|
||||
const matchedCard =
|
||||
cards.find(
|
||||
(c) =>
|
||||
!!c.steps?.find((step) => {
|
||||
if (stepId === step.id) {
|
||||
matchedStep = step;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
) ?? null;
|
||||
|
||||
const matchedSection = matchedCard
|
||||
? getSections().find((s) => s.cards?.includes(matchedCard)) ?? null
|
||||
: null;
|
||||
|
||||
return { matchedCard, matchedStep, matchedSection };
|
||||
};
|
||||
|
||||
export const getCard = ({ cardId, sectionId }: { cardId: CardId; sectionId: SectionId }) => {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { useCheckStepCompleted } from './use_check_step_completed';
|
||||
import {
|
||||
EnablePrebuiltRulesSteps,
|
||||
GetStartedWithAlertsCardsId,
|
||||
OverviewSteps,
|
||||
QuickStartSectionCardsId,
|
||||
SectionId,
|
||||
} from '../types';
|
||||
|
||||
jest.mock('../../common/services', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
http: {},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addError: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useCheckStepCompleted', () => {
|
||||
it('does nothing when autoCheckIfStepCompleted is not provided', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useCheckStepCompleted({
|
||||
indicesExist: true,
|
||||
stepId: OverviewSteps.getToKnowElasticSecurity,
|
||||
cardId: QuickStartSectionCardsId.watchTheOverviewVideo,
|
||||
sectionId: SectionId.quickStart,
|
||||
toggleTaskCompleteStatus: jest.fn(),
|
||||
})
|
||||
);
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it('calls autoCheckIfStepCompleted and toggleTaskCompleteStatus', async () => {
|
||||
const mockAutoCheck = jest.fn().mockResolvedValue(true);
|
||||
const mockToggleTask = jest.fn();
|
||||
|
||||
const { waitFor } = renderHook(() =>
|
||||
useCheckStepCompleted({
|
||||
autoCheckIfStepCompleted: mockAutoCheck,
|
||||
cardId: GetStartedWithAlertsCardsId.enablePrebuiltRules,
|
||||
indicesExist: true,
|
||||
sectionId: SectionId.getStartedWithAlerts,
|
||||
stepId: EnablePrebuiltRulesSteps.enablePrebuiltRules,
|
||||
toggleTaskCompleteStatus: mockToggleTask,
|
||||
})
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAutoCheck).toHaveBeenCalled();
|
||||
expect(mockToggleTask).toHaveBeenCalledWith({
|
||||
sectionId: SectionId.getStartedWithAlerts,
|
||||
stepId: EnablePrebuiltRulesSteps.enablePrebuiltRules,
|
||||
cardId: GetStartedWithAlertsCardsId.enablePrebuiltRules,
|
||||
undo: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 { useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { HEIGHT_ANIMATION_DURATION } from '../styles/card_step.styles';
|
||||
|
||||
const HEADER_OFFSET = 40;
|
||||
|
||||
export const useScrollToHash = () => {
|
||||
const location = useLocation();
|
||||
|
||||
const [documentReadyState, setReadyState] = useState(document.readyState);
|
||||
useEffect(() => {
|
||||
const readyStateListener = () => setReadyState(document.readyState);
|
||||
document.addEventListener('readystatechange', readyStateListener);
|
||||
return () => document.removeEventListener('readystatechange', readyStateListener);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentReadyState !== 'complete') return; // Wait for page to finish loading before scrolling
|
||||
|
||||
const hash = location.hash.split('?')[0].replace('#', '');
|
||||
const element = hash ? document.getElementById(hash) : null;
|
||||
|
||||
if (element) {
|
||||
// Wait for transition to complete before scrolling
|
||||
setTimeout(() => {
|
||||
element.focus({ preventScroll: true }); // Scrolling already handled below
|
||||
|
||||
window.scrollTo({
|
||||
top: element.offsetTop - HEADER_OFFSET,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, HEIGHT_ANIMATION_DURATION);
|
||||
}
|
||||
}, [location.hash, documentReadyState]);
|
||||
};
|
|
@ -11,8 +11,6 @@ import { useSetUpSections } from './use_setup_sections';
|
|||
import type { ActiveSections, CardId, ExpandedCardSteps, StepId } from '../types';
|
||||
import { CreateProjectSteps, QuickStartSectionCardsId, SectionId } from '../types';
|
||||
|
||||
import { ProductLine } from '../../../common/product';
|
||||
|
||||
const mockEuiTheme: EuiThemeComputed = {
|
||||
size: {
|
||||
l: '16px',
|
||||
|
@ -44,7 +42,6 @@ describe('useSetUpSections', () => {
|
|||
} as ActiveSections;
|
||||
|
||||
const sections = result.current.setUpSections({
|
||||
activeProducts: new Set([ProductLine.security]),
|
||||
activeSections,
|
||||
expandedCardSteps: {} as ExpandedCardSteps,
|
||||
onStepClicked,
|
||||
|
@ -62,7 +59,6 @@ describe('useSetUpSections', () => {
|
|||
|
||||
const sections = result.current.setUpSections({
|
||||
activeSections,
|
||||
activeProducts: new Set([ProductLine.security]),
|
||||
expandedCardSteps: {} as ExpandedCardSteps,
|
||||
onStepClicked,
|
||||
toggleTaskCompleteStatus,
|
||||
|
|
|
@ -21,12 +21,10 @@ import type {
|
|||
|
||||
import { CardItem } from '../card_item';
|
||||
import { getSections } from '../sections';
|
||||
import type { ProductLine } from '../../../common/product';
|
||||
|
||||
export const useSetUpSections = ({ euiTheme }: { euiTheme: EuiThemeComputed }) => {
|
||||
const setUpCards = useCallback(
|
||||
({
|
||||
activeProducts,
|
||||
activeSections,
|
||||
expandedCardSteps,
|
||||
finishedSteps,
|
||||
|
@ -34,7 +32,6 @@ export const useSetUpSections = ({ euiTheme }: { euiTheme: EuiThemeComputed }) =
|
|||
onStepClicked,
|
||||
sectionId,
|
||||
}: {
|
||||
activeProducts: Set<ProductLine>;
|
||||
activeSections: ActiveSections | null;
|
||||
expandedCardSteps: ExpandedCardSteps;
|
||||
finishedSteps: Record<CardId, Set<StepId>>;
|
||||
|
@ -65,14 +62,12 @@ export const useSetUpSections = ({ euiTheme }: { euiTheme: EuiThemeComputed }) =
|
|||
|
||||
const setUpSections = useCallback(
|
||||
({
|
||||
activeProducts,
|
||||
activeSections,
|
||||
expandedCardSteps,
|
||||
finishedSteps,
|
||||
toggleTaskCompleteStatus,
|
||||
onStepClicked,
|
||||
}: {
|
||||
activeProducts: Set<ProductLine>;
|
||||
activeSections: ActiveSections | null;
|
||||
expandedCardSteps: ExpandedCardSteps;
|
||||
finishedSteps: Record<CardId, Set<StepId>>;
|
||||
|
@ -81,7 +76,6 @@ export const useSetUpSections = ({ euiTheme }: { euiTheme: EuiThemeComputed }) =
|
|||
}) =>
|
||||
getSections().reduce<React.ReactNode[]>((acc, currentSection) => {
|
||||
const cardNodes = setUpCards({
|
||||
activeProducts,
|
||||
activeSections,
|
||||
expandedCardSteps,
|
||||
finishedSteps,
|
||||
|
@ -117,10 +111,10 @@ export const useSetUpSections = ({ euiTheme }: { euiTheme: EuiThemeComputed }) =
|
|||
</span>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup
|
||||
gutterSize="m"
|
||||
gutterSize="none"
|
||||
direction="column"
|
||||
css={css`
|
||||
${euiTheme.size.base}
|
||||
gap: ${euiTheme.size.base};
|
||||
`}
|
||||
>
|
||||
{cardNodes}
|
||||
|
|
|
@ -27,10 +27,10 @@ import type {
|
|||
Switch,
|
||||
} from '../types';
|
||||
import { GetStartedPageActions } from '../types';
|
||||
import { findCardByStepId } from '../helpers';
|
||||
import { findCardSectionByStepId } from '../helpers';
|
||||
|
||||
const syncExpandedCardStepsToStorageFromURL = (maybeStepId: string) => {
|
||||
const { matchedCard, matchedStep } = findCardByStepId(maybeStepId);
|
||||
const { matchedCard, matchedStep } = findCardSectionByStepId(maybeStepId);
|
||||
const hasStepContent = matchedStep && matchedStep.description;
|
||||
|
||||
if (matchedCard && matchedStep && hasStepContent) {
|
||||
|
@ -54,7 +54,9 @@ const syncExpandedCardStepsFromStorageToURL = (
|
|||
);
|
||||
|
||||
if (expandedCardStep?.expandedSteps[0]) {
|
||||
const { matchedCard, matchedStep } = findCardByStepId(expandedCardStep?.expandedSteps[0]);
|
||||
const { matchedCard, matchedStep } = findCardSectionByStepId(
|
||||
expandedCardStep?.expandedSteps[0]
|
||||
);
|
||||
|
||||
callback?.({ matchedCard, matchedStep });
|
||||
}
|
||||
|
@ -114,28 +116,6 @@ export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProduct
|
|||
return getAllExpandedCardStepsFromStorage();
|
||||
}, [getAllExpandedCardStepsFromStorage, stepIdFromHash]);
|
||||
|
||||
useEffect(() => {
|
||||
syncExpandedCardStepsFromStorageToURL(
|
||||
expandedCardsInitialStates,
|
||||
({ matchedStep }: { matchedStep: Step | null }) => {
|
||||
if (!matchedStep) return;
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.landing,
|
||||
path: `#${matchedStep.id}`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}, [expandedCardsInitialStates, getAllExpandedCardStepsFromStorage, navigateTo]);
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
activeProducts: activeProductsInitialStates,
|
||||
activeSections: activeSectionsInitialStates,
|
||||
expandedCardSteps: expandedCardsInitialStates,
|
||||
finishedSteps: finishedStepsInitialStates,
|
||||
totalActiveSteps: totalActiveStepsInitialStates,
|
||||
totalStepsLeft: totalStepsLeftInitialStates,
|
||||
});
|
||||
|
||||
const onStepClicked: OnStepClicked = useCallback(
|
||||
({ stepId, cardId, isExpanded }) => {
|
||||
dispatch({
|
||||
|
@ -157,6 +137,15 @@ export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProduct
|
|||
]
|
||||
);
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
activeProducts: activeProductsInitialStates,
|
||||
activeSections: activeSectionsInitialStates,
|
||||
expandedCardSteps: expandedCardsInitialStates,
|
||||
finishedSteps: finishedStepsInitialStates,
|
||||
totalActiveSteps: totalActiveStepsInitialStates,
|
||||
totalStepsLeft: totalStepsLeftInitialStates,
|
||||
});
|
||||
|
||||
const toggleTaskCompleteStatus: ToggleTaskCompleteStatus = useCallback(
|
||||
({ stepId, cardId, sectionId, undo }) => {
|
||||
dispatch({
|
||||
|
@ -182,5 +171,64 @@ export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProduct
|
|||
[toggleActiveProductsInStorage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
/** Handle landing on the page without hash
|
||||
** e.g.: https://localhost:5601/app/security/get_started
|
||||
** If there is no expanded card step in storage, do nothing.
|
||||
** If there is expanded card step in storage, sync it to the url.
|
||||
**/
|
||||
if (!stepIdFromHash) {
|
||||
// If all steps are collapsed, do nothing
|
||||
if (Object.values(state.expandedCardSteps).every((c) => !c.isExpanded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncExpandedCardStepsFromStorageToURL(
|
||||
expandedCardsInitialStates,
|
||||
({ matchedStep }: { matchedStep: Step | null }) => {
|
||||
if (!matchedStep) return;
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.landing,
|
||||
path: `#${matchedStep.id}`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [
|
||||
expandedCardsInitialStates,
|
||||
getAllExpandedCardStepsFromStorage,
|
||||
navigateTo,
|
||||
state.expandedCardSteps,
|
||||
stepIdFromHash,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
/** Handle hash change and expand the target step.
|
||||
** e.g.: https://localhost:5601/app/security/get_started#create_your_first_project
|
||||
**/
|
||||
if (stepIdFromHash) {
|
||||
const { matchedCard, matchedStep, matchedSection } = findCardSectionByStepId(stepIdFromHash);
|
||||
const hasStepContent = matchedStep && matchedStep.description;
|
||||
if (hasStepContent && matchedCard && matchedStep && matchedSection) {
|
||||
// If the step is already expanded, do nothing
|
||||
if (state.expandedCardSteps[matchedCard.id]?.expandedSteps.includes(matchedStep.id)) {
|
||||
return;
|
||||
}
|
||||
// Toggle step and sync the expanded card step to storage & reducer
|
||||
onStepClicked({
|
||||
stepId: matchedStep.id,
|
||||
cardId: matchedCard.id,
|
||||
sectionId: matchedSection.id,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.landing,
|
||||
path: `#${matchedStep.id}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [navigateTo, onStepClicked, state.expandedCardSteps, stepIdFromHash]);
|
||||
|
||||
return { state, onStepClicked, toggleTaskCompleteStatus, onProductSwitchChanged };
|
||||
};
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import type { ProductTier } from '../../common/product';
|
||||
|
||||
import { PROGRESS_TRACKER_LABEL } from './translations';
|
||||
import { useProgressBarStyles } from './styles/progress_bar.style';
|
||||
|
||||
const ProgressComponent: React.FC<{
|
||||
productTier: ProductTier | undefined;
|
||||
|
@ -19,7 +19,7 @@ const ProgressComponent: React.FC<{
|
|||
}> = ({ productTier, totalActiveSteps, totalStepsLeft }) => {
|
||||
const stepsDone =
|
||||
totalActiveSteps != null && totalStepsLeft != null ? totalActiveSteps - totalStepsLeft : null;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { textStyle } = useProgressBarStyles();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
|
@ -31,19 +31,11 @@ const ProgressComponent: React.FC<{
|
|||
size="m"
|
||||
label={
|
||||
<span>
|
||||
<span
|
||||
css={css`
|
||||
font-size: 10.5px;
|
||||
font-weight: ${euiTheme.font.weight.bold}}};
|
||||
text-transform: uppercase;
|
||||
`}
|
||||
>
|
||||
{PROGRESS_TRACKER_LABEL}
|
||||
</span>
|
||||
<span css={textStyle}>{PROGRESS_TRACKER_LABEL}</span>
|
||||
<EuiSpacer size="s" />
|
||||
</span>
|
||||
}
|
||||
valueText={<>{`${stepsDone}/${totalActiveSteps}`}</>}
|
||||
valueText={<span css={textStyle}>{`${stepsDone}/${totalActiveSteps}`}</span>}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -4,11 +4,8 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { MutableRefObject } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { ENABLED_FIELD } from '@kbn/security-solution-plugin/common';
|
||||
import type { Step, StepId } from './types';
|
||||
import {
|
||||
SectionId,
|
||||
|
@ -27,18 +24,21 @@ import * as i18n from './translations';
|
|||
|
||||
import { AddIntegrationButton } from './step_links/add_integration_button';
|
||||
import { AlertsButton } from './step_links/alerts_link';
|
||||
import connectToDataSources from './images/connect_to_existing_sources.png';
|
||||
import enablePrebuiltRules from './images/enable_prebuilt_rules.png';
|
||||
import createProjects from './images/create_projects.png';
|
||||
import viewAlerts from './images/view_alerts.png';
|
||||
import analyzeDataUsingDashboards from './images/analyze_data_using_dashboards.png';
|
||||
import { AddElasticRulesButton } from './step_links/add_elastic_rules_button';
|
||||
import { DashboardButton } from './step_links/dashboard_button';
|
||||
import overviewVideo from './images/overview_video.svg';
|
||||
import { Video } from './card_step/video';
|
||||
import { fetchRuleManagementFilters } from './apis';
|
||||
import { OverviewVideoDescription } from './card_step/overview_video_description';
|
||||
import { Video } from './card_step/content/video';
|
||||
import { OverviewVideoDescription } from './card_step/content/overview_video_description';
|
||||
import { ManageProjectsButton } from './step_links/manage_projects_button';
|
||||
import { EnableRuleImage } from './card_step/content/enable_rule_image';
|
||||
import {
|
||||
autoCheckAddIntegrationsStepCompleted,
|
||||
autoCheckPrebuildRuleStepCompleted,
|
||||
} from './card_step/helpers';
|
||||
import { ViewDashboardImage } from './card_step/content/view_dashboard_image';
|
||||
import { AddIntegrationsImage } from './card_step/content/add_integration_image';
|
||||
import { CreateProjectImage } from './card_step/content/create_project_step_image';
|
||||
import { ViewAlertsImage } from './card_step/content/view_alerts_image';
|
||||
|
||||
export const createProjectSteps = [
|
||||
{
|
||||
|
@ -46,9 +46,7 @@ export const createProjectSteps = [
|
|||
title: i18n.CREATE_PROJECT_TITLE,
|
||||
icon: { type: 'addDataApp', size: 'xl' as const },
|
||||
description: [i18n.CREATE_PROJECT_DESCRIPTION, <ManageProjectsButton />],
|
||||
splitPanel: (
|
||||
<img src={createProjects} alt={i18n.CREATE_PROJECT_TITLE} height="100%" width="100%" />
|
||||
),
|
||||
splitPanel: <CreateProjectImage />,
|
||||
},
|
||||
];
|
||||
export const overviewVideoSteps = [
|
||||
|
@ -67,16 +65,8 @@ export const addIntegrationsSteps: Array<Step<AddIntegrationsSteps.connectToData
|
|||
id: AddIntegrationsSteps.connectToDataSources,
|
||||
title: i18n.ADD_INTEGRATIONS_TITLE,
|
||||
description: [i18n.ADD_INTEGRATIONS_DESCRIPTION, <AddIntegrationButton />],
|
||||
splitPanel: (
|
||||
<img
|
||||
src={connectToDataSources}
|
||||
alt={i18n.ADD_INTEGRATIONS_IMAGE_TITLE}
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
autoCheckIfStepCompleted: async ({ indicesExist }: { indicesExist: boolean }) =>
|
||||
Promise.resolve(indicesExist),
|
||||
splitPanel: <AddIntegrationsImage />,
|
||||
autoCheckIfStepCompleted: autoCheckAddIntegrationsStepCompleted,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -86,14 +76,7 @@ export const viewDashboardSteps = [
|
|||
icon: { type: 'dashboardApp', size: 'xl' as const },
|
||||
title: i18n.VIEW_DASHBOARDS,
|
||||
description: [i18n.VIEW_DASHBOARDS_DESCRIPTION, <DashboardButton />],
|
||||
splitPanel: (
|
||||
<img
|
||||
src={analyzeDataUsingDashboards}
|
||||
alt={i18n.VIEW_DASHBOARDS_IMAGE_TITLE}
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
),
|
||||
splitPanel: <ViewDashboardImage />,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -103,40 +86,8 @@ export const enablePrebuildRuleSteps: Array<Step<EnablePrebuiltRulesSteps.enable
|
|||
icon: { type: 'advancedSettingsApp', size: 'xl' as const },
|
||||
id: EnablePrebuiltRulesSteps.enablePrebuiltRules,
|
||||
description: [i18n.ENABLE_RULES_DESCRIPTION, <AddElasticRulesButton />],
|
||||
splitPanel: (
|
||||
<img src={enablePrebuiltRules} alt={i18n.ENABLE_RULES} height="100%" width="100%" />
|
||||
),
|
||||
autoCheckIfStepCompleted: async ({
|
||||
abortSignal,
|
||||
kibanaServicesHttp,
|
||||
onError,
|
||||
}: {
|
||||
abortSignal: MutableRefObject<AbortController>;
|
||||
kibanaServicesHttp: HttpSetup;
|
||||
onError?: (e: Error) => void;
|
||||
}) => {
|
||||
// Check if there are any rules installed and enabled
|
||||
try {
|
||||
const data = await fetchRuleManagementFilters({
|
||||
http: kibanaServicesHttp,
|
||||
signal: abortSignal.current.signal,
|
||||
query: {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
sort_field: 'enabled',
|
||||
sort_order: 'desc',
|
||||
filter: `${ENABLED_FIELD}: true`,
|
||||
},
|
||||
});
|
||||
return data?.total > 0;
|
||||
} catch (e) {
|
||||
if (!abortSignal.current.signal.aborted) {
|
||||
onError?.(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
splitPanel: <EnableRuleImage />,
|
||||
autoCheckIfStepCompleted: autoCheckPrebuildRuleStepCompleted,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -146,7 +97,7 @@ export const viewAlertSteps = [
|
|||
title: i18n.VIEW_ALERTS_TITLE,
|
||||
id: ViewAlertsSteps.viewAlerts,
|
||||
description: [i18n.VIEW_ALERTS_DESCRIPTION, <AlertsButton />],
|
||||
splitPanel: <img src={viewAlerts} alt={i18n.VIEW_ALERTS_TITLE} height="100%" width="100%" />,
|
||||
splitPanel: <ViewAlertsImage />,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -6,37 +6,27 @@
|
|||
*/
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut, EuiIcon, useEuiTheme } from '@elastic/eui';
|
||||
import { LinkAnchor } from '@kbn/security-solution-navigation/links';
|
||||
import { EuiCallOut, EuiIcon, EuiLink, useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import { useNavigateTo } from '@kbn/security-solution-navigation';
|
||||
|
||||
import { useAddIntegrationsCalloutStyles } from '../styles/add_integrations_callout.styles';
|
||||
import { ADD_INTEGRATIONS_STEP } from './translations';
|
||||
import { AddAndValidateYourDataCardsId, AddIntegrationsSteps, SectionId } from '../types';
|
||||
import { useStepContext } from '../context/step_context';
|
||||
import { AddIntegrationsSteps } from '../types';
|
||||
|
||||
const AddIntegrationsCalloutComponent = ({ stepName }: { stepName?: string }) => {
|
||||
const { calloutWrapperStyles, calloutTitleStyles, calloutAnchorStyles } =
|
||||
useAddIntegrationsCalloutStyles();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { navigateTo } = useNavigateTo();
|
||||
const { onStepClicked } = useStepContext();
|
||||
|
||||
const toggleStep = useCallback(() => {
|
||||
onStepClicked({
|
||||
stepId: AddIntegrationsSteps.connectToDataSources,
|
||||
cardId: AddAndValidateYourDataCardsId.addIntegrations,
|
||||
sectionId: SectionId.addAndValidateYourData,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.landing,
|
||||
path: `#${AddIntegrationsSteps.connectToDataSources}`,
|
||||
});
|
||||
}, [navigateTo, onStepClicked]);
|
||||
}, [navigateTo]);
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
|
@ -55,14 +45,10 @@ const AddIntegrationsCalloutComponent = ({ stepName }: { stepName?: string }) =>
|
|||
defaultMessage="To {stepName} add integrations first {addIntegration}"
|
||||
values={{
|
||||
addIntegration: (
|
||||
<LinkAnchor
|
||||
id={SecurityPageName.landing}
|
||||
onClick={toggleStep}
|
||||
css={calloutAnchorStyles}
|
||||
>
|
||||
<EuiLink onClick={toggleStep} css={calloutAnchorStyles}>
|
||||
{ADD_INTEGRATIONS_STEP}
|
||||
<EuiIcon type="arrowRight" size="s" css={calloutAnchorStyles} />
|
||||
</LinkAnchor>
|
||||
</EuiLink>
|
||||
),
|
||||
stepName: stepName ?? (
|
||||
<FormattedMessage
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEuiBackgroundColor, useEuiShadow, useEuiTheme } from '@elastic/eui';
|
||||
import { useEuiShadow, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const SHADOW_ANIMATION_DURATION = 350;
|
||||
|
@ -13,12 +13,10 @@ export const SHADOW_ANIMATION_DURATION = 350;
|
|||
export const useCardItemStyles = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const shadow = useEuiShadow('l');
|
||||
const iconHoveredBackgroundColor = useEuiBackgroundColor('success');
|
||||
|
||||
return css`
|
||||
&.card-item {
|
||||
padding: ${euiTheme.size.base};
|
||||
margin-bottom: ${euiTheme.size.xs};
|
||||
border-radius: ${euiTheme.size.s};
|
||||
border: 1px solid ${euiTheme.colors.lightShade};
|
||||
box-sizing: content-box;
|
||||
|
@ -27,10 +25,6 @@ export const useCardItemStyles = () => {
|
|||
&.card-expanded {
|
||||
${shadow};
|
||||
transition: box-shadow ${SHADOW_ANIMATION_DURATION}ms ease-out;
|
||||
|
||||
.step-icon {
|
||||
background-color: ${iconHoveredBackgroundColor};
|
||||
}
|
||||
}
|
||||
|
||||
&.card-expanded {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useFooterStyles = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const footerStyles = useMemo(
|
||||
() => ({
|
||||
wrapperStyle: css`
|
||||
padding: ${euiTheme.size.xl} ${euiTheme.size.l} ${euiTheme.base * 4.5}px;
|
||||
gap: ${euiTheme.base * 3.75}px;
|
||||
`,
|
||||
titleStyle: css`
|
||||
font-size: ${euiTheme.base * 0.875}px;
|
||||
font-weight: ${euiTheme.font.weight.semiBold};
|
||||
line-height: ${euiTheme.size.l};
|
||||
color: ${euiTheme.colors.title};
|
||||
`,
|
||||
descriptionStyle: css`
|
||||
font-size: 12.25px;
|
||||
font-weight: ${euiTheme.font.weight.regular};
|
||||
line-height: ${euiTheme.base * 1.25}px;
|
||||
color: ${euiTheme.colors.darkestShade};
|
||||
`,
|
||||
linkStyle: css`
|
||||
font-size: ${euiTheme.size.m};
|
||||
font-weight: ${euiTheme.font.weight.medium};
|
||||
line-height: ${euiTheme.size.base};
|
||||
`,
|
||||
}),
|
||||
[
|
||||
euiTheme.base,
|
||||
euiTheme.colors.darkestShade,
|
||||
euiTheme.colors.title,
|
||||
euiTheme.font.weight.medium,
|
||||
euiTheme.font.weight.regular,
|
||||
euiTheme.font.weight.semiBold,
|
||||
euiTheme.size.base,
|
||||
euiTheme.size.l,
|
||||
euiTheme.size.m,
|
||||
euiTheme.size.xl,
|
||||
]
|
||||
);
|
||||
return footerStyles;
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useProgressBarStyles = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const progressBarStyles = useMemo(
|
||||
() => ({
|
||||
textStyle: css`
|
||||
font-size: 10.5px;
|
||||
font-weight: ${euiTheme.font.weight.bold};
|
||||
text-transform: uppercase;
|
||||
`,
|
||||
}),
|
||||
[euiTheme.font.weight.bold]
|
||||
);
|
||||
return progressBarStyles;
|
||||
};
|
|
@ -55,12 +55,19 @@ export const useStepContentStyles = () => {
|
|||
}
|
||||
`,
|
||||
rightPanelContentStyles: css`
|
||||
&.right-content-panel {
|
||||
&.right-panel-wrapper {
|
||||
height: ${RIGHT_CONTENT_HEIGHT}px;
|
||||
width: ${RIGHT_CONTENT_WIDTH}px;
|
||||
border-radius: ${euiTheme.border.radius.medium};
|
||||
}
|
||||
`,
|
||||
getRightContentStyles: ({ shadow }: { shadow: boolean }) => css`
|
||||
&.right-panel-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
${imageShadow};
|
||||
${shadow ? imageShadow : ''}
|
||||
border-radius: ${euiTheme.border.radius.medium};
|
||||
}
|
||||
`,
|
||||
}),
|
||||
|
|
|
@ -31,7 +31,7 @@ export const useWelcomeHeaderStyles = () => {
|
|||
line-height: ${euiTheme.size.l};
|
||||
`,
|
||||
headerSubtitleStyles: css`
|
||||
font-size: ${euiTheme.size.l};
|
||||
font-size: ${euiTheme.base * 2.125}px;
|
||||
color: ${euiTheme.colors.title};
|
||||
font-weight: ${euiTheme.font.weight.bold};
|
||||
`,
|
||||
|
@ -44,7 +44,7 @@ export const useWelcomeHeaderStyles = () => {
|
|||
currentPlanWrapperStyles: css`
|
||||
background-color: ${euiTheme.colors.lightestShade};
|
||||
border-radius: 56px;
|
||||
padding: ${euiTheme.size.xs} ${euiTheme.size.xs} ${euiTheme.size.xs} ${euiTheme.size.s};
|
||||
padding: ${euiTheme.size.xs} ${euiTheme.size.s} ${euiTheme.size.xs} ${euiTheme.size.m};
|
||||
height: ${euiTheme.size.xl};
|
||||
`,
|
||||
currentPlanTextStyles: css`
|
||||
|
|
|
@ -28,7 +28,6 @@ const TogglePanelComponent: React.FC<{
|
|||
|
||||
const { setUpSections } = useSetUpSections({ euiTheme });
|
||||
const sectionNodes = setUpSections({
|
||||
activeProducts,
|
||||
activeSections,
|
||||
expandedCardSteps,
|
||||
finishedSteps,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue