mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution] Siem migrations Onboarding UI changes (#212560)
## Summary 1/3 of https://github.com/elastic/security-team/issues/11696 **Done** - UI changes in the onboarding cards **Pending** - UI changes in the upload form - UI changes in the translated rules page ### Screenshots **Processing** Old  New  **Results** Old  New  **Connectors** Text changes when the EIS connector is selected https://github.com/user-attachments/assets/f819c379-42a1-4dc8-b320-aa5fd5b7639a
This commit is contained in:
parent
52381cb9bc
commit
b7412d94e7
37 changed files with 790 additions and 283 deletions
|
@ -464,6 +464,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
|
|||
signalsMigrationApi: `${SECURITY_SOLUTION_DOCS}signals-migration-api.html`,
|
||||
legacyEndpointManagementApiDeprecations: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-199598`,
|
||||
legacyRuleManagementBulkApiDeprecations: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-207091`,
|
||||
siemMigrations: `${SECURITY_SOLUTION_DOCS}siem-migration.html`,
|
||||
llmPerformanceMatrix: `${SECURITY_SOLUTION_DOCS}llm-performance-matrix.html`,
|
||||
},
|
||||
query: {
|
||||
eql: `${ELASTICSEARCH_DOCS}eql.html`,
|
||||
|
|
|
@ -325,6 +325,8 @@ export interface DocLinks {
|
|||
readonly signalsMigrationApi: string;
|
||||
readonly legacyEndpointManagementApiDeprecations: string;
|
||||
readonly legacyRuleManagementBulkApiDeprecations: string;
|
||||
readonly siemMigrations: string;
|
||||
readonly llmPerformanceMatrix: string;
|
||||
};
|
||||
readonly query: {
|
||||
readonly eql: string;
|
||||
|
|
|
@ -11,25 +11,31 @@ import { EuiText, useEuiTheme, COLOR_MODES_STANDARD, type EuiTextProps } from '@
|
|||
export interface PanelTextProps extends PropsWithChildren<EuiTextProps> {
|
||||
subdued?: true;
|
||||
semiBold?: true;
|
||||
cursive?: true;
|
||||
}
|
||||
export const PanelText = React.memo<PanelTextProps>(({ children, subdued, semiBold, ...props }) => {
|
||||
const { euiTheme, colorMode } = useEuiTheme();
|
||||
const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;
|
||||
export const PanelText = React.memo<PanelTextProps>(
|
||||
({ children, subdued, semiBold, cursive, ...props }) => {
|
||||
const { euiTheme, colorMode } = useEuiTheme();
|
||||
const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;
|
||||
|
||||
let color;
|
||||
if (subdued && !isDarkMode) {
|
||||
color = 'subdued';
|
||||
let color;
|
||||
if (subdued && !isDarkMode) {
|
||||
color = 'subdued';
|
||||
}
|
||||
|
||||
const style: CSSInterpolation = {};
|
||||
if (semiBold) {
|
||||
style.fontWeight = euiTheme.font.weight.semiBold;
|
||||
}
|
||||
if (cursive) {
|
||||
style.fontStyle = 'italic';
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText {...props} color={color} className={css(style)}>
|
||||
{children}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
|
||||
const style: CSSInterpolation = {};
|
||||
if (semiBold) {
|
||||
style.fontWeight = euiTheme.font.weight.semiBold;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText {...props} color={color} className={css(style)}>
|
||||
{children}
|
||||
</EuiText>
|
||||
);
|
||||
});
|
||||
);
|
||||
PanelText.displayName = 'PanelText';
|
||||
|
|
|
@ -9,12 +9,14 @@ import React from 'react';
|
|||
import type { OnboardingCardConfig } from '../../../../types';
|
||||
import { OnboardingCardId } from '../../../../constants';
|
||||
import { ALERTS_CARD_TITLE } from './translations';
|
||||
import { getCardIcon } from '../common/card_icon';
|
||||
import alertsIcon from './images/alerts_icon.png';
|
||||
import alertsDarkIcon from './images/alerts_icon_dark.png';
|
||||
|
||||
export const alertsCardConfig: OnboardingCardConfig = {
|
||||
id: OnboardingCardId.alerts,
|
||||
title: ALERTS_CARD_TITLE,
|
||||
icon: () => getCardIcon(OnboardingCardId.alerts),
|
||||
icon: alertsIcon,
|
||||
iconDark: alertsDarkIcon,
|
||||
Component: React.lazy(
|
||||
() =>
|
||||
import(
|
||||
|
|
|
@ -12,7 +12,7 @@ export const useCardCallOutStyles = () => {
|
|||
const { euiTheme } = useEuiTheme();
|
||||
return css`
|
||||
padding: ${euiTheme.size.s};
|
||||
border: ${euiTheme.border.width.thin} solid ${euiTheme.colors.backgroundBaseSubdued};
|
||||
border: ${euiTheme.border.width.thin} solid ${euiTheme.colors.borderBaseSubdued};
|
||||
border-radius: ${euiTheme.size.s};
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -1,66 +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 React from 'react';
|
||||
import { useDarkMode } from '@kbn/kibana-react-plugin/public';
|
||||
import { OnboardingCardId } from '../../../../constants';
|
||||
import rulesIcon from '../rules/images/rules_icon.png';
|
||||
import rulesDarkIcon from '../rules/images/rules_icon_dark.png';
|
||||
import integrationsIcon from '../integrations/images/integrations_icon.png';
|
||||
import integrationsDarkIcon from '../integrations/images/integrations_icon_dark.png';
|
||||
import dashboardsIcon from '../dashboards/images/dashboards_icon.png';
|
||||
import dashboardsDarkIcon from '../dashboards/images/dashboards_icon_dark.png';
|
||||
import alertsIcon from '../alerts/images/alerts_icon.png';
|
||||
import alertsDarkIcon from '../alerts/images/alerts_icon_dark.png';
|
||||
import startMigrationIcon from '../siem_migrations/start_migration/images/start_migration_icon.png';
|
||||
import startMigrationDarkIcon from '../siem_migrations/start_migration/images/start_migration_icon_dark.png';
|
||||
|
||||
interface CardIcons {
|
||||
[key: string]: {
|
||||
light: string;
|
||||
dark: string;
|
||||
};
|
||||
}
|
||||
|
||||
const cardIcons: CardIcons = {
|
||||
[OnboardingCardId.rules]: {
|
||||
light: rulesIcon,
|
||||
dark: rulesDarkIcon,
|
||||
},
|
||||
[OnboardingCardId.integrations]: {
|
||||
light: integrationsIcon,
|
||||
dark: integrationsDarkIcon,
|
||||
},
|
||||
[OnboardingCardId.dashboards]: {
|
||||
light: dashboardsIcon,
|
||||
dark: dashboardsDarkIcon,
|
||||
},
|
||||
[OnboardingCardId.alerts]: {
|
||||
light: alertsIcon,
|
||||
dark: alertsDarkIcon,
|
||||
},
|
||||
[OnboardingCardId.siemMigrationsStart]: {
|
||||
light: startMigrationIcon,
|
||||
dark: startMigrationDarkIcon,
|
||||
},
|
||||
};
|
||||
|
||||
interface CardIconProps {
|
||||
cardId: OnboardingCardId;
|
||||
}
|
||||
|
||||
export const CardIcon = React.memo<CardIconProps>(({ cardId }) => {
|
||||
const isDarkMode = useDarkMode();
|
||||
const icon = cardIcons[cardId]?.[isDarkMode ? 'dark' : 'light'] || '';
|
||||
|
||||
if (!icon) return null;
|
||||
|
||||
return <img src={icon} alt={`${cardId}-card-icon`} width={24} height={24} />;
|
||||
});
|
||||
|
||||
CardIcon.displayName = 'CardIcon';
|
||||
|
||||
export const getCardIcon = (cardId: OnboardingCardId) => <CardIcon cardId={cardId} />;
|
|
@ -33,10 +33,18 @@ export const ConnectorSelectorPanel = React.memo<ConnectorSelectorPanelProps>(
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectors.length === 1) {
|
||||
if (selectedConnectorId || !connectors.length) {
|
||||
return;
|
||||
}
|
||||
const inferenceConnector = connectors.find(
|
||||
({ actionTypeId }) => actionTypeId === '.inference'
|
||||
);
|
||||
if (inferenceConnector) {
|
||||
onConnectorSelected(inferenceConnector);
|
||||
} else if (connectors.length === 1) {
|
||||
onConnectorSelected(connectors[0]);
|
||||
}
|
||||
}, [connectors, onConnectorSelected]);
|
||||
}, [selectedConnectorId, connectors, onConnectorSelected]);
|
||||
|
||||
const connectorOptions = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -9,12 +9,14 @@ import React from 'react';
|
|||
import type { OnboardingCardConfig } from '../../../../types';
|
||||
import { OnboardingCardId } from '../../../../constants';
|
||||
import { DASHBOARDS_CARD_TITLE } from './translations';
|
||||
import { getCardIcon } from '../common/card_icon';
|
||||
import dashboardsIcon from './images/dashboards_icon.png';
|
||||
import dashboardsDarkIcon from './images/dashboards_icon_dark.png';
|
||||
|
||||
export const dashboardsCardConfig: OnboardingCardConfig = {
|
||||
id: OnboardingCardId.dashboards,
|
||||
title: DASHBOARDS_CARD_TITLE,
|
||||
icon: () => getCardIcon(OnboardingCardId.dashboards),
|
||||
icon: dashboardsIcon,
|
||||
iconDark: dashboardsDarkIcon,
|
||||
Component: React.lazy(
|
||||
() =>
|
||||
import(
|
||||
|
@ -22,5 +24,5 @@ export const dashboardsCardConfig: OnboardingCardConfig = {
|
|||
'./dashboards_card'
|
||||
)
|
||||
),
|
||||
capabilitiesRequired: ['dashboard_v2.show'],
|
||||
capabilitiesRequired: 'dashboard_v2.show',
|
||||
};
|
||||
|
|
|
@ -11,14 +11,16 @@ import type { OnboardingCardConfig } from '../../../../types';
|
|||
import { checkIntegrationsCardComplete } from './integrations_check_complete';
|
||||
import { OnboardingCardId } from '../../../../constants';
|
||||
import type { IntegrationCardMetadata } from './types';
|
||||
import { getCardIcon } from '../common/card_icon';
|
||||
import integrationsIcon from './images/integrations_icon.png';
|
||||
import integrationsDarkIcon from './images/integrations_icon_dark.png';
|
||||
|
||||
export const integrationsCardConfig: OnboardingCardConfig<IntegrationCardMetadata> = {
|
||||
id: OnboardingCardId.integrations,
|
||||
title: i18n.translate('xpack.securitySolution.onboarding.integrationsCard.title', {
|
||||
defaultMessage: 'Add data with integrations',
|
||||
}),
|
||||
icon: () => getCardIcon(OnboardingCardId.integrations),
|
||||
icon: integrationsIcon,
|
||||
iconDark: integrationsDarkIcon,
|
||||
Component: React.lazy(
|
||||
() =>
|
||||
import(
|
||||
|
|
|
@ -10,12 +10,14 @@ import type { OnboardingCardConfig } from '../../../../types';
|
|||
import { OnboardingCardId } from '../../../../constants';
|
||||
import { RULES_CARD_TITLE } from './translations';
|
||||
import { checkRulesComplete } from './rules_check_complete';
|
||||
import { getCardIcon } from '../common/card_icon';
|
||||
import rulesIcon from './images/rules_icon.png';
|
||||
import rulesDarkIcon from './images/rules_icon_dark.png';
|
||||
|
||||
export const rulesCardConfig: OnboardingCardConfig = {
|
||||
id: OnboardingCardId.rules,
|
||||
title: RULES_CARD_TITLE,
|
||||
icon: () => getCardIcon(OnboardingCardId.rules),
|
||||
icon: rulesIcon,
|
||||
iconDark: rulesDarkIcon,
|
||||
Component: React.lazy(
|
||||
() =>
|
||||
import(
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CenteredLoadingSpinner } from '../../../../../../common/components/centered_loading_spinner';
|
||||
import { useKibana } from '../../../../../../common/lib/kibana/kibana_react';
|
||||
import { useDefinedLocalStorage } from '../../../../hooks/use_stored_state';
|
||||
|
@ -19,6 +20,26 @@ import { ConnectorsMissingPrivilegesCallOut } from '../../common/connectors/miss
|
|||
import type { AIConnector } from '../../common/connectors/types';
|
||||
import type { AIConnectorCardMetadata } from './types';
|
||||
|
||||
const LlmPerformanceMatrixDocsLink = React.memo<{ text: string }>(({ text }) => {
|
||||
const { llmPerformanceMatrix } = useKibana().services.docLinks.links.securitySolution;
|
||||
return (
|
||||
<EuiLink href={llmPerformanceMatrix} target="_blank">
|
||||
{text}
|
||||
</EuiLink>
|
||||
);
|
||||
});
|
||||
LlmPerformanceMatrixDocsLink.displayName = 'LlmPerformanceMatrixDocsLink';
|
||||
|
||||
const SiemMigrationDocsLink = React.memo<{ text: string }>(({ text }) => {
|
||||
const { siemMigrations } = useKibana().services.docLinks.links.securitySolution;
|
||||
return (
|
||||
<EuiLink href={siemMigrations} target="_blank">
|
||||
{text}
|
||||
</EuiLink>
|
||||
);
|
||||
});
|
||||
SiemMigrationDocsLink.displayName = 'SiemMigrationDocsLink';
|
||||
|
||||
export const AIConnectorCard: OnboardingCardComponent<AIConnectorCardMetadata> = ({
|
||||
checkCompleteMetadata,
|
||||
checkComplete,
|
||||
|
@ -38,6 +59,14 @@ export const AIConnectorCard: OnboardingCardComponent<AIConnectorCardMetadata> =
|
|||
[setComplete, setStoredConnectorId, siemMigrations]
|
||||
);
|
||||
|
||||
const isInferenceConnector = useMemo(() => {
|
||||
if (!checkCompleteMetadata?.connectors?.length || !storedConnectorId) {
|
||||
return false;
|
||||
}
|
||||
const connector = checkCompleteMetadata.connectors.find((c) => c.id === storedConnectorId);
|
||||
return connector?.actionTypeId === '.inference' ?? false;
|
||||
}, [checkCompleteMetadata, storedConnectorId]);
|
||||
|
||||
if (!checkCompleteMetadata) {
|
||||
return (
|
||||
<OnboardingCardContentPanel>
|
||||
|
@ -53,7 +82,28 @@ export const AIConnectorCard: OnboardingCardComponent<AIConnectorCardMetadata> =
|
|||
{canExecuteConnectors ? (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CardSubduedText size="s">{i18n.AI_CONNECTOR_CARD_DESCRIPTION}</CardSubduedText>
|
||||
<CardSubduedText size="s">
|
||||
{i18n.AI_CONNECTOR_CARD_DESCRIPTION_START}
|
||||
{isInferenceConnector ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.onboarding.aiConnectorCardInferenceDescription"
|
||||
defaultMessage="The Elastic-provided connector is selected by default. You can configure another connector and model if you prefer. Learn more about {docsLink} and performance with our {llmMatrixLink}"
|
||||
values={{
|
||||
llmMatrixLink: <LlmPerformanceMatrixDocsLink text={i18n.LLM_MATRIX_LINK} />,
|
||||
docsLink: <SiemMigrationDocsLink text={i18n.AI_POWERED_MIGRATIONS_LINK} />,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.onboarding.aiConnectorCardNotInferenceDescription"
|
||||
defaultMessage="Refer to the {llmMatrixLink} for information about which models perform best. {docsLink} about AI-powered SIEM migration."
|
||||
values={{
|
||||
llmMatrixLink: <LlmPerformanceMatrixDocsLink text={i18n.LLM_MATRIX_LINK} />,
|
||||
docsLink: <SiemMigrationDocsLink text={i18n.LEARN_MORE_LINK} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</CardSubduedText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ConnectorCards
|
||||
|
|
|
@ -14,10 +14,30 @@ export const AI_CONNECTOR_CARD_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const AI_CONNECTOR_CARD_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.description',
|
||||
export const AI_CONNECTOR_CARD_DESCRIPTION_START = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.descriptionStart',
|
||||
{ defaultMessage: 'This feature relies on an AI connector for rule translation. ' }
|
||||
);
|
||||
|
||||
export const AI_CONNECTOR_CARD_DESCRIPTION_INFERENCE_CONNECTOR = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.descriptionInferenceConnector',
|
||||
{
|
||||
defaultMessage:
|
||||
'Choose and configure any AI provider available to start a SIEM rules migration.',
|
||||
'The Elastic-provided connector is selected by default. You can configure another connector and model if you prefer. ',
|
||||
}
|
||||
);
|
||||
|
||||
export const LLM_MATRIX_LINK = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.llmMatrixLink',
|
||||
{ defaultMessage: 'LLM performance matrix' }
|
||||
);
|
||||
|
||||
export const AI_POWERED_MIGRATIONS_LINK = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.siemMigrationLink',
|
||||
{ defaultMessage: 'AI-powered SIEM migration' }
|
||||
);
|
||||
|
||||
export const LEARN_MORE_LINK = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.aiConnector.learnMoreLink',
|
||||
{ defaultMessage: 'Learn more' }
|
||||
);
|
||||
|
|
|
@ -11,12 +11,15 @@ import { OnboardingCardId } from '../../../../../constants';
|
|||
import { START_MIGRATION_CARD_TITLE } from './translations';
|
||||
import type { StartMigrationCardMetadata } from './types';
|
||||
import { checkStartMigrationCardComplete } from './start_migration_check_complete';
|
||||
import { getCardIcon } from '../../common/card_icon';
|
||||
import startMigrationIcon from './images/start_migration_icon.png';
|
||||
import startMigrationDarkIcon from './images/start_migration_icon_dark.png';
|
||||
|
||||
export const startMigrationCardConfig: OnboardingCardConfig<StartMigrationCardMetadata> = {
|
||||
id: OnboardingCardId.siemMigrationsStart,
|
||||
id: OnboardingCardId.siemMigrationsRules,
|
||||
title: START_MIGRATION_CARD_TITLE,
|
||||
icon: () => getCardIcon(OnboardingCardId.siemMigrationsStart),
|
||||
badge: 'tech_preview',
|
||||
icon: startMigrationIcon,
|
||||
iconDark: startMigrationDarkIcon,
|
||||
Component: React.lazy(
|
||||
() =>
|
||||
import(
|
||||
|
|
|
@ -27,13 +27,13 @@ import {
|
|||
import { UploadRulesSectionPanel } from './upload_rules_panel';
|
||||
|
||||
const StartMigrationsBody: OnboardingCardComponent = React.memo(
|
||||
({ setComplete, isCardComplete, setExpandedCardId }) => {
|
||||
({ setComplete, isCardComplete, setExpandedCardId, checkComplete }) => {
|
||||
const styles = useStyles();
|
||||
const { data: migrationsStats, isLoading, refreshStats } = useLatestStats();
|
||||
|
||||
useEffect(() => {
|
||||
// Set card complete if any migration is finished
|
||||
if (!isCardComplete(OnboardingCardId.siemMigrationsStart) && migrationsStats) {
|
||||
if (!isCardComplete(OnboardingCardId.siemMigrationsRules) && migrationsStats) {
|
||||
if (migrationsStats.some(({ status }) => status === SiemMigrationTaskStatus.FINISHED)) {
|
||||
setComplete(true);
|
||||
}
|
||||
|
@ -49,13 +49,14 @@ const StartMigrationsBody: OnboardingCardComponent = React.memo(
|
|||
setExpandedCardId(OnboardingCardId.siemMigrationsAiConnectors);
|
||||
}, [setExpandedCardId]);
|
||||
|
||||
const onFlyoutClosed = useCallback(() => {
|
||||
refreshStats();
|
||||
checkComplete();
|
||||
}, [refreshStats, checkComplete]);
|
||||
|
||||
return (
|
||||
<RuleMigrationDataInputWrapper onFlyoutClosed={refreshStats}>
|
||||
<OnboardingCardContentPanel
|
||||
data-test-subj="StartMigrationsCardBody"
|
||||
paddingSize="none"
|
||||
className={styles}
|
||||
>
|
||||
<RuleMigrationDataInputWrapper onFlyoutClosed={onFlyoutClosed}>
|
||||
<OnboardingCardContentPanel data-test-subj="StartMigrationsCardBody" className={styles}>
|
||||
{isLoading ? (
|
||||
<CenteredLoadingSpinner />
|
||||
) : (
|
||||
|
@ -66,7 +67,7 @@ const StartMigrationsBody: OnboardingCardComponent = React.memo(
|
|||
/>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<PanelText size="xs" subdued>
|
||||
<PanelText size="xs" subdued cursive>
|
||||
<p>{i18n.START_MIGRATION_CARD_FOOTER_NOTE}</p>
|
||||
</PanelText>
|
||||
</OnboardingCardContentPanel>
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { SiemMigrationsService } from '../../../../../../siem_migrations/se
|
|||
import { checkStartMigrationCardComplete } from './start_migration_check_complete';
|
||||
|
||||
describe('startMigrationCheckComplete', () => {
|
||||
test('should return default values if siem migrations are not available', async () => {
|
||||
it('should return default values if siem migrations are not available', async () => {
|
||||
// Arrange
|
||||
const siemMigrations = {
|
||||
rules: {
|
||||
|
@ -26,10 +26,14 @@ describe('startMigrationCheckComplete', () => {
|
|||
};
|
||||
const result = await checkStartMigrationCardComplete(services);
|
||||
|
||||
expect(result).toEqual({ isComplete: false, metadata: { missingCapabilities: [] } });
|
||||
expect(result).toEqual({
|
||||
completeBadgeText: '0 migrations',
|
||||
isComplete: false,
|
||||
metadata: { missingCapabilities: [] },
|
||||
});
|
||||
});
|
||||
|
||||
test('should query Stats if siem migrations are available', async () => {
|
||||
it('should query Stats if siem migrations are available', async () => {
|
||||
const siemMigrations = {
|
||||
rules: {
|
||||
getMissingCapabilities: jest.fn().mockReturnValue([]),
|
||||
|
@ -52,6 +56,7 @@ describe('startMigrationCheckComplete', () => {
|
|||
expect(siemMigrations.rules.getRuleMigrationsStats).toHaveBeenCalled();
|
||||
|
||||
expect(result).toEqual({
|
||||
completeBadgeText: '1 migration',
|
||||
isComplete: true,
|
||||
metadata: { missingCapabilities: [] },
|
||||
});
|
||||
|
|
|
@ -5,11 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SiemMigrationTaskStatus } from '../../../../../../../common/siem_migrations/constants';
|
||||
|
||||
import type { OnboardingCardCheckComplete } from '../../../../../types';
|
||||
import type { StartMigrationCardMetadata } from './types';
|
||||
|
||||
const COMPLETE_BADGE_TEXT = (migrationsCount: number) =>
|
||||
i18n.translate('xpack.securitySolution.onboarding.siemMigrations.startMigration.completeBadge', {
|
||||
defaultMessage:
|
||||
'{migrationsCount} {migrationsCount, plural, one {migration} other {migrations}}',
|
||||
values: { migrationsCount },
|
||||
});
|
||||
|
||||
export const checkStartMigrationCardComplete: OnboardingCardCheckComplete<
|
||||
StartMigrationCardMetadata
|
||||
> = async ({ siemMigrations }) => {
|
||||
|
@ -18,12 +26,19 @@ export const checkStartMigrationCardComplete: OnboardingCardCheckComplete<
|
|||
.map(({ description }) => description);
|
||||
|
||||
let isComplete = false;
|
||||
let migrationsCount = 0;
|
||||
|
||||
if (siemMigrations.rules.isAvailable()) {
|
||||
const migrationsStats = await siemMigrations.rules.getRuleMigrationsStats();
|
||||
isComplete = migrationsStats.some(
|
||||
(migrationStats) => migrationStats.status === SiemMigrationTaskStatus.FINISHED
|
||||
);
|
||||
migrationsCount = migrationsStats.length;
|
||||
}
|
||||
return { isComplete, metadata: { missingCapabilities } };
|
||||
|
||||
return {
|
||||
isComplete,
|
||||
completeBadgeText: COMPLETE_BADGE_TEXT(migrationsCount),
|
||||
metadata: { missingCapabilities },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
export const START_MIGRATION_CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.startMigration.title',
|
||||
{ defaultMessage: 'Translate your existing SIEM Rules to Elastic' }
|
||||
{ defaultMessage: 'Migrate your existing Splunk® SIEM rules to Elastic' }
|
||||
);
|
||||
export const START_MIGRATION_CARD_FOOTER_NOTE = i18n.translate(
|
||||
'xpack.securitySolution.onboarding.startMigration.footerNote',
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiIcon,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiPanel,
|
||||
|
@ -45,7 +44,7 @@ export const UploadRulesSectionPanel = React.memo<UploadRulesSectionPanelProps>(
|
|||
gutterSize={isUploadMore ? 'm' : 'l'}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={SiemMigrationsIcon} className="siemMigrationsIcon" />
|
||||
<SiemMigrationsIcon className="siemMigrationsIcon" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isUploadMore ? (
|
||||
|
|
|
@ -10,6 +10,7 @@ import { OnboardingBody } from './onboarding_body';
|
|||
import { useBodyConfig } from './hooks/use_body_config';
|
||||
import { useExpandedCard } from './hooks/use_expanded_card';
|
||||
import { useCompletedCards } from './hooks/use_completed_cards';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
jest.mock('../onboarding_context');
|
||||
jest.mock('./hooks/use_body_config');
|
||||
|
@ -58,14 +59,14 @@ describe('OnboardingBody Component', () => {
|
|||
});
|
||||
|
||||
it('should render the OnboardingBody component with the correct content', () => {
|
||||
render(<OnboardingBody />);
|
||||
render(<OnboardingBody />, { wrapper: TestProviders });
|
||||
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Card 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('when the card is expanded', () => {
|
||||
beforeEach(() => {
|
||||
render(<OnboardingBody />);
|
||||
render(<OnboardingBody />, { wrapper: TestProviders });
|
||||
fireEvent.click(screen.getByText('Card 1'));
|
||||
});
|
||||
|
||||
|
@ -85,7 +86,7 @@ describe('OnboardingBody Component', () => {
|
|||
setExpandedCardId: mockSetExpandedCardId,
|
||||
});
|
||||
|
||||
render(<OnboardingBody />);
|
||||
render(<OnboardingBody />, { wrapper: TestProviders });
|
||||
|
||||
fireEvent.click(screen.getByText('Card 1'));
|
||||
});
|
||||
|
@ -112,7 +113,7 @@ describe('OnboardingBody Component', () => {
|
|||
setExpandedCardId: mockSetExpandedCardId,
|
||||
});
|
||||
|
||||
render(<OnboardingBody />);
|
||||
render(<OnboardingBody />, { wrapper: TestProviders });
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByText('Card 1'));
|
||||
});
|
||||
|
@ -136,7 +137,7 @@ describe('OnboardingBody Component', () => {
|
|||
setExpandedCardId: mockSetExpandedCardId,
|
||||
});
|
||||
|
||||
render(<OnboardingBody />);
|
||||
render(<OnboardingBody />, { wrapper: TestProviders });
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByText('Card 1'));
|
||||
});
|
||||
|
|
|
@ -61,7 +61,8 @@ export const OnboardingBody = React.memo(() => {
|
|||
<EuiSpacer size="xxl" />
|
||||
<OnboardingCardGroup title={group.title}>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{group.cards.map(({ id, title, icon, Component: LazyCardComponent }) => {
|
||||
{group.cards.map((card) => {
|
||||
const { id, title, icon, iconDark, badge, Component: LazyCardComponent } = card;
|
||||
const cardCheckCompleteResult = getCardCheckCompleteResult(id);
|
||||
return (
|
||||
<EuiFlexItem key={id} grow={false}>
|
||||
|
@ -69,6 +70,8 @@ export const OnboardingBody = React.memo(() => {
|
|||
id={id}
|
||||
title={title}
|
||||
icon={icon}
|
||||
iconDark={iconDark}
|
||||
badge={badge}
|
||||
checkCompleteResult={cardCheckCompleteResult}
|
||||
isExpanded={expandedCardId === id}
|
||||
isComplete={isCardComplete(id)}
|
||||
|
|
|
@ -10,12 +10,26 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|||
import { OnboardingCardPanel } from './onboarding_card_panel';
|
||||
import { CARD_COMPLETE_BADGE, EXPAND_CARD_BUTTON_LABEL } from './translations';
|
||||
import type { OnboardingCardId } from '../../constants';
|
||||
import { TestProviders } from '../../../common/mock/test_providers';
|
||||
|
||||
const mockUseDarkMode = jest.fn(() => false);
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
||||
...jest.requireActual('@kbn/kibana-react-plugin/public'),
|
||||
useDarkMode: () => mockUseDarkMode(),
|
||||
}));
|
||||
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
...jest.requireActual('@elastic/eui'),
|
||||
EuiIcon: jest.fn(({ type }: { type: string }) => <div data-test-subj={`EuiIcon-${type}`} />),
|
||||
}));
|
||||
|
||||
describe('OnboardingCardPanel Component', () => {
|
||||
const defaultProps = {
|
||||
id: 'card-1' as OnboardingCardId,
|
||||
title: 'Test Card',
|
||||
icon: 'testIcon',
|
||||
iconDark: undefined,
|
||||
badge: undefined,
|
||||
isExpanded: false,
|
||||
isComplete: false,
|
||||
onToggleExpanded: jest.fn(),
|
||||
|
@ -29,7 +43,8 @@ describe('OnboardingCardPanel Component', () => {
|
|||
render(
|
||||
<OnboardingCardPanel {...defaultProps}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
// Verify that the card title and icon are rendered
|
||||
|
@ -40,7 +55,8 @@ describe('OnboardingCardPanel Component', () => {
|
|||
render(
|
||||
<OnboardingCardPanel {...defaultProps} isComplete={true}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
// Verify that the complete badge is displayed
|
||||
|
@ -51,7 +67,8 @@ describe('OnboardingCardPanel Component', () => {
|
|||
render(
|
||||
<OnboardingCardPanel {...defaultProps} isComplete={false}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
// Verify that the complete badge is not displayed
|
||||
|
@ -62,7 +79,8 @@ describe('OnboardingCardPanel Component', () => {
|
|||
render(
|
||||
<OnboardingCardPanel {...defaultProps}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
// Click on the card header
|
||||
|
@ -76,7 +94,8 @@ describe('OnboardingCardPanel Component', () => {
|
|||
const { rerender } = render(
|
||||
<OnboardingCardPanel {...defaultProps} isExpanded={false}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
// Check the button icon when card is not expanded
|
||||
|
@ -94,4 +113,49 @@ describe('OnboardingCardPanel Component', () => {
|
|||
// Check the button icon when card is expanded
|
||||
expect(buttonIcon).toHaveAttribute('aria-expanded', 'true');
|
||||
});
|
||||
|
||||
describe('when badge is defined', () => {
|
||||
it('should render the badge', () => {
|
||||
render(
|
||||
<OnboardingCardPanel {...defaultProps} badge={'beta'}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('onboardingCardBadge')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when iconDark is defined', () => {
|
||||
const iconDark = 'testIconDark';
|
||||
|
||||
it('should render the dark icon with the dark theme', () => {
|
||||
mockUseDarkMode.mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<OnboardingCardPanel {...defaultProps} iconDark={iconDark}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('EuiIcon-testIconDark')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('EuiIcon-testIcon')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the dark icon with the light theme', () => {
|
||||
mockUseDarkMode.mockReturnValue(false);
|
||||
|
||||
render(
|
||||
<OnboardingCardPanel {...defaultProps} iconDark={iconDark}>
|
||||
<div>{'Test Card Content'}</div>
|
||||
</OnboardingCardPanel>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('EuiIcon-testIconDark')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('EuiIcon-testIcon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
import React, { useMemo, type PropsWithChildren } from 'react';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import {
|
||||
EuiPanel,
|
||||
|
@ -17,16 +17,20 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
import { useDarkMode } from '@kbn/kibana-react-plugin/public';
|
||||
import type { OnboardingCardId } from '../../constants';
|
||||
import type { CheckCompleteResult } from '../../types';
|
||||
import type { CheckCompleteResult, CardBadge } from '../../types';
|
||||
import { CARD_COMPLETE_BADGE, EXPAND_CARD_BUTTON_LABEL } from './translations';
|
||||
import { useCardPanelStyles } from './onboarding_card_panel.styles';
|
||||
import { useDelayedVisibility } from './hooks/use_delayed_visibility';
|
||||
import { OnboardingCardBadge } from './onboarding_card_panel_badge';
|
||||
|
||||
interface OnboardingCardPanelProps {
|
||||
id: OnboardingCardId;
|
||||
title: string;
|
||||
icon: IconType;
|
||||
iconDark: IconType | undefined;
|
||||
badge: CardBadge | undefined;
|
||||
isExpanded: boolean;
|
||||
isComplete: boolean;
|
||||
onToggleExpanded: () => void;
|
||||
|
@ -38,6 +42,8 @@ export const OnboardingCardPanel = React.memo<PropsWithChildren<OnboardingCardPa
|
|||
id,
|
||||
title,
|
||||
icon,
|
||||
iconDark,
|
||||
badge,
|
||||
isExpanded,
|
||||
isComplete,
|
||||
onToggleExpanded,
|
||||
|
@ -49,6 +55,12 @@ export const OnboardingCardPanel = React.memo<PropsWithChildren<OnboardingCardPa
|
|||
'onboardingCardPanel-expanded': isExpanded,
|
||||
'onboardingCardPanel-completed': isComplete,
|
||||
});
|
||||
const isDarkMode = useDarkMode();
|
||||
const iconType = useMemo(
|
||||
() => (iconDark && isDarkMode ? iconDark : icon),
|
||||
[isDarkMode, iconDark, icon]
|
||||
);
|
||||
|
||||
const isContentVisible = useDelayedVisibility({ isExpanded });
|
||||
|
||||
return (
|
||||
|
@ -70,13 +82,22 @@ export const OnboardingCardPanel = React.memo<PropsWithChildren<OnboardingCardPa
|
|||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span className="onboardingCardIcon">
|
||||
<EuiIcon type={icon} size="l" />
|
||||
<EuiIcon type={iconType} size="l" />
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs" className="onboardingCardHeaderTitle">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" direction="row">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xxs" className="onboardingCardHeaderTitle">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{badge && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<OnboardingCardBadge badge={badge} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{checkCompleteResult?.additionalBadges?.map((additionalBadge, index) => (
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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, { type PropsWithChildren } from 'react';
|
||||
import { EuiBadge, EuiBetaBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CardBadge } from '../../types';
|
||||
|
||||
const label = {
|
||||
beta: i18n.translate('xpack.securitySolution.onboarding.cardBadge.beta', {
|
||||
defaultMessage: 'Beta',
|
||||
}),
|
||||
techPreview: i18n.translate('xpack.securitySolution.onboarding.cardBadge.techPreview', {
|
||||
defaultMessage: 'Technical Preview',
|
||||
}),
|
||||
};
|
||||
const tooltip = {
|
||||
beta: i18n.translate('xpack.securitySolution.onboarding.cardBadge.betaTooltip', {
|
||||
defaultMessage: 'This feature is in beta and is not recommended for production use.',
|
||||
}),
|
||||
techPreview: i18n.translate('xpack.securitySolution.onboarding.cardBadge.techPreviewTooltip', {
|
||||
defaultMessage: 'This feature is in technical preview and is subject to change.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const OnboardingCardBadge = React.memo<PropsWithChildren<{ badge: CardBadge }>>(
|
||||
({ badge }) => {
|
||||
if (badge === 'beta') {
|
||||
return (
|
||||
<EuiBetaBadge
|
||||
label={label.beta}
|
||||
iconType="beta"
|
||||
tooltipContent={tooltip.beta}
|
||||
data-test-subj="onboardingCardBadge"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (badge === 'tech_preview') {
|
||||
return (
|
||||
<EuiBetaBadge
|
||||
label={label.techPreview}
|
||||
iconType="beaker"
|
||||
tooltipContent={tooltip.techPreview}
|
||||
data-test-subj="onboardingCardBadge"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <EuiBadge {...badge} data-test-subj="onboardingCardBadge" />;
|
||||
}
|
||||
);
|
||||
OnboardingCardBadge.displayName = 'OnboardingCardBadge';
|
|
@ -8,11 +8,9 @@
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
import { useKibana } from '../../common/lib/kibana/kibana_react';
|
||||
import type { OnboardingTopicId, OnboardingCardId } from '../constants';
|
||||
import { OnboardingHubEventTypes } from '../../common/lib/telemetry';
|
||||
import type { OnboardingTopicId } from '../constants';
|
||||
import { useLicense } from '../../common/hooks/use_license';
|
||||
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
|
||||
|
||||
import { hasCapabilities } from '../../common/lib/capabilities';
|
||||
import type {
|
||||
OnboardingConfigAvailabilityProps,
|
||||
|
@ -20,12 +18,7 @@ import type {
|
|||
TopicConfig,
|
||||
} from '../types';
|
||||
import { onboardingConfig } from '../config';
|
||||
|
||||
export interface OnboardingTelemetry {
|
||||
reportCardOpen: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
|
||||
reportCardComplete: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
|
||||
reportCardLinkClicked: (cardId: OnboardingCardId, linkId: string) => void;
|
||||
}
|
||||
import { useOnboardingTelemetry, type OnboardingTelemetry } from './onboarding_telemetry';
|
||||
|
||||
export type OnboardingConfig = Map<OnboardingTopicId, TopicConfig>;
|
||||
export interface OnboardingContextValue {
|
||||
|
@ -116,30 +109,3 @@ const useFilteredConfig = (): OnboardingConfig => {
|
|||
|
||||
return filteredConfig;
|
||||
};
|
||||
|
||||
const useOnboardingTelemetry = (): OnboardingTelemetry => {
|
||||
const { telemetry } = useKibana().services;
|
||||
return useMemo(
|
||||
() => ({
|
||||
reportCardOpen: (cardId, { auto = false } = {}) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepOpen, {
|
||||
stepId: cardId,
|
||||
trigger: auto ? 'navigation' : 'click',
|
||||
});
|
||||
},
|
||||
reportCardComplete: (cardId, { auto = false } = {}) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepFinished, {
|
||||
stepId: cardId,
|
||||
trigger: auto ? 'auto_check' : 'click',
|
||||
});
|
||||
},
|
||||
reportCardLinkClicked: (cardId, linkId: string) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepLinkClicked, {
|
||||
originStepId: cardId,
|
||||
stepLinkId: linkId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
[telemetry]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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';
|
||||
import { useOnboardingTelemetry } from './onboarding_telemetry';
|
||||
import { useKibana } from '../../common/lib/kibana/kibana_react';
|
||||
import { OnboardingHubEventTypes } from '../../common/lib/telemetry';
|
||||
import type { OnboardingCardId } from '../constants';
|
||||
|
||||
jest.mock('../config', () => ({
|
||||
onboardingConfig: [
|
||||
{ id: 'default', body: [{ cards: [{ id: 'testCard' }] }] },
|
||||
{ id: 'testTopic', body: [{ cards: [{ id: 'testCard2' }] }] },
|
||||
],
|
||||
}));
|
||||
|
||||
jest.mock('../../common/lib/kibana/kibana_react');
|
||||
const telemetryMock = { reportEvent: jest.fn() };
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: { telemetry: telemetryMock },
|
||||
});
|
||||
|
||||
describe('useOnboardingTelemetry', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when opening a card', () => {
|
||||
it('should report card open event on default topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardOpen('testCard' as OnboardingCardId);
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepOpen,
|
||||
{ stepId: 'testCard', trigger: 'click' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should report card open event on another topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardOpen('testCard2' as OnboardingCardId);
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepOpen,
|
||||
{ stepId: 'testTopic#testCard2', trigger: 'click' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should report card auto open event', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardOpen('testCard' as OnboardingCardId, { auto: true });
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepOpen,
|
||||
{ stepId: 'testCard', trigger: 'navigation' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when completing a card', () => {
|
||||
it('should report card complete event on the default topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardComplete('testCard' as OnboardingCardId);
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepFinished,
|
||||
{ stepId: 'testCard', trigger: 'click' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should report card complete event on the another topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardComplete('testCard2' as OnboardingCardId);
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepFinished,
|
||||
{ stepId: 'testTopic#testCard2', trigger: 'click' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should report card auto complete event', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardComplete('testCard' as OnboardingCardId, { auto: true });
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepFinished,
|
||||
{ stepId: 'testCard', trigger: 'auto_check' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when clicking a card link', () => {
|
||||
it('should report card link clicked event on the default topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardLinkClicked('testCard' as OnboardingCardId, 'link1');
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepLinkClicked,
|
||||
{ originStepId: 'testCard', stepLinkId: 'link1' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should report card link clicked event on another topic', () => {
|
||||
const { result } = renderHook(useOnboardingTelemetry);
|
||||
result.current.reportCardLinkClicked('testCard2' as OnboardingCardId, 'link1');
|
||||
|
||||
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
|
||||
OnboardingHubEventTypes.OnboardingHubStepLinkClicked,
|
||||
{ originStepId: 'testTopic#testCard2', stepLinkId: 'link1' }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useKibana } from '../../common/lib/kibana/kibana_react';
|
||||
import type { OnboardingCardId } from '../constants';
|
||||
import { OnboardingTopicId } from '../constants';
|
||||
import { OnboardingHubEventTypes } from '../../common/lib/telemetry';
|
||||
import { onboardingConfig } from '../config';
|
||||
|
||||
export interface OnboardingTelemetry {
|
||||
reportCardOpen: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
|
||||
reportCardComplete: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
|
||||
reportCardLinkClicked: (cardId: OnboardingCardId, linkId: string) => void;
|
||||
}
|
||||
|
||||
export const useOnboardingTelemetry = (): OnboardingTelemetry => {
|
||||
const { telemetry } = useKibana().services;
|
||||
return useMemo(
|
||||
() => ({
|
||||
reportCardOpen: (cardId, { auto = false } = {}) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepOpen, {
|
||||
stepId: getStepId(cardId),
|
||||
trigger: auto ? 'navigation' : 'click',
|
||||
});
|
||||
},
|
||||
reportCardComplete: (cardId, { auto = false } = {}) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepFinished, {
|
||||
stepId: getStepId(cardId),
|
||||
trigger: auto ? 'auto_check' : 'click',
|
||||
});
|
||||
},
|
||||
reportCardLinkClicked: (cardId, linkId: string) => {
|
||||
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepLinkClicked, {
|
||||
originStepId: getStepId(cardId),
|
||||
stepLinkId: linkId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
[telemetry]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the step id for a given card id.
|
||||
* The stepId is used to track the onboarding card in telemetry, it is a combination of the topic id and the card id.
|
||||
* To keep backwards compatibility, if the card is in the default topic, the stepId will be the card id only.
|
||||
*/
|
||||
const getStepId = (cardId: OnboardingCardId) => {
|
||||
const cardTopic = onboardingConfig.find((topic) =>
|
||||
topic.body.some((group) => group.cards.some((card) => card.id === cardId))
|
||||
);
|
||||
if (!cardTopic || cardTopic.id === OnboardingTopicId.default) {
|
||||
return cardId; // Do not add topic id for default topic to preserve existing events format
|
||||
}
|
||||
return `${cardTopic.id}#${cardId}`;
|
||||
};
|
|
@ -21,5 +21,5 @@ export enum OnboardingCardId {
|
|||
|
||||
// siem_migrations topic cards
|
||||
siemMigrationsAiConnectors = 'ai_connectors',
|
||||
siemMigrationsStart = 'start',
|
||||
siemMigrationsRules = 'migrate_rules',
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import type { EuiBadgeProps, IconType } from '@elastic/eui';
|
||||
import type { LicenseType } from '@kbn/licensing-plugin/public';
|
||||
|
||||
import type { ExperimentalFeatures } from '../../common';
|
||||
|
@ -48,6 +48,8 @@ export type SetExpandedCardId = (
|
|||
options?: { scroll?: boolean }
|
||||
) => void;
|
||||
|
||||
export type CardBadge = 'beta' | 'tech_preview' | EuiBadgeProps;
|
||||
|
||||
export type OnboardingCardComponent<TMetadata extends {} = {}> = React.ComponentType<{
|
||||
/**
|
||||
* Function to set the current card completion status.
|
||||
|
@ -130,6 +132,10 @@ export interface OnboardingCardConfig<TMetadata extends {} = {}>
|
|||
* @returns Promise for the complete status
|
||||
*/
|
||||
checkComplete?: OnboardingCardCheckComplete<TMetadata>;
|
||||
/** Optional icon for dark mode */
|
||||
iconDark?: IconType;
|
||||
/** Optional badge to display on the card. */
|
||||
badge?: CardBadge;
|
||||
}
|
||||
|
||||
export interface OnboardingGroupConfig {
|
||||
|
|
|
@ -4,5 +4,17 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useDarkMode } from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiIcon, type EuiIconProps } from '@elastic/eui';
|
||||
import SiemMigrationsIconSVG from './siem_migrations.svg';
|
||||
export const SiemMigrationsIcon = SiemMigrationsIconSVG;
|
||||
import SiemMigrationsIconDarkSVG from './siem_migrations_dark.svg';
|
||||
|
||||
export const SiemMigrationsIcon = React.memo<Omit<EuiIconProps, 'type'>>((props) => {
|
||||
const isDark = useDarkMode();
|
||||
if (isDark) {
|
||||
return <EuiIcon type={SiemMigrationsIconDarkSVG} {...props} />;
|
||||
}
|
||||
return <EuiIcon type={SiemMigrationsIconSVG} {...props} />;
|
||||
});
|
||||
SiemMigrationsIcon.displayName = 'SiemMigrationsIcon';
|
||||
|
|
|
@ -1,47 +1,69 @@
|
|||
<svg width="78" height="80" viewBox="0 0 78 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3393_148179)">
|
||||
<mask id="mask0_3393_148179" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="3" y="0" width="30" height="30">
|
||||
<path d="M3 15C3 6.71572 9.71572 0 18 0C26.2843 0 33 6.71572 33 15C33 23.2843 26.2843 30 18 30C9.71572 30 3 23.2843 3 15Z" fill="white"/>
|
||||
<svg width="85" height="86" viewBox="0 0 85 86" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddd_3001_187062)">
|
||||
<g clip-path="url(#clip0_3001_187062)">
|
||||
<rect x="10" y="6" width="30" height="30" rx="15" fill="white"/>
|
||||
<mask id="mask0_3001_187062" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="10" y="6" width="30" height="30">
|
||||
<path d="M10 21C10 12.7157 16.7157 6 25 6C33.2843 6 40 12.7157 40 21C40 29.2843 33.2843 36 25 36C16.7157 36 10 29.2843 10 21Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3393_148179)">
|
||||
<path d="M18 29.5C17.6808 29.5 17.3642 29.4897 17.0503 29.4694L17.018 29.9684C16.357 29.9257 15.7076 29.8401 15.0726 29.7145L15.1696 29.224C14.5434 29.1001 13.9317 28.9359 13.3373 28.7342L13.1766 29.2077C12.5531 28.996 11.9479 28.7445 11.3642 28.4561L11.5857 28.0078C11.0164 27.7265 10.4681 27.4089 9.94392 27.058L9.66576 27.4735C9.12079 27.1086 8.60106 26.709 8.10974 26.2778L8.43956 25.902C7.96379 25.4844 7.51557 25.0362 7.09799 24.5604L6.7222 24.8903C6.29099 24.3989 5.89137 23.8792 5.52652 23.3342L5.942 23.0561C5.59108 22.5319 5.27346 21.9836 4.99216 21.4143L4.54389 21.6358C4.2555 21.0521 4.00397 20.4469 3.79234 19.8234L4.26581 19.6627C4.0641 19.0683 3.89989 18.4566 3.776 17.8304L3.28551 17.9274C3.15989 17.2924 3.07435 16.643 3.03163 15.982L3.53059 15.9497C3.5103 15.6358 3.5 15.3192 3.5 15C3.5 14.6808 3.5103 14.3642 3.53059 14.0503L3.03163 14.018C3.07435 13.357 3.15989 12.7076 3.28551 12.0726L3.776 12.1696C3.89989 11.5434 4.0641 10.9317 4.26581 10.3373L3.79234 10.1766C4.00397 9.55307 4.2555 8.94792 4.54389 8.36423L4.99216 8.58571C5.27346 8.01637 5.59108 7.46809 5.942 6.94392L5.52652 6.66576C5.89137 6.12079 6.29099 5.60106 6.7222 5.10974L7.09799 5.43956C7.51556 4.96379 7.96378 4.51557 8.43956 4.09799L8.10974 3.7222C8.60105 3.29099 9.12079 2.89137 9.66576 2.52652L9.94392 2.942C10.4681 2.59108 11.0164 2.27346 11.5857 1.99216L11.3642 1.54389C11.9479 1.2555 12.5531 1.00397 13.1766 0.792339L13.3373 1.26581C13.9317 1.0641 14.5434 0.899892 15.1696 0.776005L15.0726 0.285511C15.7076 0.159891 16.357 0.0743492 17.018 0.0316296L17.0503 0.530589C17.3642 0.510304 17.6808 0.5 18 0.5C18.3192 0.5 18.6358 0.510304 18.9497 0.530589L18.982 0.0316296C19.643 0.074349 20.2924 0.159891 20.9274 0.28551L20.8304 0.776004C21.4566 0.899892 22.0683 1.0641 22.6627 1.26581L22.8234 0.792339C23.4469 1.00397 24.0521 1.2555 24.6358 1.54389L24.4143 1.99216C24.9836 2.27346 25.5319 2.59108 26.0561 2.942L26.3342 2.52652C26.8792 2.89137 27.3989 3.29099 27.8903 3.7222L27.5604 4.09799C28.0362 4.51556 28.4844 4.96378 28.902 5.43956L29.2778 5.10974C29.709 5.60105 30.1086 6.12079 30.4735 6.66576L30.058 6.94392C30.4089 7.46809 30.7265 8.01637 31.0078 8.58571L31.4561 8.36423C31.7445 8.94791 31.996 9.55307 32.2077 10.1766L31.7342 10.3373C31.9359 10.9317 32.1001 11.5434 32.224 12.1696L32.7145 12.0726C32.8401 12.7076 32.9257 13.357 32.9684 14.018L32.4694 14.0503C32.4897 14.3642 32.5 14.6808 32.5 15C32.5 15.3192 32.4897 15.6358 32.4694 15.9497L32.9684 15.982C32.9257 16.643 32.8401 17.2924 32.7145 17.9274L32.224 17.8304C32.1001 18.4566 31.9359 19.0683 31.7342 19.6627L32.2077 19.8234C31.996 20.4469 31.7445 21.0521 31.4561 21.6358L31.0078 21.4143C30.7265 21.9836 30.4089 22.5319 30.058 23.0561L30.4735 23.3342C30.1086 23.8792 29.709 24.3989 29.2778 24.8903L28.902 24.5604C28.4844 25.0362 28.0362 25.4844 27.5604 25.902L27.8903 26.2778C27.3989 26.709 26.8792 27.1086 26.3342 27.4735L26.0561 27.058C25.5319 27.4089 24.9836 27.7265 24.4143 28.0078L24.6358 28.4561C24.0521 28.7445 23.4469 28.996 22.8234 29.2077L22.6627 28.7342C22.0683 28.9359 21.4566 29.1001 20.8304 29.224L20.9274 29.7145C20.2924 29.8401 19.643 29.9257 18.982 29.9684L18.9497 29.4694C18.6358 29.4897 18.3192 29.5 18 29.5Z" stroke="#F05529" stroke-dasharray="2 2"/>
|
||||
<g mask="url(#mask0_3001_187062)">
|
||||
<path d="M25 35.5C24.6808 35.5 24.3642 35.4897 24.0503 35.4694L24.018 35.9684C23.357 35.9257 22.7076 35.8401 22.0726 35.7145L22.1696 35.224C21.5434 35.1001 20.9317 34.9359 20.3373 34.7342L20.1766 35.2077C19.5531 34.996 18.9479 34.7445 18.3642 34.4561L18.5857 34.0078C18.0164 33.7265 17.4681 33.4089 16.9439 33.058L16.6658 33.4735C16.1208 33.1086 15.6011 32.709 15.1097 32.2778L15.4396 31.902C14.9638 31.4844 14.5156 31.0362 14.098 30.5604L13.7222 30.8903C13.291 30.3989 12.8914 29.8792 12.5265 29.3342L12.942 29.0561C12.5911 28.5319 12.2735 27.9836 11.9922 27.4143L11.5439 27.6358C11.2555 27.0521 11.004 26.4469 10.7923 25.8234L11.2658 25.6627C11.0641 25.0683 10.8999 24.4566 10.776 23.8304L10.2855 23.9274C10.1599 23.2924 10.0743 22.643 10.0316 21.982L10.5306 21.9497C10.5103 21.6358 10.5 21.3192 10.5 21C10.5 20.6808 10.5103 20.3642 10.5306 20.0503L10.0316 20.018C10.0743 19.357 10.1599 18.7076 10.2855 18.0726L10.776 18.1696C10.8999 17.5434 11.0641 16.9317 11.2658 16.3373L10.7923 16.1766C11.004 15.5531 11.2555 14.9479 11.5439 14.3642L11.9922 14.5857C12.2735 14.0164 12.5911 13.4681 12.942 12.9439L12.5265 12.6658C12.8914 12.1208 13.291 11.6011 13.7222 11.1097L14.098 11.4396C14.5156 10.9638 14.9638 10.5156 15.4396 10.098L15.1097 9.7222C15.6011 9.29099 16.1208 8.89137 16.6658 8.52652L16.9439 8.942C17.4681 8.59108 18.0164 8.27346 18.5857 7.99216L18.3642 7.54389C18.9479 7.2555 19.5531 7.00397 20.1766 6.79234L20.3373 7.26581C20.9317 7.0641 21.5434 6.89989 22.1696 6.776L22.0726 6.28551C22.7076 6.15989 23.357 6.07435 24.018 6.03163L24.0503 6.53059C24.3642 6.5103 24.6808 6.5 25 6.5C25.3192 6.5 25.6358 6.5103 25.9497 6.53059L25.982 6.03163C26.643 6.07435 27.2924 6.15989 27.9274 6.28551L27.8304 6.776C28.4566 6.89989 29.0683 7.0641 29.6627 7.26581L29.8234 6.79234C30.4469 7.00397 31.0521 7.2555 31.6358 7.54389L31.4143 7.99216C31.9836 8.27346 32.5319 8.59108 33.0561 8.942L33.3342 8.52652C33.8792 8.89137 34.3989 9.29099 34.8903 9.7222L34.5604 10.098C35.0362 10.5156 35.4844 10.9638 35.902 11.4396L36.2778 11.1097C36.709 11.6011 37.1086 12.1208 37.4735 12.6658L37.058 12.9439C37.4089 13.4681 37.7265 14.0164 38.0078 14.5857L38.4561 14.3642C38.7445 14.9479 38.996 15.5531 39.2077 16.1766L38.7342 16.3373C38.9359 16.9317 39.1001 17.5434 39.224 18.1696L39.7145 18.0726C39.8401 18.7076 39.9257 19.357 39.9684 20.018L39.4694 20.0503C39.4897 20.3642 39.5 20.6808 39.5 21C39.5 21.3192 39.4897 21.6358 39.4694 21.9497L39.9684 21.982C39.9257 22.643 39.8401 23.2924 39.7145 23.9274L39.224 23.8304C39.1001 24.4566 38.9359 25.0683 38.7342 25.6627L39.2077 25.8234C38.996 26.4469 38.7445 27.0521 38.4561 27.6358L38.0078 27.4143C37.7265 27.9836 37.4089 28.5319 37.058 29.0561L37.4735 29.3342C37.1086 29.8792 36.709 30.3989 36.2778 30.8903L35.902 30.5604C35.4844 31.0362 35.0362 31.4844 34.5604 31.902L34.8903 32.2778C34.3989 32.709 33.8792 33.1086 33.3342 33.4735L33.0561 33.058C32.5319 33.4089 31.9836 33.7265 31.4143 34.0078L31.6358 34.4561C31.0521 34.7445 30.4469 34.996 29.8234 35.2077L29.6627 34.7342C29.0683 34.9359 28.4566 35.1001 27.8304 35.224L27.9274 35.7145C27.2924 35.8401 26.643 35.9257 25.982 35.9684L25.9497 35.4694C25.6358 35.4897 25.3192 35.5 25 35.5Z" stroke="#F05529" stroke-dasharray="2 2"/>
|
||||
</g>
|
||||
</g>
|
||||
<g filter="url(#filter0_ddd_3393_148179)">
|
||||
<rect x="10" y="7" width="58" height="58" rx="29" fill="white"/>
|
||||
<g clip-path="url(#clip1_3393_148179)">
|
||||
<rect width="38.6667" height="38.6667" transform="translate(19.667 16.6666)" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.542 25.1348V16.6666H54.7087V36.8992C54.7087 41.6277 47.0015 44.7254 43.815 45.6666V25.1348H30.542Z" fill="#FA744E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.292 40.9212V28.75H40.2087V55.3333C40.2087 55.3333 23.292 48.0472 23.292 40.9212Z" fill="#00BFB3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.542 28.75H40.2087V45.6667C36.6036 44.288 30.542 41.1276 30.542 37.1558V28.75Z" fill="#343741"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_ddd_3001_187062)">
|
||||
<rect x="17" y="13" width="58" height="58" rx="29" fill="white"/>
|
||||
<g clip-path="url(#clip1_3001_187062)">
|
||||
<rect width="38.6667" height="38.6667" transform="translate(26.667 22.6666)" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.542 31.1348V22.6666H61.7087V42.8992C61.7087 47.6277 54.0015 50.7254 50.815 51.6666V31.1348H37.542Z" fill="#FA744E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.292 46.9212V34.75H47.2087V61.3333C47.2087 61.3333 30.292 54.0472 30.292 46.9212Z" fill="#00BFB3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.542 34.75H47.2087V51.6667C43.6036 50.288 37.542 47.1276 37.542 43.1558V34.75Z" fill="#343741"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M41.6545 15.0069L45.4102 6.70915L42.3157 8.77196C39.4869 2.30711 32.3832 -1.02953 25.6348 1.70871C25.6348 1.70871 29.8634 1.35599 34.0015 4.31742C35.6362 5.50997 36.8866 7.15436 37.599 9.04829C37.7044 9.33484 37.8015 9.63649 37.8821 9.93996L34.2531 9.69867L41.6545 15.0069Z" fill="#0077CC"/>
|
||||
<path d="M48.6545 21.0069L52.4102 12.7091L49.3157 14.772C46.4869 8.30711 39.3832 4.97047 32.6348 7.70871C32.6348 7.70871 36.8634 7.35599 41.0015 10.3174C42.6362 11.51 43.8866 13.1544 44.599 15.0483C44.7044 15.3348 44.8015 15.6365 44.8821 15.94L41.2531 15.6987L48.6545 21.0069Z" fill="#0077CC"/>
|
||||
<defs>
|
||||
<filter id="filter0_ddd_3393_148179" x="0" y="1.5" width="78" height="78" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="filter0_ddd_3001_187062" x="0" y="0.5" width="50" height="50" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4.5"/>
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3393_148179"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3001_187062"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.9"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_3393_148179" result="effect2_dropShadow_3393_148179"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_3001_187062" result="effect2_dropShadow_3001_187062"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.7"/>
|
||||
<feGaussianBlur stdDeviation="0.7"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_3393_148179" result="effect3_dropShadow_3393_148179"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_3393_148179" result="shape"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_3001_187062" result="effect3_dropShadow_3001_187062"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_3001_187062" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_3393_148179">
|
||||
<rect width="30" height="30" fill="white" transform="translate(3)"/>
|
||||
<filter id="filter1_ddd_3001_187062" x="7" y="7.5" width="78" height="78" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4.5"/>
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3001_187062"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.9"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_3001_187062" result="effect2_dropShadow_3001_187062"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.7"/>
|
||||
<feGaussianBlur stdDeviation="0.7"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_3001_187062" result="effect3_dropShadow_3001_187062"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_3001_187062" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_3001_187062">
|
||||
<rect x="10" y="6" width="30" height="30" rx="15" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_3393_148179">
|
||||
<rect width="38.6667" height="38.6667" fill="white" transform="translate(19.667 16.6666)"/>
|
||||
<clipPath id="clip1_3001_187062">
|
||||
<rect width="38.6667" height="38.6667" fill="white" transform="translate(26.667 22.6666)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 7.6 KiB |
|
@ -0,0 +1,68 @@
|
|||
<svg width="85" height="86" viewBox="0 0 85 86" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddd_4197_88284)">
|
||||
<g clip-path="url(#clip0_4197_88284)">
|
||||
<rect x="10" y="6" width="30" height="30" rx="15" fill="#0B1628"/>
|
||||
<mask id="mask0_4197_88284" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="10" y="6" width="30" height="30">
|
||||
<path d="M10 21C10 12.7157 16.7157 6 25 6C33.2843 6 40 12.7157 40 21C40 29.2843 33.2843 36 25 36C16.7157 36 10 29.2843 10 21Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_4197_88284)">
|
||||
<path d="M25 35.5C24.6808 35.5 24.3642 35.4897 24.0503 35.4694L24.018 35.9684C23.357 35.9257 22.7076 35.8401 22.0726 35.7145L22.1696 35.224C21.5434 35.1001 20.9317 34.9359 20.3373 34.7342L20.1766 35.2077C19.5531 34.996 18.9479 34.7445 18.3642 34.4561L18.5857 34.0078C18.0164 33.7265 17.4681 33.4089 16.9439 33.058L16.6658 33.4735C16.1208 33.1086 15.6011 32.709 15.1097 32.2778L15.4396 31.902C14.9638 31.4844 14.5156 31.0362 14.098 30.5604L13.7222 30.8903C13.291 30.3989 12.8914 29.8792 12.5265 29.3342L12.942 29.0561C12.5911 28.5319 12.2735 27.9836 11.9922 27.4143L11.5439 27.6358C11.2555 27.0521 11.004 26.4469 10.7923 25.8234L11.2658 25.6627C11.0641 25.0683 10.8999 24.4566 10.776 23.8304L10.2855 23.9274C10.1599 23.2924 10.0743 22.643 10.0316 21.982L10.5306 21.9497C10.5103 21.6358 10.5 21.3192 10.5 21C10.5 20.6808 10.5103 20.3642 10.5306 20.0503L10.0316 20.018C10.0743 19.357 10.1599 18.7076 10.2855 18.0726L10.776 18.1696C10.8999 17.5434 11.0641 16.9317 11.2658 16.3373L10.7923 16.1766C11.004 15.5531 11.2555 14.9479 11.5439 14.3642L11.9922 14.5857C12.2735 14.0164 12.5911 13.4681 12.942 12.9439L12.5265 12.6658C12.8914 12.1208 13.291 11.6011 13.7222 11.1097L14.098 11.4396C14.5156 10.9638 14.9638 10.5156 15.4396 10.098L15.1097 9.7222C15.6011 9.29099 16.1208 8.89137 16.6658 8.52652L16.9439 8.942C17.4681 8.59108 18.0164 8.27346 18.5857 7.99216L18.3642 7.54389C18.9479 7.2555 19.5531 7.00397 20.1766 6.79234L20.3373 7.26581C20.9317 7.0641 21.5434 6.89989 22.1696 6.776L22.0726 6.28551C22.7076 6.15989 23.357 6.07435 24.018 6.03163L24.0503 6.53059C24.3642 6.5103 24.6808 6.5 25 6.5C25.3192 6.5 25.6358 6.5103 25.9497 6.53059L25.982 6.03163C26.643 6.07435 27.2924 6.15989 27.9274 6.28551L27.8304 6.776C28.4566 6.89989 29.0683 7.0641 29.6627 7.26581L29.8234 6.79234C30.4469 7.00397 31.0521 7.2555 31.6358 7.54389L31.4143 7.99216C31.9836 8.27346 32.5319 8.59108 33.0561 8.942L33.3342 8.52652C33.8792 8.89137 34.3989 9.29099 34.8903 9.7222L34.5604 10.098C35.0362 10.5156 35.4844 10.9638 35.902 11.4396L36.2778 11.1097C36.709 11.6011 37.1086 12.1208 37.4735 12.6658L37.058 12.9439C37.4089 13.4681 37.7265 14.0164 38.0078 14.5857L38.4561 14.3642C38.7445 14.9479 38.996 15.5531 39.2077 16.1766L38.7342 16.3373C38.9359 16.9317 39.1001 17.5434 39.224 18.1696L39.7145 18.0726C39.8401 18.7076 39.9257 19.357 39.9684 20.018L39.4694 20.0503C39.4897 20.3642 39.5 20.6808 39.5 21C39.5 21.3192 39.4897 21.6358 39.4694 21.9497L39.9684 21.982C39.9257 22.643 39.8401 23.2924 39.7145 23.9274L39.224 23.8304C39.1001 24.4566 38.9359 25.0683 38.7342 25.6627L39.2077 25.8234C38.996 26.4469 38.7445 27.0521 38.4561 27.6358L38.0078 27.4143C37.7265 27.9836 37.4089 28.5319 37.058 29.0561L37.4735 29.3342C37.1086 29.8792 36.709 30.3989 36.2778 30.8903L35.902 30.5604C35.4844 31.0362 35.0362 31.4844 34.5604 31.902L34.8903 32.2778C34.3989 32.709 33.8792 33.1086 33.3342 33.4735L33.0561 33.058C32.5319 33.4089 31.9836 33.7265 31.4143 34.0078L31.6358 34.4561C31.0521 34.7445 30.4469 34.996 29.8234 35.2077L29.6627 34.7342C29.0683 34.9359 28.4566 35.1001 27.8304 35.224L27.9274 35.7145C27.2924 35.8401 26.643 35.9257 25.982 35.9684L25.9497 35.4694C25.6358 35.4897 25.3192 35.5 25 35.5Z" stroke="#F05529" stroke-dasharray="2 2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g filter="url(#filter1_ddd_4197_88284)">
|
||||
<rect x="17" y="13" width="58" height="58" rx="29" fill="#0B1628"/>
|
||||
<g clip-path="url(#clip1_4197_88284)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.542 31.1348V22.6666H61.7087V42.8992C61.7087 47.6277 54.0015 50.7254 50.815 51.6666V31.1348H37.542Z" fill="#FA744E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.292 46.9212V34.75H47.2087V61.3333C47.2087 61.3333 30.292 54.0472 30.292 46.9212Z" fill="#00BFB3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.542 34.75H47.2087V51.6667C43.6036 50.288 37.542 47.1276 37.542 43.1558V34.75Z" fill="#343741"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M48.6545 21.0069L52.4102 12.7091L49.3157 14.772C46.4869 8.30711 39.3832 4.97047 32.6348 7.70871C32.6348 7.70871 36.8634 7.35599 41.0015 10.3174C42.6362 11.51 43.8866 13.1544 44.599 15.0483C44.7044 15.3348 44.8015 15.6365 44.8821 15.94L41.2531 15.6987L48.6545 21.0069Z" fill="#0077CC"/>
|
||||
<defs>
|
||||
<filter id="filter0_ddd_4197_88284" x="0" y="0.5" width="50" height="50" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4.5"/>
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4197_88284"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.9"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_4197_88284" result="effect2_dropShadow_4197_88284"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.7"/>
|
||||
<feGaussianBlur stdDeviation="0.7"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_4197_88284" result="effect3_dropShadow_4197_88284"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_4197_88284" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_ddd_4197_88284" x="7" y="7.5" width="78" height="78" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4.5"/>
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4197_88284"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.9"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_4197_88284" result="effect2_dropShadow_4197_88284"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.7"/>
|
||||
<feGaussianBlur stdDeviation="0.7"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_4197_88284" result="effect3_dropShadow_4197_88284"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_4197_88284" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_4197_88284">
|
||||
<rect x="10" y="6" width="30" height="30" rx="15" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_4197_88284">
|
||||
<rect width="38.6667" height="38.6667" fill="white" transform="translate(26.667 22.6666)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
|
@ -15,6 +15,8 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
useEuiTheme,
|
||||
tint,
|
||||
} from '@elastic/eui';
|
||||
import { AssistantIcon } from '@kbn/ai-assistant-icon';
|
||||
import { PanelText } from '../../../../common/components/panel_text';
|
||||
|
@ -27,6 +29,7 @@ export interface MigrationProgressPanelProps {
|
|||
}
|
||||
export const MigrationProgressPanel = React.memo<MigrationProgressPanelProps>(
|
||||
({ migrationStats }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const finishedCount = migrationStats.rules.completed + migrationStats.rules.failed;
|
||||
const progressValue = (finishedCount / migrationStats.rules.total) * 100;
|
||||
|
||||
|
@ -66,7 +69,7 @@ export const MigrationProgressPanel = React.memo<MigrationProgressPanelProps>(
|
|||
value={progressValue}
|
||||
valueText={`${Math.floor(progressValue)}%`}
|
||||
max={100}
|
||||
color="success"
|
||||
color={tint(euiTheme.colors.success, 0.25)}
|
||||
/>
|
||||
<EuiSpacer size="xs" />
|
||||
<RuleMigrationsReadMore />
|
||||
|
|
|
@ -18,8 +18,11 @@ import {
|
|||
EuiText,
|
||||
EuiAccordion,
|
||||
EuiButtonIcon,
|
||||
type EuiBasicTableColumn,
|
||||
EuiSpacer,
|
||||
EuiBadge,
|
||||
type EuiBasicTableColumn,
|
||||
useEuiTheme,
|
||||
COLOR_MODES_STANDARD,
|
||||
} from '@elastic/eui';
|
||||
import { Chart, BarSeries, Settings, ScaleType } from '@elastic/charts';
|
||||
import { SecurityPageName } from '@kbn/security-solution-navigation';
|
||||
|
@ -48,6 +51,18 @@ const headerStyle = css`
|
|||
}
|
||||
`;
|
||||
|
||||
const useCompleteBadgeStyles = () => {
|
||||
const { euiTheme, colorMode } = useEuiTheme();
|
||||
const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;
|
||||
return css`
|
||||
background-color: ${isDarkMode
|
||||
? euiTheme.colors.success
|
||||
: euiTheme.colors.backgroundBaseSuccess};
|
||||
color: ${isDarkMode ? euiTheme.colors.plainDark : euiTheme.colors.textSuccess};
|
||||
text-decoration: none;
|
||||
`;
|
||||
};
|
||||
|
||||
export interface MigrationResultPanelProps {
|
||||
migrationStats: RuleMigrationStats;
|
||||
isCollapsed: boolean;
|
||||
|
@ -59,6 +74,8 @@ export const MigrationResultPanel = React.memo<MigrationResultPanelProps>(
|
|||
const { data: translationStats, isLoading: isLoadingTranslationStats } =
|
||||
useGetMigrationTranslationStats(migrationStats.id);
|
||||
|
||||
const completeBadgeStyles = useCompleteBadgeStyles();
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="none">
|
||||
<EuiPanel hasShadow={false} hasBorder={false} paddingSize="m">
|
||||
|
@ -67,7 +84,7 @@ export const MigrationResultPanel = React.memo<MigrationResultPanelProps>(
|
|||
<EuiFlexGroup direction="column" alignItems="flexStart" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<PanelText size="s" semiBold>
|
||||
<p>{i18n.RULE_MIGRATION_COMPLETE_TITLE(migrationStats.number)}</p>
|
||||
<p>{i18n.RULE_MIGRATION_TITLE(migrationStats.number)}</p>
|
||||
</PanelText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -82,6 +99,9 @@ export const MigrationResultPanel = React.memo<MigrationResultPanelProps>(
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge css={completeBadgeStyles}>{i18n.RULE_MIGRATION_COMPLETE_BADGE}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={isCollapsed ? 'arrowDown' : 'arrowUp'}
|
||||
|
@ -168,22 +188,22 @@ const TranslationResultsChart = React.memo<{
|
|||
const translationResultColors = useResultVisColors();
|
||||
const data = [
|
||||
{
|
||||
category: 'Results',
|
||||
category: i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS,
|
||||
type: convertTranslationResultIntoText(RuleTranslationResult.FULL),
|
||||
value: translationStats.rules.success.result.full,
|
||||
},
|
||||
{
|
||||
category: 'Results',
|
||||
category: i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS,
|
||||
type: convertTranslationResultIntoText(RuleTranslationResult.PARTIAL),
|
||||
value: translationStats.rules.success.result.partial,
|
||||
},
|
||||
{
|
||||
category: 'Results',
|
||||
category: i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS,
|
||||
type: convertTranslationResultIntoText(RuleTranslationResult.UNTRANSLATABLE),
|
||||
value: translationStats.rules.success.result.untranslatable,
|
||||
},
|
||||
{
|
||||
category: 'Results',
|
||||
category: i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS,
|
||||
type: i18n.RULE_MIGRATION_TRANSLATION_FAILED,
|
||||
value: translationStats.rules.failed,
|
||||
},
|
||||
|
@ -201,7 +221,7 @@ const TranslationResultsChart = React.memo<{
|
|||
<Settings showLegend={false} rotation={90} baseTheme={baseTheme} />
|
||||
<BarSeries
|
||||
id="results"
|
||||
name="Results"
|
||||
name={i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS}
|
||||
data={data}
|
||||
xAccessor="category"
|
||||
yAccessors={['value']}
|
||||
|
@ -225,7 +245,7 @@ interface TranslationResultsTableItem {
|
|||
const columns: Array<EuiBasicTableColumn<TranslationResultsTableItem>> = [
|
||||
{
|
||||
field: 'title',
|
||||
name: i18n.RULE_MIGRATION_TABLE_COLUMN_RESULT,
|
||||
name: i18n.RULE_MIGRATION_TABLE_COLUMN_STATUS,
|
||||
render: (title: string, { color }) => (
|
||||
<EuiHealth color={color} textSize="xs">
|
||||
{title}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { PanelText } from '../../../../common/components/panel_text';
|
|||
import { useKibana } from '../../../../common/lib/kibana/kibana_react';
|
||||
|
||||
export const RuleMigrationsReadMore = React.memo(() => {
|
||||
const docLink = useKibana().services.docLinks.links.siem.gettingStarted;
|
||||
const docLink = useKibana().services.docLinks.links.securitySolution.siemMigrations;
|
||||
return (
|
||||
<PanelText size="xs" subdued>
|
||||
<p>
|
||||
|
|
|
@ -49,12 +49,6 @@ export const RULE_MIGRATION_TRANSLATING = i18n.translate(
|
|||
{ defaultMessage: `Translating rules` }
|
||||
);
|
||||
|
||||
export const RULE_MIGRATION_COMPLETE_TITLE = (number: number) =>
|
||||
i18n.translate('xpack.securitySolution.siemMigrations.rules.panel.result.title', {
|
||||
defaultMessage: 'SIEM rules migration #{number} complete',
|
||||
values: { number },
|
||||
});
|
||||
|
||||
export const RULE_MIGRATION_COMPLETE_DESCRIPTION = (createdAt: string, finishedAt: string) =>
|
||||
i18n.translate('xpack.securitySolution.siemMigrations.rules.panel.result.description', {
|
||||
defaultMessage: 'Export uploaded on {createdAt} and translation finished {finishedAt}.',
|
||||
|
@ -77,7 +71,7 @@ export const RULE_MIGRATION_SUMMARY_CHART_TITLE = i18n.translate(
|
|||
|
||||
export const RULE_MIGRATION_VIEW_TRANSLATED_RULES_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.result.summary.button',
|
||||
{ defaultMessage: 'View translated rules' }
|
||||
{ defaultMessage: 'View rules' }
|
||||
);
|
||||
|
||||
export const RULE_MIGRATION_TRANSLATION_FAILED = i18n.translate(
|
||||
|
@ -85,9 +79,9 @@ export const RULE_MIGRATION_TRANSLATION_FAILED = i18n.translate(
|
|||
{ defaultMessage: 'Failed' }
|
||||
);
|
||||
|
||||
export const RULE_MIGRATION_TABLE_COLUMN_RESULT = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.result.summary.tableColumn.result',
|
||||
{ defaultMessage: 'Result' }
|
||||
export const RULE_MIGRATION_TABLE_COLUMN_STATUS = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.result.summary.tableColumn.status',
|
||||
{ defaultMessage: 'Status' }
|
||||
);
|
||||
export const RULE_MIGRATION_TABLE_COLUMN_RULES = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.result.summary.tableColumn.rules',
|
||||
|
@ -96,12 +90,16 @@ export const RULE_MIGRATION_TABLE_COLUMN_RULES = i18n.translate(
|
|||
|
||||
export const RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.uploadMissingResources',
|
||||
{ defaultMessage: 'Upload missing Macros and Lookups.' }
|
||||
);
|
||||
export const RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.uploadMissingResourcesDescription',
|
||||
{ defaultMessage: 'Click upload for step-by-step guidance to finish partially translated rules.' }
|
||||
{ defaultMessage: 'Upload missing macros and lookup lists.' }
|
||||
);
|
||||
export const RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_DESCRIPTION = (partialRulesCount: number) =>
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.uploadMissingResourcesDescription',
|
||||
{
|
||||
defaultMessage: 'Click Upload to continue translating {partialRulesCount} rules',
|
||||
values: { partialRulesCount },
|
||||
}
|
||||
);
|
||||
|
||||
export const RULE_MIGRATION_UPLOAD_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.panel.uploadMacros.button',
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
useEuiTheme,
|
||||
|
@ -23,6 +24,7 @@ import { useGetMissingResources } from '../../service/hooks/use_get_missing_reso
|
|||
import * as i18n from './translations';
|
||||
import { useRuleMigrationDataInputContext } from '../data_input_flyout/context';
|
||||
import type { RuleMigrationStats } from '../../types';
|
||||
import { useGetMigrationTranslationStats } from '../../logic/use_get_migration_translation_stats';
|
||||
|
||||
interface RuleMigrationsUploadMissingPanelProps {
|
||||
migrationStats: RuleMigrationStats;
|
||||
|
@ -30,9 +32,6 @@ interface RuleMigrationsUploadMissingPanelProps {
|
|||
}
|
||||
export const RuleMigrationsUploadMissingPanel = React.memo<RuleMigrationsUploadMissingPanelProps>(
|
||||
({ migrationStats, topSpacerSize }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { telemetry } = useKibana().services.siemMigrations.rules;
|
||||
const { openFlyout } = useRuleMigrationDataInputContext();
|
||||
const [missingResources, setMissingResources] = useState<RuleMigrationResourceBase[]>([]);
|
||||
const { getMissingResources, isLoading } = useGetMissingResources(setMissingResources);
|
||||
|
||||
|
@ -40,56 +39,94 @@ export const RuleMigrationsUploadMissingPanel = React.memo<RuleMigrationsUploadM
|
|||
getMissingResources(migrationStats.id);
|
||||
}, [getMissingResources, migrationStats.id]);
|
||||
|
||||
const onOpenFlyout = useCallback(() => {
|
||||
openFlyout(migrationStats);
|
||||
telemetry.reportSetupMigrationOpenResources({
|
||||
migrationId: migrationStats.id,
|
||||
missingResourcesCount: missingResources.length,
|
||||
});
|
||||
}, [migrationStats, openFlyout, missingResources, telemetry]);
|
||||
|
||||
if (isLoading || missingResources.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{topSpacerSize && <EuiSpacer size={topSpacerSize} />}
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
paddingSize="s"
|
||||
style={{ backgroundColor: euiTheme.colors.backgroundBasePrimary }}
|
||||
>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<PanelText size="s" semiBold>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_TITLE}
|
||||
</PanelText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<PanelText size="s" subdued>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_DESCRIPTION}
|
||||
</PanelText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
onClick={onOpenFlyout}
|
||||
iconType="download"
|
||||
iconSide="right"
|
||||
size="s"
|
||||
>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</>
|
||||
<RuleMigrationsUploadMissingPanelContent
|
||||
migrationStats={migrationStats}
|
||||
topSpacerSize={topSpacerSize}
|
||||
missingResources={missingResources}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
RuleMigrationsUploadMissingPanel.displayName = 'RuleMigrationsUploadMissingPanel';
|
||||
|
||||
interface RuleMigrationsUploadMissingPanelContentProps
|
||||
extends RuleMigrationsUploadMissingPanelProps {
|
||||
missingResources: RuleMigrationResourceBase[];
|
||||
}
|
||||
const RuleMigrationsUploadMissingPanelContent =
|
||||
React.memo<RuleMigrationsUploadMissingPanelContentProps>(
|
||||
({ migrationStats, topSpacerSize, missingResources }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { telemetry } = useKibana().services.siemMigrations.rules;
|
||||
const { openFlyout } = useRuleMigrationDataInputContext();
|
||||
|
||||
const { data: translationStats, isLoading: isLoadingTranslationStats } =
|
||||
useGetMigrationTranslationStats(migrationStats.id);
|
||||
|
||||
const onOpenFlyout = useCallback(() => {
|
||||
openFlyout(migrationStats);
|
||||
telemetry.reportSetupMigrationOpenResources({
|
||||
migrationId: migrationStats.id,
|
||||
missingResourcesCount: missingResources.length,
|
||||
});
|
||||
}, [migrationStats, openFlyout, missingResources, telemetry]);
|
||||
|
||||
const totalRulesToRetry = useMemo(() => {
|
||||
return (
|
||||
(translationStats?.rules.failed ?? 0) +
|
||||
(translationStats?.rules.success.result.partial ?? 0) +
|
||||
(translationStats?.rules.success.result.untranslatable ?? 0)
|
||||
);
|
||||
}, [translationStats]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{topSpacerSize && <EuiSpacer size={topSpacerSize} />}
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
paddingSize="s"
|
||||
style={{ backgroundColor: euiTheme.colors.backgroundBasePrimary }}
|
||||
>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<PanelText size="s" semiBold>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_TITLE}
|
||||
</PanelText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isLoadingTranslationStats ? (
|
||||
<EuiLoadingSpinner size="s" />
|
||||
) : (
|
||||
<PanelText size="s" subdued>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_MISSING_RESOURCES_DESCRIPTION(totalRulesToRetry)}
|
||||
</PanelText>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
onClick={onOpenFlyout}
|
||||
iconType="download"
|
||||
iconSide="right"
|
||||
size="s"
|
||||
>
|
||||
{i18n.RULE_MIGRATION_UPLOAD_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
RuleMigrationsUploadMissingPanelContent.displayName = 'RuleMigrationsUploadMissingPanelContent';
|
||||
|
|
|
@ -10,6 +10,13 @@ import { RuleTranslationResult } from '../../../../../common/siem_migrations/con
|
|||
import type { RuleMigrationTranslationResult } from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const COLORS = {
|
||||
[RuleTranslationResult.FULL]: '#54B399',
|
||||
[RuleTranslationResult.PARTIAL]: '#D6BF57',
|
||||
[RuleTranslationResult.UNTRANSLATABLE]: '#DA8B45',
|
||||
error: '#E7664C',
|
||||
} as const;
|
||||
|
||||
export const useResultVisColors = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
if (euiTheme.themeName === 'EUI_THEME_AMSTERDAM') {
|
||||
|
@ -21,22 +28,17 @@ export const useResultVisColors = () => {
|
|||
};
|
||||
}
|
||||
// Borealis
|
||||
return {
|
||||
[RuleTranslationResult.FULL]: euiTheme.colors.vis.euiColorVisSuccess0,
|
||||
[RuleTranslationResult.PARTIAL]: euiTheme.colors.vis.euiColorSeverity7,
|
||||
[RuleTranslationResult.UNTRANSLATABLE]: euiTheme.colors.vis.euiColorSeverity10,
|
||||
error: euiTheme.colors.vis.euiColorSeverity14,
|
||||
};
|
||||
return COLORS;
|
||||
};
|
||||
|
||||
export const convertTranslationResultIntoColor = (status?: RuleMigrationTranslationResult) => {
|
||||
switch (status) {
|
||||
case RuleTranslationResult.FULL:
|
||||
return 'primary';
|
||||
return COLORS[RuleTranslationResult.FULL];
|
||||
case RuleTranslationResult.PARTIAL:
|
||||
return 'warning';
|
||||
return COLORS[RuleTranslationResult.PARTIAL];
|
||||
case RuleTranslationResult.UNTRANSLATABLE:
|
||||
return 'danger';
|
||||
return COLORS[RuleTranslationResult.UNTRANSLATABLE];
|
||||
default:
|
||||
return 'subdued';
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue