-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[AI4DSOC] Add configurations integrations page #217905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
6491a60
base setup for configurations integrations browse and installed pages
kgeller b2a4963
updating note to myself
kgeller 063a924
sorting and fixing installed only show allowed
kgeller aabac67
refactor to only have one page that we just send props in to
kgeller fe11da0
sorting by id
kgeller cd355dc
splunk too
kgeller 9aa976c
Merge branch 'master' into add-integrations-pages
kgeller 7973fa0
added relevant tests and jest config for configurations
kgeller 3ff7756
updating loading skeleton to match figma, cleaning up code
kgeller 687c94c
cleanup
kgeller b47a404
fixing link order to match tabs
kgeller 54ebfb4
using lodash noop
kgeller 5c5d205
move allowlist to common/constants
kgeller bede677
Merge branch 'master' into add-integrations-pages
kgeller 02d2100
cleanup
kgeller 483708c
fixing tests
kgeller c38700f
moving from configurations subplugin to common/lib/search_ai_lake
kgeller b27efa3
updating alerts to use searchailake constants allowlist
kgeller d6a19c4
rename
kgeller 0343130
Merge branch 'main' into add-integrations-pages
kgeller 4c2ea84
Merge branch 'main' into add-integrations-pages
kgeller 97adffb
moving non-shared pieces back to configurations subplugin; keeping sh…
kgeller 49dc58a
moving hook to shared
kgeller fcf8abd
Merge branch 'main' into add-integrations-pages
kgeller File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
...utions/security/plugins/security_solution/public/common/lib/search_ai_lake/hooks/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| /* | ||
| * 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 { useEnhancedIntegrationCards } from './integrations/use_enhanced_integration_cards'; |
9 changes: 9 additions & 0 deletions
9
...lugins/security_solution/public/common/lib/search_ai_lake/hooks/integrations/constants.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /* | ||
| * 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 RETURN_APP_ID = 'returnAppId'; | ||
| export const RETURN_PATH = 'returnPath'; |
169 changes: 169 additions & 0 deletions
169
...blic/common/lib/search_ai_lake/hooks/integrations/use_enhanced_integration_cards.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| /* | ||
| * 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 { | ||
| applyCategoryBadgeAndStyling, | ||
| useEnhancedIntegrationCards, | ||
| getCategoryBadgeIfAny, | ||
| } from './use_enhanced_integration_cards'; | ||
| import { IntegrationsFacets } from '../../../../../configurations/constants'; | ||
| import type { IntegrationCardItem } from '@kbn/fleet-plugin/public'; | ||
| import { installationStatuses } from '@kbn/fleet-plugin/public'; | ||
| import { renderHook } from '@testing-library/react'; | ||
|
|
||
| const mockCard = (name: string, categories?: string[]) => | ||
| ({ | ||
| id: `epr:${name}`, | ||
| description: 'description', | ||
| icons: [], | ||
| title: name, | ||
| url: `/app/integrations/detail/${name}-1.0.0/overview`, | ||
| integration: '', | ||
| name, | ||
| version: '1.0.0', | ||
| release: 'ga', | ||
| categories: categories ?? [], | ||
| isUnverified: false, | ||
| } as IntegrationCardItem); | ||
|
|
||
| describe('applyCategoryBadgeAndStyling', () => { | ||
| const mockInt = mockCard('crowdstrike', ['edr_xdr']); | ||
|
|
||
| it('should add the correct return path to the URL', () => { | ||
| const callerView = IntegrationsFacets.available; | ||
| const result = applyCategoryBadgeAndStyling(mockInt, callerView); | ||
|
|
||
| const urlParams = new URLSearchParams(result.url.split('?')[1]); | ||
| expect(urlParams.get('returnPath')).toBe(`/configurations/integrations/${callerView}`); | ||
| }); | ||
|
|
||
| it('should add the EDR/XDR badge if the category includes edr_xdr', () => { | ||
| const cardWithEdrXdr = { ...mockInt, categories: ['edr_xdr'] }; | ||
| const result = applyCategoryBadgeAndStyling(cardWithEdrXdr, IntegrationsFacets.available); | ||
|
|
||
| expect(result.extraLabelsBadges).toHaveLength(1); | ||
| }); | ||
|
|
||
| it('should add the SIEM badge if the category includes siem', () => { | ||
| const cardWithSiem = { ...mockInt, categories: ['siem'] }; | ||
| const result = applyCategoryBadgeAndStyling(cardWithSiem, IntegrationsFacets.available); | ||
|
|
||
| expect(result.extraLabelsBadges).toHaveLength(1); | ||
| }); | ||
|
|
||
| it('should not add any badge if the category does not include edr_xdr or siem', () => { | ||
| const cardWithOtherCategory = { ...mockInt, categories: ['other'] }; | ||
| const result = applyCategoryBadgeAndStyling( | ||
| cardWithOtherCategory, | ||
| IntegrationsFacets.available | ||
| ); | ||
|
|
||
| expect(result.extraLabelsBadges).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('should set showDescription and showReleaseBadge to false', () => { | ||
| const result = applyCategoryBadgeAndStyling(mockInt, IntegrationsFacets.available); | ||
|
|
||
| expect(result.showDescription).toBe(false); | ||
| expect(result.showReleaseBadge).toBe(false); | ||
| }); | ||
|
|
||
| it('should set maxCardHeight to 88', () => { | ||
| const result = applyCategoryBadgeAndStyling(mockInt, IntegrationsFacets.available); | ||
|
|
||
| expect(result.maxCardHeight).toBe(88); | ||
| }); | ||
| }); | ||
|
|
||
| describe('useEnhancedIntegrationCards', () => { | ||
| const intA = mockCard('crowdstrike', ['edr_xdr']); | ||
| const intB = mockCard('google_secops', ['siem']); | ||
| const intC = mockCard('microsoft_sentinel', ['siem']); | ||
| const intD = mockCard('sentinel_one', ['edr_xdr']); | ||
|
|
||
| it('should return sorted available integrations with badges applied', () => { | ||
| const mockIntegrationsList = [intA, intB, intC, intD]; | ||
| const { result } = renderHook(() => useEnhancedIntegrationCards(mockIntegrationsList)); | ||
|
|
||
| expect(result.current.available).toHaveLength(4); | ||
| expect(result.current.available[0].id).toBe('epr:google_secops'); | ||
| expect(result.current.available[1].id).toBe('epr:microsoft_sentinel'); | ||
| expect(result.current.available[0].extraLabelsBadges).toHaveLength(1); | ||
| }); | ||
|
|
||
| it('should return sorted installed integrations with badges applied', () => { | ||
| const mockIntegrationsList = [ | ||
| intA, | ||
| intB, | ||
| { ...intC, installStatus: installationStatuses.Installed }, | ||
| intD, | ||
| ]; | ||
| const { result } = renderHook(() => useEnhancedIntegrationCards(mockIntegrationsList)); | ||
|
|
||
| expect(result.current.installed).toHaveLength(1); | ||
| expect(result.current.installed[0].id).toBe('epr:microsoft_sentinel'); | ||
| expect(result.current.installed[0].extraLabelsBadges).toHaveLength(1); | ||
| }); | ||
|
|
||
| it('should handle an empty integrations list', () => { | ||
| const { result } = renderHook(() => useEnhancedIntegrationCards([])); | ||
|
|
||
| expect(result.current.available).toHaveLength(0); | ||
| expect(result.current.installed).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('should correctly apply custom display order', () => { | ||
| const mockIntegrationsList = [intA, intB, intC, intD]; | ||
|
|
||
| const shuffledList = [ | ||
| mockIntegrationsList[3], | ||
| mockIntegrationsList[1], | ||
| mockIntegrationsList[0], | ||
| mockIntegrationsList[2], | ||
| ]; | ||
|
|
||
| const { result } = renderHook(() => useEnhancedIntegrationCards(shuffledList)); | ||
|
|
||
| expect(result.current.available[0].id).toBe('epr:google_secops'); | ||
| expect(result.current.available[1].id).toBe('epr:microsoft_sentinel'); | ||
| expect(result.current.available[2].id).toBe('epr:sentinel_one'); | ||
| expect(result.current.available[3].id).toBe('epr:crowdstrike'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getCategoryBadgeIfAny', () => { | ||
| it('should return "EDR/XDR" when the categories include "edr_xdr"', () => { | ||
| const categories = ['edr_xdr', 'other_category']; | ||
| const result = getCategoryBadgeIfAny(categories); | ||
| expect(result).toBe('EDR/XDR'); | ||
| }); | ||
|
|
||
| it('should return "SIEM" when the categories include "siem"', () => { | ||
| const categories = ['siem', 'other_category']; | ||
| const result = getCategoryBadgeIfAny(categories); | ||
| expect(result).toBe('SIEM'); | ||
| }); | ||
|
|
||
| it('should return "EDR/XDR" when both "edr_xdr" and "siem" are present', () => { | ||
| const categories = ['edr_xdr', 'siem']; | ||
| const result = getCategoryBadgeIfAny(categories); | ||
| // "edr_xdr" takes precedence, but we don't realistically expect both to be present | ||
| expect(result).toBe('EDR/XDR'); | ||
| }); | ||
|
|
||
| it('should return null when neither "edr_xdr" nor "siem" are present', () => { | ||
| const categories = ['other_category']; | ||
| const result = getCategoryBadgeIfAny(categories); | ||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('should return null when the categories array is empty', () => { | ||
| const categories: string[] = []; | ||
| const result = getCategoryBadgeIfAny(categories); | ||
| expect(result).toBeNull(); | ||
| }); | ||
| }); |
95 changes: 95 additions & 0 deletions
95
...on/public/common/lib/search_ai_lake/hooks/integrations/use_enhanced_integration_cards.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* | ||
| * 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, { useMemo } from 'react'; | ||
| import { EuiFlexItem, EuiSpacer, EuiBadge } from '@elastic/eui'; | ||
| import { installationStatuses, type IntegrationCardItem } from '@kbn/fleet-plugin/public'; | ||
| import { SECURITY_UI_APP_ID } from '@kbn/security-solution-navigation'; | ||
| import { CONFIGURATIONS_PATH } from '../../../../../../common/constants'; | ||
| import { IntegrationsFacets } from '../../../../../configurations/constants'; | ||
| import { RETURN_APP_ID, RETURN_PATH } from './constants'; | ||
|
|
||
| const FEATURED_INTEGRATION_SORT_ORDER = [ | ||
| 'epr:splunk', | ||
| 'epr:google_secops', | ||
| 'epr:microsoft_sentinel', | ||
| 'epr:sentinel_one', | ||
| 'epr:crowdstrike', | ||
| ]; | ||
| const INTEGRATION_CARD_MAX_HEIGHT_PX = 88; | ||
|
|
||
| const addPathParamToUrl = (url: string, path: string) => { | ||
| const encodedPath = encodeURIComponent(path); | ||
| const paramsString = `${RETURN_APP_ID}=${SECURITY_UI_APP_ID}&${RETURN_PATH}=${encodedPath}`; | ||
|
|
||
| if (url.indexOf('?') >= 0) { | ||
| return `${url}&${paramsString}`; | ||
| } | ||
| return `${url}?${paramsString}`; | ||
| }; | ||
|
|
||
| export const getCategoryBadgeIfAny = (categories: string[]): string | null => { | ||
| return categories.includes('edr_xdr') ? 'EDR/XDR' : categories.includes('siem') ? 'SIEM' : null; | ||
| }; | ||
|
|
||
| export const applyCategoryBadgeAndStyling = ( | ||
| card: IntegrationCardItem, | ||
| callerView: IntegrationsFacets | ||
| ): IntegrationCardItem => { | ||
| const returnPath = `${CONFIGURATIONS_PATH}/integrations/${callerView}`; | ||
| const url = addPathParamToUrl(card.url, returnPath); | ||
| const categoryBadge = getCategoryBadgeIfAny(card.categories); | ||
| return { | ||
| ...card, | ||
| url, | ||
| showDescription: false, | ||
| showReleaseBadge: false, | ||
| extraLabelsBadges: categoryBadge | ||
| ? ([ | ||
| <EuiFlexItem grow={false}> | ||
| <EuiSpacer size="xs" /> | ||
| <span> | ||
| <EuiBadge color="hollow">{categoryBadge}</EuiBadge> | ||
| </span> | ||
| </EuiFlexItem>, | ||
| ] as React.ReactNode[]) | ||
| : [], | ||
| maxCardHeight: INTEGRATION_CARD_MAX_HEIGHT_PX, | ||
| }; | ||
| }; | ||
|
|
||
| const applyCustomDisplayOrder = (integrationsList: IntegrationCardItem[]) => { | ||
| return integrationsList.sort( | ||
| (a, b) => | ||
| FEATURED_INTEGRATION_SORT_ORDER.indexOf(a.id) - FEATURED_INTEGRATION_SORT_ORDER.indexOf(b.id) | ||
| ); | ||
| }; | ||
|
|
||
| export const useEnhancedIntegrationCards = ( | ||
| integrationsList: IntegrationCardItem[] | ||
| ): { available: IntegrationCardItem[]; installed: IntegrationCardItem[] } => { | ||
| const sorted = applyCustomDisplayOrder(integrationsList); | ||
|
|
||
| const available = useMemo( | ||
| () => sorted.map((card) => applyCategoryBadgeAndStyling(card, IntegrationsFacets.available)), | ||
| [sorted] | ||
| ); | ||
|
|
||
| const installed = useMemo( | ||
| () => | ||
| sorted | ||
| .map((card) => applyCategoryBadgeAndStyling(card, IntegrationsFacets.installed)) | ||
| .filter( | ||
| (card) => | ||
| card.installStatus === installationStatuses.Installed || | ||
| card.installStatus === installationStatuses.InstallFailed | ||
| ), | ||
| [sorted] | ||
| ); | ||
|
|
||
| return { available, installed }; | ||
| }; | ||
15 changes: 15 additions & 0 deletions
15
...tions/security/plugins/security_solution/public/common/lib/search_ai_lake/integrations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| /** Allow list of integrations to be available in the AI4DSOC integrations page */ | ||
| export const SEARCH_AI_LAKE_ALLOWED_INTEGRATIONS: string[] = [ | ||
| 'crowdstrike', | ||
| 'google_secops', | ||
| 'microsoft_sentinel', | ||
| 'sentinel_one', | ||
| 'splunk', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
x-pack/solutions/security/plugins/security_solution/public/configurations/jest.config.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| module.exports = { | ||
| preset: '@kbn/test', | ||
| rootDir: '../../../../../../..', | ||
| roots: ['<rootDir>/x-pack/solutions/security/plugins/security_solution/public/configurations'], | ||
| coverageDirectory: | ||
| '<rootDir>/target/kibana-coverage/jest/x-pack/solutions/security/plugins/security_solution/public/configurations', | ||
| coverageReporters: ['text', 'html'], | ||
| collectCoverageFrom: [ | ||
| '<rootDir>/x-pack/solutions/security/plugins/security_solution/public/configurations/**/*.{ts,tsx}', | ||
| ], | ||
| moduleNameMapper: require('../../server/__mocks__/module_name_map'), | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.