mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.16`: - [[SecuritySolution][Onboarding] Catch completion check failure (#196389)](https://github.com/elastic/kibana/pull/196389) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Angela Chuang","email":"6295984+angorayc@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-21T10:56:50Z","message":"[SecuritySolution][Onboarding] Catch completion check failure (#196389)\n\n## Summary\r\n\r\nFixes: https://github.com/elastic/kibana/issues/196091 |\r\n~https://github.com/elastic/kibana/issues/196649~\r\n\r\n1. Add the error handling when an error occurred during check complete.\r\n2. Update the AI assistant capabilities to\r\n`[['securitySolutionAssistant.ai-assistant', 'actions.show']]`\r\n\r\n**Steps to verify:**\r\n\r\n\r\nhttps://p.elstc.co/paste/+6OYqx41#tZxjvqXgQJ2uRlCSqZH8ADMEAvR1+qnXe-5kEbt+bro\r\nLogin with the user without indices privilege. It should display the tab\r\ncontent without the completion information.\r\n\r\nWhen completion check failed - it should display and error `toast`,\r\nregard the card as `incomplete` and `show the content`:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/30b1654e-99cc-4582-8beb-c4a5fb005e6f\r\n\r\nAI assistant should not show connectors options as `add integration` is\r\nregarded as incomplete:\r\n\r\n<img width=\"1761\" alt=\"Screenshot 2024-10-17 at 15 02 42\"\r\nsrc=\"https://github.com/user-attachments/assets/07fb317e-57d6-4980-aae3-7eb2d0fce12a\">\r\n\r\n\r\nThen add the index privilege:\r\n<img width=\"2208\" alt=\"Screenshot 2024-10-17 at 15 07 36\"\r\nsrc=\"https://github.com/user-attachments/assets/bb879964-e31b-4ee3-8eb3-dff0381be287\">\r\n\r\nWhen completion check success: it should display the completion results:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2a27a042-c634-4f44-bfd0-2ae503f396a2\r\n\r\n\r\n\r\nSet `actions and connectors` to read only:\r\n<img width=\"779\" alt=\"Screenshot 2024-10-17 at 15 04 28\"\r\nsrc=\"https://github.com/user-attachments/assets/098b0c90-30a9-4e82-ad16-10d2cd64a9cc\">\r\n\r\n\r\n<img width=\"1250\" alt=\"Screenshot 2024-10-17 at 16 40 18\"\r\nsrc=\"https://github.com/user-attachments/assets/5e677d5a-32b8-4cea-b240-207a3f055f9c\">\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"10ec204128776930c48376a848fe20b1301569f9","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","backport","v9.0.0","Team:Threat Hunting:Explore","ci:cloud-deploy","v8.16.0"],"title":"[SecuritySolution][Onboarding] Catch completion check failure","number":196389,"url":"https://github.com/elastic/kibana/pull/196389","mergeCommit":{"message":"[SecuritySolution][Onboarding] Catch completion check failure (#196389)\n\n## Summary\r\n\r\nFixes: https://github.com/elastic/kibana/issues/196091 |\r\n~https://github.com/elastic/kibana/issues/196649~\r\n\r\n1. Add the error handling when an error occurred during check complete.\r\n2. Update the AI assistant capabilities to\r\n`[['securitySolutionAssistant.ai-assistant', 'actions.show']]`\r\n\r\n**Steps to verify:**\r\n\r\n\r\nhttps://p.elstc.co/paste/+6OYqx41#tZxjvqXgQJ2uRlCSqZH8ADMEAvR1+qnXe-5kEbt+bro\r\nLogin with the user without indices privilege. It should display the tab\r\ncontent without the completion information.\r\n\r\nWhen completion check failed - it should display and error `toast`,\r\nregard the card as `incomplete` and `show the content`:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/30b1654e-99cc-4582-8beb-c4a5fb005e6f\r\n\r\nAI assistant should not show connectors options as `add integration` is\r\nregarded as incomplete:\r\n\r\n<img width=\"1761\" alt=\"Screenshot 2024-10-17 at 15 02 42\"\r\nsrc=\"https://github.com/user-attachments/assets/07fb317e-57d6-4980-aae3-7eb2d0fce12a\">\r\n\r\n\r\nThen add the index privilege:\r\n<img width=\"2208\" alt=\"Screenshot 2024-10-17 at 15 07 36\"\r\nsrc=\"https://github.com/user-attachments/assets/bb879964-e31b-4ee3-8eb3-dff0381be287\">\r\n\r\nWhen completion check success: it should display the completion results:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2a27a042-c634-4f44-bfd0-2ae503f396a2\r\n\r\n\r\n\r\nSet `actions and connectors` to read only:\r\n<img width=\"779\" alt=\"Screenshot 2024-10-17 at 15 04 28\"\r\nsrc=\"https://github.com/user-attachments/assets/098b0c90-30a9-4e82-ad16-10d2cd64a9cc\">\r\n\r\n\r\n<img width=\"1250\" alt=\"Screenshot 2024-10-17 at 16 40 18\"\r\nsrc=\"https://github.com/user-attachments/assets/5e677d5a-32b8-4cea-b240-207a3f055f9c\">\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"10ec204128776930c48376a848fe20b1301569f9"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196389","number":196389,"mergeCommit":{"message":"[SecuritySolution][Onboarding] Catch completion check failure (#196389)\n\n## Summary\r\n\r\nFixes: https://github.com/elastic/kibana/issues/196091 |\r\n~https://github.com/elastic/kibana/issues/196649~\r\n\r\n1. Add the error handling when an error occurred during check complete.\r\n2. Update the AI assistant capabilities to\r\n`[['securitySolutionAssistant.ai-assistant', 'actions.show']]`\r\n\r\n**Steps to verify:**\r\n\r\n\r\nhttps://p.elstc.co/paste/+6OYqx41#tZxjvqXgQJ2uRlCSqZH8ADMEAvR1+qnXe-5kEbt+bro\r\nLogin with the user without indices privilege. It should display the tab\r\ncontent without the completion information.\r\n\r\nWhen completion check failed - it should display and error `toast`,\r\nregard the card as `incomplete` and `show the content`:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/30b1654e-99cc-4582-8beb-c4a5fb005e6f\r\n\r\nAI assistant should not show connectors options as `add integration` is\r\nregarded as incomplete:\r\n\r\n<img width=\"1761\" alt=\"Screenshot 2024-10-17 at 15 02 42\"\r\nsrc=\"https://github.com/user-attachments/assets/07fb317e-57d6-4980-aae3-7eb2d0fce12a\">\r\n\r\n\r\nThen add the index privilege:\r\n<img width=\"2208\" alt=\"Screenshot 2024-10-17 at 15 07 36\"\r\nsrc=\"https://github.com/user-attachments/assets/bb879964-e31b-4ee3-8eb3-dff0381be287\">\r\n\r\nWhen completion check success: it should display the completion results:\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2a27a042-c634-4f44-bfd0-2ae503f396a2\r\n\r\n\r\n\r\nSet `actions and connectors` to read only:\r\n<img width=\"779\" alt=\"Screenshot 2024-10-17 at 15 04 28\"\r\nsrc=\"https://github.com/user-attachments/assets/098b0c90-30a9-4e82-ad16-10d2cd64a9cc\">\r\n\r\n\r\n<img width=\"1250\" alt=\"Screenshot 2024-10-17 at 16 40 18\"\r\nsrc=\"https://github.com/user-attachments/assets/5e677d5a-32b8-4cea-b240-207a3f055f9c\">\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"10ec204128776930c48376a848fe20b1301569f9"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com>
This commit is contained in:
parent
29333f8587
commit
f3bb299e32
6 changed files with 181 additions and 16 deletions
|
@ -25,6 +25,8 @@ export const assistantCardConfig: OnboardingCardConfig<AssistantCardMetadata> =
|
|||
)
|
||||
),
|
||||
checkComplete: checkAssistantCardComplete,
|
||||
capabilities: 'securitySolutionAssistant.ai-assistant',
|
||||
// Both capabilities are needed for this card, so we should use a double array to create an AND conditional
|
||||
// (a single array would create an OR conditional between them)
|
||||
capabilities: [['securitySolutionAssistant.ai-assistant', 'actions.show']],
|
||||
licenseType: 'enterprise',
|
||||
};
|
||||
|
|
|
@ -28,4 +28,15 @@ describe('IntegrationsCard', () => {
|
|||
);
|
||||
expect(getByTestId('loadingInstalledIntegrations')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the content', () => {
|
||||
const { queryByTestId } = render(
|
||||
<IntegrationsCard
|
||||
{...props}
|
||||
checkCompleteMetadata={{ installedIntegrationsCount: 1, isAgentRequired: false }}
|
||||
/>
|
||||
);
|
||||
expect(queryByTestId('loadingInstalledIntegrations')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('integrationsCardGridTabs')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ jest.mock('rxjs', () => ({
|
|||
}));
|
||||
|
||||
describe('checkIntegrationsCardComplete', () => {
|
||||
const mockLastValueFrom = lastValueFrom as jest.Mock;
|
||||
const mockHttpGet: jest.Mock = jest.fn();
|
||||
const mockSearch: jest.Mock = jest.fn();
|
||||
const mockService = {
|
||||
|
@ -27,6 +28,11 @@ describe('checkIntegrationsCardComplete', () => {
|
|||
search: mockSearch,
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addError: jest.fn(),
|
||||
},
|
||||
},
|
||||
} as unknown as StartServices;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -38,7 +44,7 @@ describe('checkIntegrationsCardComplete', () => {
|
|||
items: [],
|
||||
});
|
||||
|
||||
(lastValueFrom as jest.Mock).mockResolvedValue({
|
||||
mockLastValueFrom.mockResolvedValue({
|
||||
rawResponse: {
|
||||
hits: { total: 0 },
|
||||
},
|
||||
|
@ -60,7 +66,7 @@ describe('checkIntegrationsCardComplete', () => {
|
|||
items: [{ status: installationStatuses.Installed }],
|
||||
});
|
||||
|
||||
(lastValueFrom as jest.Mock).mockResolvedValue({
|
||||
mockLastValueFrom.mockResolvedValue({
|
||||
rawResponse: {
|
||||
hits: { total: 0 },
|
||||
},
|
||||
|
@ -86,7 +92,7 @@ describe('checkIntegrationsCardComplete', () => {
|
|||
],
|
||||
});
|
||||
|
||||
(lastValueFrom as jest.Mock).mockResolvedValue({
|
||||
mockLastValueFrom.mockResolvedValue({
|
||||
rawResponse: {
|
||||
hits: { total: 1 },
|
||||
},
|
||||
|
@ -103,4 +109,43 @@ describe('checkIntegrationsCardComplete', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error toast when fetching integrations data fails', async () => {
|
||||
const err = new Error('Failed to fetch integrations data');
|
||||
mockHttpGet.mockRejectedValue(err);
|
||||
|
||||
const res = await checkIntegrationsCardComplete(mockService);
|
||||
|
||||
expect(mockService.notifications.toasts.addError).toHaveBeenCalledWith(err, {
|
||||
title: 'Error fetching integrations data',
|
||||
});
|
||||
expect(res).toEqual({
|
||||
isComplete: false,
|
||||
metadata: {
|
||||
installedIntegrationsCount: 0,
|
||||
isAgentRequired: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an error toast when fetching agents data fails', async () => {
|
||||
const err = new Error('Failed to fetch agents data');
|
||||
mockLastValueFrom.mockRejectedValue(err);
|
||||
|
||||
const res = await checkIntegrationsCardComplete(mockService);
|
||||
|
||||
expect(mockService.notifications.toasts.addError).toHaveBeenCalledWith(
|
||||
new Error('Failed to fetch agents data'),
|
||||
{
|
||||
title: 'Error fetching agents data',
|
||||
}
|
||||
);
|
||||
expect(res).toEqual({
|
||||
isComplete: false,
|
||||
metadata: {
|
||||
installedIntegrationsCount: 0,
|
||||
isAgentRequired: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,18 +17,37 @@ import type { IntegrationCardMetadata } from './types';
|
|||
export const checkIntegrationsCardComplete: OnboardingCardCheckComplete<
|
||||
IntegrationCardMetadata
|
||||
> = async (services: StartServices) => {
|
||||
const packageData = await services.http.get<GetPackagesResponse>(
|
||||
EPM_API_ROUTES.INSTALL_BY_UPLOAD_PATTERN,
|
||||
{
|
||||
const packageData = await services.http
|
||||
.get<GetPackagesResponse>(EPM_API_ROUTES.INSTALL_BY_UPLOAD_PATTERN, {
|
||||
version: '2023-10-31',
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
services.notifications.toasts.addError(err, {
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.onboarding.integrationsCard.checkComplete.fetchIntegrations.errorTitle',
|
||||
{
|
||||
defaultMessage: 'Error fetching integrations data',
|
||||
}
|
||||
),
|
||||
});
|
||||
return { items: [] };
|
||||
});
|
||||
|
||||
const agentsData = await lastValueFrom(
|
||||
services.data.search.search({
|
||||
params: { index: AGENT_INDEX, body: { size: 1 } },
|
||||
})
|
||||
);
|
||||
).catch((err: Error) => {
|
||||
services.notifications.toasts.addError(err, {
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.onboarding.integrationsCard.checkComplete.fetchAgents.errorTitle',
|
||||
{
|
||||
defaultMessage: 'Error fetching agents data',
|
||||
}
|
||||
),
|
||||
});
|
||||
return { rawResponse: { hits: { total: 0 } } };
|
||||
});
|
||||
|
||||
const installed = packageData?.items?.filter(
|
||||
(pkg) =>
|
||||
|
|
|
@ -11,9 +11,11 @@ import { useCompletedCards } from './use_completed_cards';
|
|||
import type { OnboardingGroupConfig } from '../../../types';
|
||||
import type { OnboardingCardId } from '../../../constants';
|
||||
import { mockReportCardComplete } from '../../__mocks__/onboarding_context_mocks';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
const defaultStoredCompletedCardIds: OnboardingCardId[] = [];
|
||||
const mockSetStoredCompletedCardIds = jest.fn();
|
||||
const mockUseKibana = useKibana as jest.Mock;
|
||||
const mockUseStoredCompletedCardIds = jest.fn(() => [
|
||||
defaultStoredCompletedCardIds,
|
||||
mockSetStoredCompletedCardIds,
|
||||
|
@ -24,6 +26,15 @@ jest.mock('../../../hooks/use_stored_state', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../../onboarding_context');
|
||||
jest.mock('../../../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../../../common/lib/kibana');
|
||||
return {
|
||||
...original,
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
services: { notifications: { toasts: { addError: jest.fn() } } },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const cardComplete = {
|
||||
id: 'card-completed' as OnboardingCardId,
|
||||
|
@ -62,6 +73,13 @@ const cardMetadata = {
|
|||
.fn()
|
||||
.mockResolvedValue({ isComplete: true, metadata: { custom: 'metadata' } }),
|
||||
};
|
||||
const mockAddError = jest.fn();
|
||||
const mockError = new Error('Failed to check complete');
|
||||
const cardCheckCompleteFailed = {
|
||||
id: 'card-failed' as OnboardingCardId,
|
||||
title: 'card failed',
|
||||
checkComplete: jest.fn().mockRejectedValue(mockError),
|
||||
};
|
||||
|
||||
const mockCardsGroupConfig = [
|
||||
{
|
||||
|
@ -74,11 +92,65 @@ const mockCardsGroupConfig = [
|
|||
},
|
||||
] as unknown as OnboardingGroupConfig[];
|
||||
|
||||
const mockFailureCardsGroupConfig = [
|
||||
{
|
||||
title: 'Group 1',
|
||||
cards: [cardCheckCompleteFailed],
|
||||
},
|
||||
] as unknown as OnboardingGroupConfig[];
|
||||
|
||||
describe('useCompletedCards Hook', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when checkComplete functions are rejected', () => {
|
||||
let renderResult: RenderHookResult<
|
||||
OnboardingGroupConfig[],
|
||||
ReturnType<typeof useCompletedCards>
|
||||
>;
|
||||
beforeEach(async () => {
|
||||
mockUseKibana.mockReturnValue({
|
||||
services: { notifications: { toasts: { addError: mockAddError } } },
|
||||
});
|
||||
renderResult = renderHook(useCompletedCards, { initialProps: mockFailureCardsGroupConfig });
|
||||
await act(async () => {
|
||||
await waitFor(() => {
|
||||
expect(mockSetStoredCompletedCardIds).toHaveBeenCalledTimes(0); // number of completed cards
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a the auto check is called', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
await act(async () => {
|
||||
renderResult.result.current.checkCardComplete(cardCheckCompleteFailed.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set the completed card ids', async () => {
|
||||
expect(mockSetStoredCompletedCardIds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return the correct completed state', () => {
|
||||
expect(renderResult.result.current.isCardComplete(cardCheckCompleteFailed.id)).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should show an error toast', () => {
|
||||
expect(mockAddError).toHaveBeenCalledWith(mockError, {
|
||||
title: cardCheckCompleteFailed.title,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not report the completed card', async () => {
|
||||
expect(mockReportCardComplete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when checkComplete functions are resolved', () => {
|
||||
let renderResult: RenderHookResult<
|
||||
OnboardingGroupConfig[],
|
||||
|
|
|
@ -114,9 +114,17 @@ export const useCompletedCards = (cardsGroupConfig: OnboardingGroupConfig[]) =>
|
|||
const cardConfig = cardsWithAutoCheck.find(({ id }) => id === cardId);
|
||||
|
||||
if (cardConfig) {
|
||||
cardConfig.checkComplete?.(services).then((checkCompleteResult) => {
|
||||
processCardCheckCompleteResult(cardId, checkCompleteResult);
|
||||
});
|
||||
cardConfig
|
||||
.checkComplete?.(services)
|
||||
.catch((err: Error) => {
|
||||
services.notifications.toasts.addError(err, { title: cardConfig.title });
|
||||
return {
|
||||
isComplete: false,
|
||||
};
|
||||
})
|
||||
.then((checkCompleteResult) => {
|
||||
processCardCheckCompleteResult(cardId, checkCompleteResult);
|
||||
});
|
||||
}
|
||||
},
|
||||
[cardsWithAutoCheck, processCardCheckCompleteResult, services]
|
||||
|
@ -129,9 +137,17 @@ export const useCompletedCards = (cardsGroupConfig: OnboardingGroupConfig[]) =>
|
|||
}
|
||||
autoCheckCompletedRef.current = true;
|
||||
cardsWithAutoCheck.map((card) =>
|
||||
card.checkComplete?.(services).then((checkCompleteResult) => {
|
||||
processCardCheckCompleteResult(card.id, checkCompleteResult);
|
||||
})
|
||||
card
|
||||
.checkComplete?.(services)
|
||||
.catch((err: Error) => {
|
||||
services.notifications.toasts.addError(err, { title: card.title });
|
||||
return {
|
||||
isComplete: false,
|
||||
};
|
||||
})
|
||||
.then((checkCompleteResult) => {
|
||||
processCardCheckCompleteResult(card.id, checkCompleteResult);
|
||||
})
|
||||
);
|
||||
}, [cardsWithAutoCheck, processCardCheckCompleteResult, services]);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue