Skip to content

Commit

Permalink
Merge branch 'main' into siem_migrations_upselling
Browse files Browse the repository at this point in the history
  • Loading branch information
logeekal authored Feb 28, 2025
2 parents 027c7ad + bdc4790 commit a549610
Show file tree
Hide file tree
Showing 46 changed files with 1,351 additions and 81 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,7 @@
"@kbn/whereis-pkg-cli": "link:packages/kbn-whereis-pkg-cli",
"@kbn/yarn-lock-validator": "link:packages/kbn-yarn-lock-validator",
"@mapbox/vector-tile": "1.3.1",
"@mswjs/http-middleware": "0.10.1",
"@mswjs/http-middleware": "0.10.3",
"@octokit/rest": "^21.1.1",
"@parcel/watcher": "^2.1.0",
"@playwright/test": "1.49.0",
Expand Down Expand Up @@ -1808,7 +1808,7 @@
"mochawesome-merge": "^4.3.0",
"mock-fs": "^5.1.2",
"ms-chromium-edge-driver": "^0.5.1",
"msw": "~2.5.2",
"msw": "~2.7.0",
"mutation-observer": "^1.0.3",
"native-hdr-histogram": "^1.0.0",
"nock": "12.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/dev/build/tasks/os_packages/docker_generator/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function runDockerGenerator(
*/
if (flags.baseImage === 'wolfi')
baseImageName =
'docker.elastic.co/wolfi/chainguard-base:latest@sha256:c66fdafe581a6ab1668a962015de4ce4666a60ed601d24f019f03bb4aaab8eeb';
'docker.elastic.co/wolfi/chainguard-base:latest@sha256:6387bd4c462007eaecaf13a423aea99c8a8452da09244c129703324aa97769c6';

