mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `9.0`: - [[Siem Migrations] Add Translated Rules Empty Page (#213438)](https://github.com/elastic/kibana/pull/213438) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Jatin Kathuria","email":"jatin.kathuria@elastic.co"},"sourceCommit":{"committedDate":"2025-03-12T13:59:12Z","message":"[Siem Migrations] Add Translated Rules Empty Page (#213438)\n\n## Summary\n\nThis PR adds empty state for Translated Rules Page.\n\n\n\nhttps://github.com/user-attachments/assets/b8222151-526c-435e-b9bb-403e1097c056\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"bbc91a26917e0ad5257bfd879a0f1931d3442f25","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:ExpressionLanguage","release_note:skip","v9.0.0","Team:Threat Hunting","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Siem Migrations] Add Translated Rules Empty Page","number":213438,"url":"https://github.com/elastic/kibana/pull/213438","mergeCommit":{"message":"[Siem Migrations] Add Translated Rules Empty Page (#213438)\n\n## Summary\n\nThis PR adds empty state for Translated Rules Page.\n\n\n\nhttps://github.com/user-attachments/assets/b8222151-526c-435e-b9bb-403e1097c056\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"bbc91a26917e0ad5257bfd879a0f1931d3442f25"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213438","number":213438,"mergeCommit":{"message":"[Siem Migrations] Add Translated Rules Empty Page (#213438)\n\n## Summary\n\nThis PR adds empty state for Translated Rules Page.\n\n\n\nhttps://github.com/user-attachments/assets/b8222151-526c-435e-b9bb-403e1097c056\n\n\n\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"bbc91a26917e0ad5257bfd879a0f1931d3442f25"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>
This commit is contained in:
parent
873f281cd3
commit
95824add22
9 changed files with 495 additions and 11 deletions
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { SiemMigrationTaskStatus } from '../../../../common/siem_migrations/constants';
|
||||
import type {
|
||||
GetRuleMigrationResponse,
|
||||
GetRuleMigrationTranslationStatsResponse,
|
||||
} from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import type { RuleMigrationStats } from '../../rules/types';
|
||||
|
||||
const getMockMigrationResultRule = ({
|
||||
migrationId,
|
||||
translationResult = 'full',
|
||||
status = 'completed',
|
||||
}: {
|
||||
migrationId: string;
|
||||
status?: GetRuleMigrationResponse['data'][number]['status'];
|
||||
translationResult?: GetRuleMigrationResponse['data'][number]['translation_result'];
|
||||
}): GetRuleMigrationResponse['data'][number] => {
|
||||
const ruleId = uuidv4();
|
||||
return {
|
||||
migration_id: migrationId,
|
||||
original_rule: {
|
||||
id: ruleId,
|
||||
vendor: 'splunk',
|
||||
title: ` 'Rule Title - ${ruleId}'`,
|
||||
description: `Rule Title Description - ${ruleId}`,
|
||||
query: 'some query',
|
||||
query_language: 'spl',
|
||||
},
|
||||
'@timestamp': '2025-03-06T15:00:01.036Z',
|
||||
status,
|
||||
created_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_at: '2025-03-06T15:01:37.321Z',
|
||||
comments: [
|
||||
{
|
||||
created_at: '2025-03-06T15:01:06.390Z',
|
||||
message: '## Prebuilt Rule Matching Summary\nNo related prebuilt rule found.',
|
||||
created_by: 'assistant',
|
||||
},
|
||||
],
|
||||
translation_result: translationResult,
|
||||
elastic_rule: {
|
||||
severity: 'low',
|
||||
risk_score: 21,
|
||||
query:
|
||||
'FROM index metadata _id,_version,_index\n| WHERE MATCH(host.hostname, "vendor_sales")',
|
||||
description: `Converted Splunk Rule to Elastic Rule - ${ruleId}`,
|
||||
query_language: 'esql',
|
||||
title: `Converted Splunk Rule - ${ruleId}`,
|
||||
integration_ids: ['endpoint'],
|
||||
},
|
||||
id: ruleId,
|
||||
};
|
||||
};
|
||||
|
||||
export const mockedMigrationLatestStatsData: RuleMigrationStats[] = [
|
||||
{
|
||||
id: '1',
|
||||
number: 1,
|
||||
status: SiemMigrationTaskStatus.FINISHED,
|
||||
rules: {
|
||||
total: 1,
|
||||
pending: 0,
|
||||
processing: 0,
|
||||
completed: 1,
|
||||
failed: 0,
|
||||
},
|
||||
last_updated_at: '2025-03-06T15:01:37.321Z',
|
||||
created_at: '2025-03-06T15:01:37.321Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
number: 2,
|
||||
status: SiemMigrationTaskStatus.FINISHED,
|
||||
rules: {
|
||||
total: 2,
|
||||
pending: 0,
|
||||
processing: 0,
|
||||
completed: 2,
|
||||
failed: 0,
|
||||
},
|
||||
|
||||
created_at: '2025-03-06T15:01:37.321Z',
|
||||
last_updated_at: '2025-03-06T15:01:37.321Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockedMigrationResultsObj: Record<string, GetRuleMigrationResponse> = {
|
||||
'1': {
|
||||
total: 2,
|
||||
data: [
|
||||
getMockMigrationResultRule({
|
||||
migrationId: '1',
|
||||
translationResult: 'full',
|
||||
status: 'completed',
|
||||
}),
|
||||
getMockMigrationResultRule({
|
||||
migrationId: '1',
|
||||
translationResult: 'untranslatable',
|
||||
status: 'failed',
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const mockedMigrationTranslationStats: Record<
|
||||
string,
|
||||
GetRuleMigrationTranslationStatsResponse
|
||||
> = {
|
||||
'1': {
|
||||
id: '1',
|
||||
rules: {
|
||||
total: 2,
|
||||
success: {
|
||||
total: 2,
|
||||
result: {
|
||||
full: 1,
|
||||
partial: 0,
|
||||
untranslatable: 1,
|
||||
},
|
||||
installable: 1,
|
||||
prebuilt: 0,
|
||||
},
|
||||
failed: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockRefreshStats = jest.fn();
|
||||
export const mockedLatestStatsEmpty = {
|
||||
data: [],
|
||||
isLoading: false,
|
||||
refreshStats: mockRefreshStats,
|
||||
};
|
||||
|
||||
export const mockedLatestStats = {
|
||||
...mockedLatestStatsEmpty,
|
||||
data: mockedMigrationLatestStatsData,
|
||||
};
|
|
@ -320,7 +320,12 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
|
|||
<EmptyMigration />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexGroup
|
||||
data-test-subj="siemMigrationsRulesTable"
|
||||
gutterSize="m"
|
||||
justifyContent="flexEnd"
|
||||
wrap
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SearchField initialValue={searchTerm} onSearch={handleOnSearch} />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -20,7 +20,7 @@ interface NameProps {
|
|||
const Name = ({ rule, openMigrationRuleDetails }: NameProps) => {
|
||||
if (rule.status === SiemMigrationStatus.FAILED) {
|
||||
return (
|
||||
<EuiText color="danger" size="s">
|
||||
<EuiText data-test-subj="ruleName" color="danger" size="s">
|
||||
{rule.original_rule.title}
|
||||
</EuiText>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import * as i18n from './translations';
|
|||
export const UnknownMigration: React.FC = React.memo(() => {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
data-test-subj="siemMigrationsUnknown"
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
|
|
|
@ -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 React from 'react';
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { SecurityPageName } from '@kbn/deeplinks-security';
|
||||
import { css } from '@emotion/react';
|
||||
import { SecuritySolutionLinkButton } from '../../../common/components/links';
|
||||
import * as i18n from './translations';
|
||||
import { OnboardingCardId, OnboardingTopicId } from '../../../onboarding/constants';
|
||||
|
||||
export const EmptyMigrationRulesPage = () => {
|
||||
return (
|
||||
<KibanaPageTemplate.Section color="plain" paddingSize="none">
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
/**
|
||||
* 240px compensates for the kibana header, action bar and page header.
|
||||
* It also compensates for the extra margin that header introduces
|
||||
*/
|
||||
min-height: calc(100vh - 240px);
|
||||
`}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<span data-test-subj="siemMigrationsTranslatedRulesEmptyPageHeader">
|
||||
{i18n.TRANSLATED_RULES_EMPTY_PAGE_TITLE}
|
||||
</span>
|
||||
}
|
||||
actions={
|
||||
<SecuritySolutionLinkButton
|
||||
deepLinkId={SecurityPageName.landing}
|
||||
path={`${OnboardingTopicId.siemMigrations}#${OnboardingCardId.siemMigrationsRules}`}
|
||||
>
|
||||
{i18n.TRANSLATED_RULES_EMPTY_PAGE_CTA}
|
||||
</SecuritySolutionLinkButton>
|
||||
}
|
||||
iconType={'logoSecurity'}
|
||||
body={
|
||||
<span data-test-subj="siemMigrationsTranslatedRulesEmptyPageMessage">
|
||||
{i18n.TRANSLATED_RULES_EMPTY_PAGE_MESSAGE}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</KibanaPageTemplate.Section>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* 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 hash from 'object-hash';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { MigrationRulesPage, type MigrationRulesPageProps } from '.';
|
||||
import * as useLatestStatsModule from '../service/hooks/use_latest_stats';
|
||||
import * as useNavigationModule from '@kbn/security-solution-navigation/src/navigation';
|
||||
import * as useGetIntegrationsModule from '../service/hooks/use_get_integrations';
|
||||
import * as useGetMigrationRulesModule from '../logic/use_get_migration_rules';
|
||||
import * as useGetMigrationTranslationStatsModule from '../logic/use_get_migration_translation_stats';
|
||||
import * as useMissingPrivilegesModule from '../../../detections/components/callouts/missing_privileges_callout/use_missing_privileges';
|
||||
import * as useGetMigrationMissingPrivilegesModule from '../logic/use_get_migration_privileges';
|
||||
import * as useCallOutStorageModule from '../../../common/components/callouts/use_callout_storage';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
mockedLatestStats,
|
||||
mockedLatestStatsEmpty,
|
||||
mockedMigrationResultsObj,
|
||||
mockedMigrationTranslationStats,
|
||||
} from '../../common/mocks/migration_result.data';
|
||||
import * as useGetMissingResourcesModule from '../service/hooks/use_get_missing_resources';
|
||||
|
||||
jest.mock('../../../common/components/page_wrapper', () => {
|
||||
return {
|
||||
SecuritySolutionPageWrapper: jest.fn(({ children }) => {
|
||||
return <div data-test-subj="SecuritySolutionPageWrapper">{children}</div>;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const useLatestStatsSpy = jest.spyOn(useLatestStatsModule, 'useLatestStats');
|
||||
const useNavigationSpy = jest.spyOn(useNavigationModule, 'useNavigation');
|
||||
const useGetIntegrationsSpy = jest.spyOn(useGetIntegrationsModule, 'useGetIntegrations');
|
||||
const useInvalidateGetMigrationRulesSpy = jest.spyOn(
|
||||
useGetMigrationRulesModule,
|
||||
'useInvalidateGetMigrationRules'
|
||||
);
|
||||
const useInvalidateGetMigrationTranslationStatsSpy = jest.spyOn(
|
||||
useGetMigrationTranslationStatsModule,
|
||||
'useInvalidateGetMigrationTranslationStats'
|
||||
);
|
||||
|
||||
const useGetMigrationRulesSpy = jest.spyOn(useGetMigrationRulesModule, 'useGetMigrationRules');
|
||||
// missing detection privileges
|
||||
const useMissingPrivilegesSpy = jest.spyOn(useMissingPrivilegesModule, 'useMissingPrivileges');
|
||||
// missing migration privileges
|
||||
const useGetMigrationMissingPrivilegesSpy = jest.spyOn(
|
||||
useGetMigrationMissingPrivilegesModule,
|
||||
'useGetMigrationMissingPrivileges'
|
||||
);
|
||||
const useCalloutStorageSpy = jest.spyOn(useCallOutStorageModule, 'useCallOutStorage');
|
||||
const useGetMissingResourcesSpy = jest.spyOn(
|
||||
useGetMissingResourcesModule,
|
||||
'useGetMissingResources'
|
||||
);
|
||||
const useGetMigrationTranslationStatsSpy = jest.spyOn(
|
||||
useGetMigrationTranslationStatsModule,
|
||||
'useGetMigrationTranslationStats'
|
||||
);
|
||||
|
||||
const defaultProps: MigrationRulesPageProps = {
|
||||
history: createMemoryHistory(),
|
||||
location: createMemoryHistory().location,
|
||||
match: {
|
||||
isExact: true,
|
||||
path: '',
|
||||
url: '',
|
||||
params: {
|
||||
migrationId: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockNavigateTo = jest.fn();
|
||||
const mockGetIntegrations = jest.fn();
|
||||
|
||||
const mockMissingDetectionsPrivileges: useMissingPrivilegesModule.MissingPrivileges = {
|
||||
featurePrivileges: [],
|
||||
indexPrivileges: [],
|
||||
};
|
||||
|
||||
const mockGetMigrationMissingPrivileges = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const mockVisibleCallStorageResult = {
|
||||
isVisible: () => true,
|
||||
dismiss: jest.fn(),
|
||||
getVisibleMessageIds: jest.fn(() => []),
|
||||
};
|
||||
|
||||
const mockHiddenCallStorageResult = {
|
||||
isVisible: () => false,
|
||||
dismiss: jest.fn(),
|
||||
getVisibleMessageIds: jest.fn(() => []),
|
||||
};
|
||||
|
||||
function renderTestComponent(args?: { migrationId?: string; wrapper?: React.ComponentType }) {
|
||||
const finalProps = {
|
||||
...defaultProps,
|
||||
match: {
|
||||
...defaultProps.match,
|
||||
params: {
|
||||
migrationId: args?.migrationId ?? defaultProps.match.params.migrationId,
|
||||
},
|
||||
},
|
||||
};
|
||||
return render(<MigrationRulesPage {...finalProps} />, {
|
||||
wrapper: args?.wrapper,
|
||||
});
|
||||
}
|
||||
|
||||
const mockUseMigrationRuleTransationStats: typeof useGetMigrationTranslationStatsModule.useGetMigrationTranslationStats =
|
||||
jest.fn((migrationId: string) => {
|
||||
const result = structuredClone(mockedMigrationTranslationStats)[migrationId];
|
||||
return {
|
||||
data: result,
|
||||
isLoading: false,
|
||||
} as unknown as ReturnType<
|
||||
typeof useGetMigrationTranslationStatsModule.useGetMigrationTranslationStats
|
||||
>;
|
||||
});
|
||||
|
||||
const mockUseGetMigrationRules: typeof useGetMigrationRulesModule.useGetMigrationRules = jest.fn(
|
||||
({ migrationId }) => {
|
||||
const { data, total } = mockedMigrationResultsObj[migrationId];
|
||||
return {
|
||||
data: {
|
||||
ruleMigrations: data,
|
||||
total,
|
||||
},
|
||||
isLoading: false,
|
||||
} as unknown as ReturnType<typeof useGetMigrationRulesModule.useGetMigrationRules>;
|
||||
}
|
||||
);
|
||||
|
||||
describe('Migrations: Translated Rules Page', () => {
|
||||
beforeEach(() => {
|
||||
useLatestStatsSpy.mockReturnValue(mockedLatestStatsEmpty);
|
||||
useNavigationSpy.mockReturnValue({ navigateTo: mockNavigateTo, getAppUrl: jest.fn() });
|
||||
useGetIntegrationsSpy.mockReturnValue({
|
||||
getIntegrations: mockGetIntegrations,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
useInvalidateGetMigrationTranslationStatsSpy.mockReturnValue(jest.fn());
|
||||
useInvalidateGetMigrationRulesSpy.mockReturnValue(jest.fn());
|
||||
useMissingPrivilegesSpy.mockReturnValue(mockMissingDetectionsPrivileges);
|
||||
useGetMigrationMissingPrivilegesSpy.mockReturnValue(
|
||||
mockGetMigrationMissingPrivileges as unknown as ReturnType<
|
||||
typeof useGetMigrationMissingPrivilegesModule.useGetMigrationMissingPrivileges
|
||||
>
|
||||
);
|
||||
useCalloutStorageSpy.mockReturnValue(mockHiddenCallStorageResult);
|
||||
useGetMigrationRulesSpy.mockImplementation(mockUseGetMigrationRules);
|
||||
useGetMissingResourcesSpy.mockReturnValue({
|
||||
getMissingResources: jest.fn(() => []),
|
||||
isLoading: false,
|
||||
} as unknown as ReturnType<typeof useGetMissingResourcesModule.useGetMissingResources>);
|
||||
|
||||
useGetMigrationTranslationStatsSpy.mockImplementation(mockUseMigrationRuleTransationStats);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('With No MigrationId', () => {
|
||||
test('should render empty page when no translated rules are available', () => {
|
||||
renderTestComponent({
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('siemMigrationsTranslatedRulesEmptyPageHeader')).toBeVisible();
|
||||
expect(screen.queryByTestId('siemMigrationsRulesTable')).toBeNull();
|
||||
});
|
||||
|
||||
test('should render skeleton when loading', () => {
|
||||
useLatestStatsSpy.mockReturnValue({ ...mockedLatestStatsEmpty, isLoading: true });
|
||||
renderTestComponent();
|
||||
|
||||
expect(screen.getByTestId('migrationRulesPageLoading')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should redirect to the most recent migration when no migrationId is provided and migrations exists', () => {
|
||||
useLatestStatsSpy.mockReturnValue(mockedLatestStats);
|
||||
renderTestComponent();
|
||||
|
||||
expect(mockNavigateTo).toHaveBeenCalledWith({
|
||||
deepLinkId: 'siem_migrations-rules',
|
||||
path: '2',
|
||||
});
|
||||
});
|
||||
|
||||
test('should render missing privileges panel', () => {
|
||||
const mockMissingPrivileges: useMissingPrivilegesModule.MissingPrivileges = {
|
||||
...mockMissingDetectionsPrivileges,
|
||||
indexPrivileges: [['index', ['privilege1', 'privilege2']]],
|
||||
};
|
||||
|
||||
useLatestStatsSpy.mockReturnValue(mockedLatestStats);
|
||||
useMissingPrivilegesSpy.mockReturnValue(mockMissingPrivileges);
|
||||
useCalloutStorageSpy.mockReturnValue(mockVisibleCallStorageResult);
|
||||
|
||||
const missingPrivilegesHash = hash(mockMissingPrivileges);
|
||||
|
||||
renderTestComponent({
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(useCalloutStorageSpy).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
screen.getByTestId(`callout-missing-siem-migrations-privileges-${missingPrivilegesHash}`)
|
||||
).toBeVisible();
|
||||
|
||||
expect(
|
||||
screen.getByTestId(`callout-missing-siem-migrations-privileges-${missingPrivilegesHash}`)
|
||||
).toHaveTextContent(/Insufficient privileges/);
|
||||
});
|
||||
|
||||
test('should render unknown migration state if no migrationId is provided', () => {
|
||||
useLatestStatsSpy.mockReturnValue(mockedLatestStats);
|
||||
renderTestComponent();
|
||||
expect(screen.getByTestId('siemMigrationsUnknown')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe('With MigrationId', () => {
|
||||
test('should render migration table successfully if migrationId is provided', async () => {
|
||||
useLatestStatsSpy.mockReturnValue(mockedLatestStats);
|
||||
renderTestComponent({
|
||||
migrationId: '1',
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('siemMigrationsRulesTable')).toBeVisible();
|
||||
});
|
||||
|
||||
expect(screen.getAllByTestId(/ruleName/)).toHaveLength(2);
|
||||
expect(screen.getAllByTestId(/ruleName/)[0]).toHaveTextContent(/Converted Splunk Rule -/);
|
||||
// only successful is selectable
|
||||
expect(screen.getAllByTitle(/Select row 1/)).toHaveLength(1);
|
||||
expect(screen.getByTitle(/Not fully translated migration rule/)).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -30,8 +30,9 @@ import { useInvalidateGetMigrationTranslationStats } from '../logic/use_get_migr
|
|||
import { useGetIntegrations } from '../service/hooks/use_get_integrations';
|
||||
import { PageTitle } from './page_title';
|
||||
import { RuleMigrationsUploadMissingPanel } from '../components/migration_status_panels/upload_missing_panel';
|
||||
import { EmptyMigrationRulesPage } from './empty';
|
||||
|
||||
type MigrationRulesPageProps = RouteComponentProps<{ migrationId?: string }>;
|
||||
export type MigrationRulesPageProps = RouteComponentProps<{ migrationId?: string }>;
|
||||
|
||||
export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
|
||||
({
|
||||
|
@ -54,13 +55,7 @@ export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
|
|||
}, [getIntegrations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to landing page if there are no migrations
|
||||
if (!ruleMigrationsStats.length) {
|
||||
navigateTo({ deepLinkId: SecurityPageName.landing, path: 'siem_migrations' });
|
||||
if (isLoading || ruleMigrationsStats.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -96,6 +91,9 @@ export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
|
|||
const pageTitle = useMemo(() => <PageTitle />, []);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (ruleMigrationsStats.length === 0 && !migrationId) {
|
||||
return <EmptyMigrationRulesPage />;
|
||||
}
|
||||
const migrationStats = ruleMigrationsStats.find((stats) => stats.id === migrationId);
|
||||
if (!migrationId || !migrationStats) {
|
||||
return <UnknownMigration />;
|
||||
|
@ -141,6 +139,7 @@ export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
|
|||
<NeedAdminForUpdateRulesCallOut />
|
||||
<MissingPrivilegesCallOut />
|
||||
<EuiSkeletonLoading
|
||||
data-test-subj="migrationRulesPageLoading"
|
||||
isLoading={isLoading}
|
||||
loadingContent={
|
||||
<>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle, useEuiTheme } from '
|
|||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export const PageTitle: React.FC = React.memo(() => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
|
|
@ -25,3 +25,25 @@ export const BETA_TOOLTIP = i18n.translate(
|
|||
'This functionality is in technical preview and is subject to change. Please use SIEM Migrations with caution in production environments.',
|
||||
}
|
||||
);
|
||||
|
||||
export const TRANSLATED_RULES_EMPTY_PAGE_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.emptyPageMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Translate your existing Splunk Rules with Elastic Automatic Migration. Got to SIEM rule migration for step-by-step guidance.',
|
||||
}
|
||||
);
|
||||
|
||||
export const TRANSLATED_RULES_EMPTY_PAGE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.emptyPageTitle',
|
||||
{
|
||||
defaultMessage: 'No migrations to View',
|
||||
}
|
||||
);
|
||||
|
||||
export const TRANSLATED_RULES_EMPTY_PAGE_CTA = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.emptyPageCta',
|
||||
{
|
||||
defaultMessage: 'Start SIEM rule Migration',
|
||||
}
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue