[Asset Inventory] Add OnboardingSuccessCallout to the All Assets page (#216739)

## Summary

It closes https://github.com/elastic/kibana/issues/210717

This PR adds the OnboardingSuccessCallout component to the All Assets
page, but the component is only visible to the user who initiated the
Onboarding and it no longer shows once dismissed by the user.

Also, this PR adds the `checkAndInitAssetCriticalityResources` to the
enablement as it's required by the transforms installed during the
Entity Store initialization.

## Recording


https://github.com/user-attachments/assets/31130195-c67c-4a55-aa37-555d527f38f0
This commit is contained in:
Paulo Silva 2025-04-02 06:37:40 -07:00 committed by GitHub
parent cf289cbd1e
commit 1b3b66b6de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 63 additions and 2 deletions

View file

@ -14,6 +14,7 @@ export const mockUseOnboardingSuccessCallout = (
isOnboardingSuccessCalloutVisible: false,
hideOnboardingSuccessCallout: jest.fn(),
showOnboardingSuccessCallout: jest.fn(),
onAddIntegrationClick: jest.fn(),
...overrides,
};
return defaultMock;

View file

@ -11,6 +11,17 @@ import { useOnboardingSuccessCallout } from './use_onboarding_success_callout';
jest.mock('react-use/lib/useLocalStorage');
const mockLocalStorage = [false, jest.fn()];
const mockNavigateToApp = jest.fn();
jest.mock('../../../../common/lib/kibana', () => ({
useKibana: () => ({
services: {
application: {
navigateToApp: mockNavigateToApp,
},
},
}),
}));
describe('useOnboardingSuccessCallout', () => {
beforeEach(() => {
@ -44,4 +55,12 @@ describe('useOnboardingSuccessCallout', () => {
expect(mockLocalStorage[1]).toHaveBeenCalledWith(false);
});
});
it('should call application.navigateToApp when onAddIntegrationClick is called', () => {
const { result } = renderHook(() => useOnboardingSuccessCallout());
result.current.onAddIntegrationClick();
expect(mockNavigateToApp).toHaveBeenCalledWith('integrations');
});
});

View file

@ -6,6 +6,8 @@
*/
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { INTEGRATIONS_PLUGIN_ID } from '@kbn/fleet-plugin/common';
import { useKibana } from '../../../../common/lib/kibana';
import { LOCAL_STORAGE_ONBOARDING_SUCCESS_CALLOUT_KEY } from '../../../constants';
/**
@ -17,13 +19,18 @@ export const useOnboardingSuccessCallout = () => {
const [isOnboardingSuccessCalloutVisible, setOnboardingSuccessCalloutVisible] =
useLocalStorage<boolean>(LOCAL_STORAGE_ONBOARDING_SUCCESS_CALLOUT_KEY, false);
const { application } = useKibana().services;
const hideOnboardingSuccessCallout = () => setOnboardingSuccessCalloutVisible(false);
const showOnboardingSuccessCallout = () => setOnboardingSuccessCalloutVisible(true);
const onAddIntegrationClick = () => application.navigateToApp(INTEGRATIONS_PLUGIN_ID);
return {
isOnboardingSuccessCalloutVisible,
hideOnboardingSuccessCallout,
showOnboardingSuccessCallout,
onAddIntegrationClick,
};
};

View file

@ -57,4 +57,21 @@ describe('OnboardingSuccessCallout', () => {
expect(mockHideCallout).toHaveBeenCalled();
});
});
it('should have an "Add integration" button that calls onAddIntegrationClick when clicked', async () => {
const mockAddIntegrationClick = jest.fn();
(useOnboardingSuccessCallout as jest.Mock).mockReturnValue(
mockUseOnboardingSuccessCallout({
isOnboardingSuccessCalloutVisible: true,
onAddIntegrationClick: mockAddIntegrationClick,
})
);
renderWithTestProvider(<OnboardingSuccessCallout />);
await userEvent.click(screen.getByRole('button', { name: /add integration/i }));
expect(mockAddIntegrationClick).toHaveBeenCalled();
});
});

View file

@ -5,17 +5,22 @@
* 2.0.
*/
import React from 'react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useOnboardingSuccessCallout } from './hooks/use_onboarding_success_callout';
import { TEST_SUBJ_ONBOARDING_SUCCESS_CALLOUT } from '../../constants';
/**
* Component to display a success callout after onboarding is completed.
* Only visible if the user enabled the onboarding process.
*/
export const OnboardingSuccessCallout = () => {
const { isOnboardingSuccessCalloutVisible, hideOnboardingSuccessCallout } =
const { isOnboardingSuccessCalloutVisible, hideOnboardingSuccessCallout, onAddIntegrationClick } =
useOnboardingSuccessCallout();
return isOnboardingSuccessCalloutVisible ? (
<>
<EuiSpacer size="l" />
<EuiCallOut
onDismiss={hideOnboardingSuccessCallout}
title={
@ -34,6 +39,12 @@ export const OnboardingSuccessCallout = () => {
defaultMessage="Asset Inventory is now set up and ready to use. You can start managing your assets with enhanced visibility and context, empowering your security team to make informed decisions."
/>
</p>
<EuiButton size="s" color="success" onClick={onAddIntegrationClick}>
<FormattedMessage
id="xpack.securitySolution.assetInventory.addIntegration"
defaultMessage="Add integration"
/>
</EuiButton>
</EuiCallOut>
<EuiSpacer size="l" />
</>

View file

@ -22,6 +22,7 @@ import {
} from '../hooks/use_asset_inventory_url_state/use_asset_inventory_url_state';
import { LOCAL_STORAGE_COLUMNS_KEY, LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY } from '../constants';
import { OnboardingSuccessCallout } from '../components/onboarding/onboarding_success_callout';
const getDefaultQuery = ({ query, filters }: AssetsBaseURLQuery): URLQuery => ({
query,
@ -53,6 +54,7 @@ export const AllAssets = () => {
<AssetInventorySearchBar query={urlQuery} setQuery={setUrlQuery} />
<EuiPageTemplate.Section>
<AssetInventoryTitle />
<OnboardingSuccessCallout />
<AssetInventoryFilters setQuery={setUrlQuery} />
<AssetInventoryBarChart
isLoading={isLoadingChartData}

View file

@ -14,6 +14,7 @@ import { API_VERSIONS } from '../../../../common/constants';
import type { AssetInventoryRoutesDeps } from '../types';
import { InitEntityStoreRequestBody } from '../../../../common/api/entity_analytics/entity_store/enable.gen';
import { ASSET_INVENTORY_ENABLE_API_PATH } from '../../../../common/api/asset_inventory/constants';
import { checkAndInitAssetCriticalityResources } from '../../entity_analytics/asset_criticality/check_and_init_asset_criticality_resources';
export const enableAssetInventoryRoute = (
router: AssetInventoryRoutesDeps['router'],
@ -44,6 +45,9 @@ export const enableAssetInventoryRoute = (
const secSol = await context.securitySolution;
try {
// Criticality resources are required by the Entity Store transforms
await checkAndInitAssetCriticalityResources(context, logger);
const body = await secSol.getAssetInventoryClient().enable(secSol, request.body);
return response.ok({ body });