let imageFlavor = '';
if (flags.baseImage === 'wolfi' && !flags.serverless && !flags.cloud) imageFlavor += `-wolfi`;
Expand Down
2 changes: 1 addition & 1 deletion versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"previousMajor": true
},
{
"version": "7.17.28",
"version": "7.17.29",
"branch": "7.17"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
*/

/** @type {import('@jest/types').Config.InitialOptions} */

module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/solutions/security/plugins/cloud_security_posture'],
// setupFilesAfterEnv is a list of scripts that are executed after JSDOM is initialized.
setupFilesAfterEnv: [
'<rootDir>/x-pack/solutions/security/plugins/cloud_security_posture/jest.setup.js',
],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/security/plugins/cloud_security_posture',
coverageReporters: ['text', 'html'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.
*/

// This is a fix for the BroadcastChannel API not being available in JSDOM.
// This is a temporary workaround until JSDOM supports BroadcastChannel.
// https://github.com/mswjs/data/issues/306
// https://github.com/elastic/kibana/pull/208427
class BroadcastChannelMock {
constructor() {
this.onmessage = null;
}
postMessage(data) {
if (this.onmessage) {
this.onmessage({ data });
}
}
}
global.BroadcastChannel = BroadcastChannelMock;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { CspClientPluginStartDeps } from '@kbn/cloud-security-posture';
import { defaultHandlers } from './handlers';
import { getMockDependencies } from '../fixtures/get_mock_dependencies';
import { MOCK_SERVER_LICENSING_INFO_URL } from './handlers/licensing.handlers.mock';

/**
* Mock the lastValueFrom function from rxjs to return the result of the promise instead of the Observable
* This is for simplifying the testing by avoiding the need to subscribe to the Observable while producing the same result
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const ASSET_INVENTORY_ENABLE_API_PATH = '/api/asset_inventory/enable';
export const ASSET_INVENTORY_STATUS_API_PATH = '/api/asset_inventory/status';
export const ASSET_INVENTORY_DELETE_API_PATH = '/api/asset_inventory/delete';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { ServerApiError } from '../../../public/common/types';
import type { EntityAnalyticsPrivileges } from '../entity_analytics';
import type { InitEntityStoreResponse } from '../entity_analytics/entity_store/enable.gen';

export type AssetInventoryStatus =
| 'disabled'
| 'initializing'
| 'empty'
| 'permission_denied'
| 'ready';

export interface AssetInventoryStatusResponse {
status: AssetInventoryStatus;
privileges?: EntityAnalyticsPrivileges['privileges'];
}

export type AssetInventoryEnableResponse = InitEntityStoreResponse;

export interface AssetInventoryServerApiError {
body: ServerApiError;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLoadingLogo, EuiSpacer } from '@elastic/eui';
import { InventoryTitle } from './inventory_title';
import { CenteredWrapper } from './onboarding/centered_wrapper';

/**
* A loading state for the asset inventory page.
*/
export const AssetInventoryLoading = () => (
<EuiFlexGroup>
<EuiFlexItem>
<InventoryTitle />
<EuiSpacer size="l" />
<CenteredWrapper>
<EuiLoadingLogo logo="logoSecurity" size="xl" />
</CenteredWrapper>
</EuiFlexItem>
</EuiFlexGroup>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { css } from '@emotion/react';
import React from 'react';

// width of the illustration used in the empty states
const DEFAULT_ILLUSTRATION_WIDTH = 360;

/**
* A container component that maintains a fixed width for SVG elements,
* this prevents the EmptyState component from flickering while the SVGs are loading.
*/
export const EmptyStateIllustrationContainer: React.FC<{ children: React.ReactNode }> = ({
children,
}) => (
<div
css={css`
width: ${DEFAULT_ILLUSTRATION_WIDTH}px;
`}
>
{children}
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';

export const InventoryTitle = () => {
return (
<EuiTitle size="l" data-test-subj="inventory-title">
<h1>
<FormattedMessage
id="xpack.securitySolution.assetInventory.inventoryTitle"
defaultMessage="Inventory"
/>
</h1>
</EuiTitle>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 type { FC, PropsWithChildren } from 'react';
import { GetStarted } from './get_started';
import { AssetInventoryLoading } from '../asset_inventory_loading';
import { useAssetInventoryStatus } from '../../hooks/use_asset_inventory_status';

/**
* This component serves as a wrapper to render appropriate onboarding screens
* based on the current onboarding status. If no specific onboarding status
* matches, it will render the child components.
*/
export const AssetInventoryOnboarding: FC<PropsWithChildren> = ({ children }) => {
const { data, isLoading } = useAssetInventoryStatus();

if (isLoading || !data) {
return <AssetInventoryLoading />;
}

const { status, privileges } = data;

// Render different screens based on the onboarding status.
switch (status) {
case 'disabled': // The user has not yet started the onboarding process.
return <GetStarted />;
case 'initializing': // Todo: The onboarding process is currently initializing.
return <div>{'Initializing...'}</div>;
case 'empty': // Todo: Onboarding cannot proceed because no relevant data was found.
return <div>{'No data found.'}</div>;
case 'permission_denied': // Todo: User lacks the necessary permissions to proceed.
return (
<div>
{'Permission denied.'}
<pre>{JSON.stringify(privileges)}</pre>
</div>
);
default:
// If no onboarding status matches, render the child components.
return children;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, type CommonProps } from '@elastic/eui';
import { css } from '@emotion/react';
import React from 'react';

/**
* A wrapper that centers its children both horizontally and vertically.
*/
export const CenteredWrapper = ({
children,
...rest
}: { children: React.ReactNode } & CommonProps) => (
<EuiFlexGroup
css={css`
// 250px is roughly the Kibana chrome with a page title and tabs
min-height: calc(100vh - 250px);
`}
justifyContent="center"
alignItems="center"
direction="column"
{...rest}
>
<EuiFlexItem>{children}</EuiFlexItem>
</EuiFlexGroup>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { GetStarted } from './get_started';
import { useEnableAssetInventory } from './hooks/use_enable_asset_inventory';
import { TestProvider } from '../../test/test_provider';
import { userEvent } from '@testing-library/user-event';

jest.mock('./hooks/use_enable_asset_inventory', () => ({
useEnableAssetInventory: jest.fn(),
}));

const mockGetStarted = {
isEnabling: false,
error: null,
reset: jest.fn(),
enableAssetInventory: jest.fn(),
};

const renderWithProvider = (children: React.ReactNode) => {
return render(<TestProvider>{children}</TestProvider>);
};

describe('GetStarted Component', () => {
beforeEach(() => {
jest.resetAllMocks();
(useEnableAssetInventory as jest.Mock).mockReturnValue(mockGetStarted);
});

it('renders the component', () => {
renderWithProvider(<GetStarted />);

expect(screen.getByText(/get started with asset inventory/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /enable asset inventory/i })).toBeInTheDocument();

expect(screen.getByText(/read documentation/i).closest('a')).toHaveAttribute(
'href',
'https://ela.st/asset-inventory'
);
});

it('calls enableAssetInventory when enable asset inventory button is clicked', async () => {
renderWithProvider(<GetStarted />);

await userEvent.click(screen.getByRole('button', { name: /enable asset inventory/i }));

expect(mockGetStarted.enableAssetInventory).toHaveBeenCalled();
});

it('shows a loading spinner when enabling', () => {
(useEnableAssetInventory as jest.Mock).mockReturnValue({
...mockGetStarted,
isEnabling: true,
});

renderWithProvider(<GetStarted />);

expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /enabling asset inventory/i })).toBeInTheDocument();
});

it('displays an error message when there is an error', () => {
const errorMessage = 'Task Manager is not available';
(useEnableAssetInventory as jest.Mock).mockReturnValue({
...mockGetStarted,
error: errorMessage,
});

renderWithProvider(<GetStarted />);

expect(screen.getByText(/sorry, there was an error/i)).toBeInTheDocument();
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});

it('calls reset when error message is dismissed', async () => {
(useEnableAssetInventory as jest.Mock).mockReturnValue({
...mockGetStarted,
error: 'Task Manager is not available',
});

renderWithProvider(<GetStarted />);

await userEvent.click(screen.getByRole('button', { name: /dismiss/i }));

await waitFor(() => expect(mockGetStarted.reset).toHaveBeenCalled());
});
});
Loading

0 comments on commit a549610

Please sign in to comment.