forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Entity Analytics][Entity Store] Refactor enablement UI (elastic#199762)
## Summary This PR reworks the client side enablement flow for the Entity Store. It's the final piece for the work tracked in elastic/security-team#10846, elastic/security-team#10847 and elastic/security-team#10947 https://github.com/user-attachments/assets/bb919c3c-b8dc-4e6b-a14b-4d413f8da13f ## How to test Optionally On a fresh kibana and es cluster instance: 1. Load up some entity analytics data via the https://github.com/elastic/security-documents-generator * Running `yarn start entity-resolution-demo --mini` is enough 1. Navigate to `Security > Dashboards > Entity Analytics` 3. Click the `Enable` entity store button 4. Both Risk Score and Entity Store toggles should be checked. * This state represents the engines we will install, _NOT_ the current state 5. Click `Enable` 6. The modal should close and Risk Scoring should initialize first 7. As soon as risk score initialization finished, the entity store initialization should start * Risk score related panels should show up while the store is initializing 8. Finally, the Entities List panel should appear ## - [x] added cypress tests to verify the correct enablement flow
- Loading branch information
1 parent
c2c6f56
commit 722900e
Showing
14 changed files
with
703 additions
and
681 deletions.
There are no files selected for viewing
This file contains 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 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
198 changes: 198 additions & 0 deletions
198
...public/entity_analytics/components/entity_store/components/dashboard_enablement_panel.tsx
This file contains 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,198 @@ | ||
/* | ||
* 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, { useCallback, useState } from 'react'; | ||
import { | ||
EuiCallOut, | ||
EuiPanel, | ||
EuiEmptyPrompt, | ||
EuiLoadingLogo, | ||
EuiToolTip, | ||
EuiButton, | ||
EuiImage, | ||
} from '@elastic/eui'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import type { UseQueryResult } from '@tanstack/react-query'; | ||
import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; | ||
import type { StoreStatus } from '../../../../../common/api/entity_analytics'; | ||
import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics'; | ||
import { useInitRiskEngineMutation } from '../../../api/hooks/use_init_risk_engine_mutation'; | ||
import { useEnableEntityStoreMutation } from '../hooks/use_entity_store'; | ||
import { | ||
ENABLEMENT_INITIALIZING_RISK_ENGINE, | ||
ENABLEMENT_INITIALIZING_ENTITY_STORE, | ||
ENABLE_ALL_TITLE, | ||
ENABLEMENT_DESCRIPTION_BOTH, | ||
ENABLE_RISK_SCORE_TITLE, | ||
ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY, | ||
ENABLE_ENTITY_STORE_TITLE, | ||
ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY, | ||
} from '../translations'; | ||
import type { Enablements } from './enablement_modal'; | ||
import { EntityStoreEnablementModal } from './enablement_modal'; | ||
import dashboardEnableImg from '../../../images/entity_store_dashboard.png'; | ||
import type { RiskEngineStatus } from '../../../api/hooks/use_risk_engine_status'; | ||
|
||
interface EnableEntityStorePanelProps { | ||
state: { | ||
riskEngine: UseQueryResult<RiskEngineStatus>; | ||
entityStore: UseQueryResult<GetEntityStoreStatusResponse>; | ||
}; | ||
} | ||
|
||
export const EnablementPanel: React.FC<EnableEntityStorePanelProps> = ({ state }) => { | ||
const riskEngineStatus = state.riskEngine.data?.risk_engine_status; | ||
const entityStoreStatus = state.entityStore.data?.status; | ||
|
||
const [modal, setModalState] = useState({ visible: false }); | ||
const [riskEngineInitializing, setRiskEngineInitializing] = useState(false); | ||
|
||
const initRiskEngine = useInitRiskEngineMutation(); | ||
const storeEnablement = useEnableEntityStoreMutation(); | ||
|
||
const enableEntityStore = useCallback( | ||
(enable: Enablements) => () => { | ||
if (enable.riskScore) { | ||
const options = { | ||
onSuccess: () => { | ||
setRiskEngineInitializing(false); | ||
if (enable.entityStore) { | ||
storeEnablement.mutate(); | ||
} | ||
}, | ||
}; | ||
setRiskEngineInitializing(true); | ||
initRiskEngine.mutate(undefined, options); | ||
setModalState({ visible: false }); | ||
return; | ||
} | ||
|
||
if (enable.entityStore) { | ||
storeEnablement.mutate(); | ||
setModalState({ visible: false }); | ||
} | ||
}, | ||
[storeEnablement, initRiskEngine] | ||
); | ||
|
||
if (storeEnablement.error) { | ||
return ( | ||
<> | ||
<EuiCallOut | ||
title={ | ||
<FormattedMessage | ||
id="xpack.securitySolution.entityAnalytics.entityStore.enablement.mutation.errorTitle" | ||
defaultMessage={'There was a problem initializing the entity store'} | ||
/> | ||
} | ||
color="danger" | ||
iconType="error" | ||
> | ||
<p>{storeEnablement.error.body.message}</p> | ||
</EuiCallOut> | ||
</> | ||
); | ||
} | ||
|
||
if (riskEngineInitializing) { | ||
return ( | ||
<EuiPanel hasBorder data-test-subj="riskEngineInitializingPanel"> | ||
<EuiEmptyPrompt | ||
icon={<EuiLoadingLogo logo="logoElastic" size="xl" />} | ||
title={<h2>{ENABLEMENT_INITIALIZING_RISK_ENGINE}</h2>} | ||
/> | ||
</EuiPanel> | ||
); | ||
} | ||
|
||
if (entityStoreStatus === 'installing' || storeEnablement.isLoading) { | ||
return ( | ||
<EuiPanel hasBorder data-test-subj="entityStoreInitializingPanel"> | ||
<EuiEmptyPrompt | ||
icon={<EuiLoadingLogo logo="logoElastic" size="xl" />} | ||
title={<h2>{ENABLEMENT_INITIALIZING_ENTITY_STORE}</h2>} | ||
body={ | ||
<p> | ||
<FormattedMessage | ||
id="xpack.securitySolution.entityAnalytics.entityStore.enablement.initializing.description" | ||
defaultMessage="This can take up to 5 minutes." | ||
/> | ||
</p> | ||
} | ||
/> | ||
</EuiPanel> | ||
); | ||
} | ||
|
||
if ( | ||
riskEngineStatus !== RiskEngineStatusEnum.NOT_INSTALLED && | ||
(entityStoreStatus === 'running' || entityStoreStatus === 'stopped') | ||
) { | ||
return null; | ||
} | ||
|
||
const [title, body] = getEnablementTexts(entityStoreStatus, riskEngineStatus); | ||
return ( | ||
<> | ||
<EuiEmptyPrompt | ||
css={{ minWidth: '100%' }} | ||
hasBorder | ||
layout="horizontal" | ||
title={<h2>{title}</h2>} | ||
body={<p>{body}</p>} | ||
actions={ | ||
<EuiToolTip content={title}> | ||
<EuiButton | ||
color="primary" | ||
fill | ||
onClick={() => setModalState({ visible: true })} | ||
data-test-subj={`entityStoreEnablementButton`} | ||
> | ||
<FormattedMessage | ||
id="xpack.securitySolution.entityAnalytics.entityStore.enablement.enableButton" | ||
defaultMessage="Enable" | ||
/> | ||
</EuiButton> | ||
</EuiToolTip> | ||
} | ||
icon={<EuiImage size="l" hasShadow src={dashboardEnableImg} alt={title} />} | ||
data-test-subj="entityStoreEnablementPanel" | ||
/> | ||
|
||
<EntityStoreEnablementModal | ||
visible={modal.visible} | ||
toggle={(visible) => setModalState({ visible })} | ||
enableStore={enableEntityStore} | ||
riskScore={{ | ||
disabled: riskEngineStatus !== RiskEngineStatusEnum.NOT_INSTALLED, | ||
checked: riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED, | ||
}} | ||
entityStore={{ | ||
disabled: entityStoreStatus === 'running', | ||
checked: entityStoreStatus === 'not_installed', | ||
}} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
const getEnablementTexts = ( | ||
entityStoreStatus?: StoreStatus, | ||
riskEngineStatus?: RiskEngineStatus['risk_engine_status'] | ||
): [string, string] => { | ||
if ( | ||
(entityStoreStatus === 'not_installed' || entityStoreStatus === 'stopped') && | ||
riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED | ||
) { | ||
return [ENABLE_ALL_TITLE, ENABLEMENT_DESCRIPTION_BOTH]; | ||
} | ||
|
||
if (riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED) { | ||
return [ENABLE_RISK_SCORE_TITLE, ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY]; | ||
} | ||
|
||
return [ENABLE_ENTITY_STORE_TITLE, ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY]; | ||
}; |
95 changes: 95 additions & 0 deletions
95
...lic/entity_analytics/components/entity_store/components/dashboard_entity_store_panels.tsx
This file contains 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 from 'react'; | ||
import { | ||
EuiEmptyPrompt, | ||
EuiLoadingSpinner, | ||
EuiFlexItem, | ||
EuiFlexGroup, | ||
EuiPanel, | ||
EuiCallOut, | ||
} from '@elastic/eui'; | ||
|
||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics'; | ||
import { RiskScoreEntity } from '../../../../../common/search_strategy'; | ||
import { EntitiesList } from '../entities_list'; | ||
import { useEntityStoreStatus } from '../hooks/use_entity_store'; | ||
import { EntityAnalyticsRiskScores } from '../../entity_analytics_risk_score'; | ||
import { useRiskEngineStatus } from '../../../api/hooks/use_risk_engine_status'; | ||
|
||
import { EnablementPanel } from './dashboard_enablement_panel'; | ||
|
||
const EntityStoreDashboardPanelsComponent = () => { | ||
const riskEngineStatus = useRiskEngineStatus(); | ||
const storeStatusQuery = useEntityStoreStatus({}); | ||
|
||
const callouts = (storeStatusQuery.data?.engines ?? []) | ||
.filter((engine) => engine.status === 'error') | ||
.map((engine) => { | ||
const err = engine.error as { | ||
message: string; | ||
}; | ||
return ( | ||
<EuiCallOut | ||
title={ | ||
<FormattedMessage | ||
id="xpack.securitySolution.entityAnalytics.entityStore.enablement.errors.title" | ||
defaultMessage={'An error occurred during entity store resource initialization'} | ||
/> | ||
} | ||
color="danger" | ||
iconType="error" | ||
> | ||
<p>{err?.message}</p> | ||
</EuiCallOut> | ||
); | ||
}); | ||
|
||
if (storeStatusQuery.status === 'loading') { | ||
return ( | ||
<EuiPanel hasBorder> | ||
<EuiEmptyPrompt icon={<EuiLoadingSpinner size="xl" />} /> | ||
</EuiPanel> | ||
); | ||
} | ||
|
||
return ( | ||
<EuiFlexGroup direction="column" data-test-subj="entityStorePanelsGroup"> | ||
{storeStatusQuery.status === 'error' ? ( | ||
callouts | ||
) : ( | ||
<EnablementPanel | ||
state={{ | ||
riskEngine: riskEngineStatus, | ||
entityStore: storeStatusQuery, | ||
}} | ||
/> | ||
)} | ||
|
||
{riskEngineStatus.data?.risk_engine_status !== RiskEngineStatusEnum.NOT_INSTALLED && ( | ||
<> | ||
<EuiFlexItem> | ||
<EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.user} /> | ||
</EuiFlexItem> | ||
<EuiFlexItem> | ||
<EntityAnalyticsRiskScores riskEntity={RiskScoreEntity.host} /> | ||
</EuiFlexItem> | ||
</> | ||
)} | ||
{storeStatusQuery.data?.status !== 'not_installed' && | ||
storeStatusQuery.data?.status !== 'installing' && ( | ||
<EuiFlexItem data-test-subj="entitiesListPanel"> | ||
<EntitiesList /> | ||
</EuiFlexItem> | ||
)} | ||
</EuiFlexGroup> | ||
); | ||
}; | ||
|
||
export const EntityStoreDashboardPanels = React.memo(EntityStoreDashboardPanelsComponent); | ||
EntityStoreDashboardPanels.displayName = 'EntityStoreDashboardPanels'; |
Oops, something went wrong.