Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
tiansivive committed Oct 11, 2024
1 parent 405803c commit a0a0051
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,36 @@
* 2.0.
*/
import { useMemo } from 'react';
import { API_VERSIONS } from '../../../common/entity_analytics/constants';
import type {
EngineDescriptor,
EntityType,
} from '../../../common/api/entity_analytics/entity_store/common.gen';
GetEntityEngineResponse,
InitEntityEngineResponse,
ListEntityEnginesResponse,
} from '../../../common/api/entity_analytics';
import { API_VERSIONS } from '../../../common/entity_analytics/constants';
import type { EntityType } from '../../../common/api/entity_analytics/entity_store/common.gen';
import { useKibana } from '../../common/lib/kibana/kibana_react';

export const useEntityStoreRoutes = () => {
const http = useKibana().services.http;

return useMemo(() => {
const initEntityStore = async (entityType: EntityType) => {
return http.fetch<EngineDescriptor>(`/api/entity_store/engines/${entityType}/init`, {
return http.fetch<InitEntityEngineResponse>(`/api/entity_store/engines/${entityType}/init`, {
method: 'POST',
version: API_VERSIONS.public.v1,
body: JSON.stringify({}),
});
};

const getEntityEngine = async (entityType: EntityType) => {
return http.fetch<EngineDescriptor>(`/api/entity_store/engines/${entityType}`, {
return http.fetch<GetEntityEngineResponse>(`/api/entity_store/engines/${entityType}`, {
method: 'GET',
version: API_VERSIONS.public.v1,
});
};

const listEntityEngines = async () => {
return http.fetch<EngineDescriptor[]>(`/api/entity_store/engines`, {
return http.fetch<ListEntityEnginesResponse>(`/api/entity_store/engines`, {
method: 'GET',
version: API_VERSIONS.public.v1,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,82 @@ import {
EuiLoadingSpinner,
EuiFlexItem,
EuiFlexGroup,
EuiLoadingLogo,
EuiPanel,
} from '@elastic/eui';

import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics';
import { RiskScoreEntity } from '../../../../../common/search_strategy';
import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries';

import { Panel } from '../../../../common/components/panel';
import { HeaderSection } from '../../../../common/components/header_section';
import { EntityAnalyticsLearnMoreLink } from '../../risk_score_onboarding/entity_analytics_doc_link';
import { EntitiesList } from '../entities_list';

import { useEntityStore } from '../hooks/use_entity_store';
import { useEntityStoreEnablement } from '../hooks/use_entity_store';
import { EntityStoreEnablementModal, type Enablements } from './enablement_modal';
import { EnableRiskScore } from '../../enable_risk_score';
import { useGlobalTime } from '../../../../common/containers/use_global_time';

import { EntityAnalyticsRiskScores } from '../../entity_analytics_risk_score';
import { useInitRiskEngineMutation } from '../../../api/hooks/use_init_risk_engine_mutation';
import { useEntityEngineStatus } from '../hooks/use_entity_engine_status';

const EntityStoreDashboardPanelsComponent = () => {
const [modal, setModalState] = useState({ visible: false, enablements: {} });

const entityStore = useEntityStore();
const [modal, setModalState] = useState({ visible: false });
const [riskEngineInitializing, setRiskEngineInitializing] = useState(false);

// NOTE: Props needed only for current implementation of the EnableRiskScore component
const refreshPage = useRefetchQueries();
const { deleteQuery, setQuery, from, to } = useGlobalTime();
const entityStore = useEntityEngineStatus();
const { enable: enableStore } = useEntityStoreEnablement();
const { mutate: initRiskEngine } = useInitRiskEngineMutation();

const enableEntityStore = (enable: Enablements) => () => {
setModalState({ visible: false, enablements: enable });
setModalState({ visible: false });
if (enable.riskScore) {
// return enableRiskScore().then(() => {
// if (enablements.entityStore) {
// entityStore.enablement.enableEntityStore();
// }
// });
const options = {
onSuccess: () => {
setRiskEngineInitializing(false);
if (enable.entityStore) {
enableStore();
}
},
};
setRiskEngineInitializing(true);
initRiskEngine(undefined, options);
}

if (enable.entityStore) {
entityStore.enablement.enableEntityStore();
enableStore();
}
};

if (entityStore.enablement.loading) {
return <EuiLoadingSpinner size="xl" />;
if (entityStore.status === 'loading') {
return (
<EuiPanel hasBorder>
<EuiEmptyPrompt
icon={<EuiLoadingSpinner size="xl" />}
title={<h2>{'Initializing store'}</h2>}
/>
</EuiPanel>
);
}

const isRiskScoreEnabled =
entityStore.status.newRiskScore.installed ||
entityStore.status.legacyHostRiskScore.isEnabled ||
entityStore.status.legacyUserRiskScore.isEnabled;
if (entityStore.status === 'installing') {
return (
<EuiPanel hasBorder>
<EuiEmptyPrompt
icon={<EuiLoadingLogo logo="logoElastic" size="xl" />}
title={<h2>{'Initializing store'}</h2>}
/>
</EuiPanel>
);
}

const isRiskScoreAvailable =
entityStore.riskEngineStatus.data &&
entityStore.riskEngineStatus.data.risk_engine_status !== RiskEngineStatusEnum.NOT_INSTALLED;

return (
<EuiFlexGroup direction="column" data-test-subj="entityStorePanelsGroup">
{entityStore.status.entityStore.status === 'enabled' && isRiskScoreEnabled && (
{entityStore.status === 'enabled' && isRiskScoreAvailable && (
<>
<EuiFlexItem>
<EntitiesList />
Expand All @@ -77,50 +101,33 @@ const EntityStoreDashboardPanelsComponent = () => {
</EuiFlexItem>
</>
)}
{entityStore.status.entityStore.status === 'enabled' && !isRiskScoreEnabled && (
{entityStore.status === 'enabled' && !isRiskScoreAvailable && (
<>
{/* QUESTION: Should we have a separate component for enabling risk score? Current one seems too overloaded */}
<EuiFlexItem>
<EnableRiskScore
entityType={RiskScoreEntity.user}
timerange={{ from, to }}
refetch={refreshPage}
isDisabled={false}
isDeprecated={entityStore.status.legacyUserRiskScore.isDeprecated}
/>
</EuiFlexItem>
<EuiFlexItem>
<EnableRiskScore
entityType={RiskScoreEntity.host}
timerange={{ from, to }}
refetch={refreshPage}
isDisabled={false}
isDeprecated={entityStore.status.legacyHostRiskScore.isDeprecated}
onEnable={() => setModalState({ visible: true })}
loading={riskEngineInitializing}
/>
</EuiFlexItem>

<EuiFlexItem>
<EntitiesList />
</EuiFlexItem>
</>
)}

{entityStore.status.entityStore.status === 'not_installed' && !isRiskScoreEnabled && (
{entityStore.status === 'not_installed' && !isRiskScoreAvailable && (
// TODO: Move modal inside EnableEntityStore component, eliminating the onEnable prop in favour of forwarding the riskScoreEnabled status
<EnableEntityStore
onEnable={() =>
setModalState({ visible: true, enablements: { riskScore: true, entityStore: true } })
}
/>
<EnableEntityStore onEnable={() => setModalState({ visible: true })} />
)}

{entityStore.status.entityStore.status === 'not_installed' && isRiskScoreEnabled && (
{entityStore.status === 'not_installed' && isRiskScoreAvailable && (
<>
<EuiFlexItem>
<EnableEntityStore
onEnable={() =>
setModalState({
visible: true,
enablements: { riskScore: false, entityStore: true },
})
}
/>
Expand All @@ -136,9 +143,13 @@ const EntityStoreDashboardPanelsComponent = () => {

<EntityStoreEnablementModal
visible={modal.visible}
toggle={(visible) => setModalState((prev) => ({ ...prev, visible }))}
toggle={(visible) => setModalState({ visible })}
enableStore={enableEntityStore}
riskScore={modal.enablements}
riskScore={{ disabled: isRiskScoreAvailable, checked: !isRiskScoreAvailable }}
entityStore={{
disabled: entityStore.status === 'enabled',
checked: entityStore.status !== 'enabled',
}}
/>
</EuiFlexGroup>
);
Expand Down Expand Up @@ -177,5 +188,49 @@ export const EnableEntityStore: React.FC<EnableEntityStoreProps> = ({ onEnable }
);
};

interface EnableRiskEngineProps {
onEnable: () => void;
loading: boolean;
}

export const EnableRiskScore: React.FC<EnableRiskEngineProps> = ({ onEnable, loading }) => {
if (loading) {
return (
<EuiPanel hasBorder>
<EuiEmptyPrompt
icon={<EuiLoadingLogo logo="logoElastic" size="xl" />}
title={<h2>{'Initializing risk engine'}</h2>}
/>
</EuiPanel>
);
}
return (
<Panel hasBorder data-test-subj={`entity_analytics_enable_risk_score`}>
<HeaderSection title={'Risk Store'} titleSize="s" />
<EuiEmptyPrompt
title={<h2>{'Placeholder title'}</h2>}
body={
<>
{'Placeholder text'}
<EntityAnalyticsLearnMoreLink />
</>
}
actions={
<EuiToolTip content={'Enable Risk Score'}>
<EuiButton
color="primary"
fill
onClick={onEnable}
data-test-subj={`enable_risk_score_btn`}
>
{'Enable'}
</EuiButton>
</EuiToolTip>
}
/>
</Panel>
);
};

export const EntityStoreDashboardPanels = React.memo(EntityStoreDashboardPanelsComponent);
EntityStoreDashboardPanels.displayName = 'EntityStoreDashboardPanels';
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,22 @@ interface EntityStoreEnablementModalProps {
disabled?: boolean;
checked?: boolean;
};
entityStore: {
disabled?: boolean;
checked?: boolean;
};
}

export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProps> = ({
visible,
toggle,
enableStore,
riskScore,
entityStore,
}) => {
const [enablements, setEnablements] = useState({
riskScore: !!riskScore.checked,
entityStore: true,
entityStore: !!entityStore.checked,
});

if (!visible) {
Expand All @@ -58,15 +63,16 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
<EuiFlexItem>
<EuiSwitch
label="Risk Score"
checked={!!riskScore.checked}
checked={enablements.riskScore}
disabled={riskScore.disabled || false}
onChange={() => setEnablements((prev) => ({ ...prev, riskScore: !prev.riskScore }))}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiSwitch
label="Entity Store"
checked
checked={enablements.entityStore}
disabled={entityStore.disabled || false}
onChange={() =>
setEnablements((prev) => ({ ...prev, entityStore: !prev.entityStore }))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,57 @@
* 2.0.
*/

import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import type { EngineDescriptor } from '../../../../../common/api/entity_analytics';
import type { ListEntityEnginesResponse } from '../../../../../common/api/entity_analytics';
import { useEntityStoreRoutes } from '../../../api/entity_store';
import { useRiskEngineStatus } from '../../../api/hooks/use_risk_engine_status';

const ENTITY_STORE_ENGINE_STATUS = 'ENTITY_STORE_ENGINE_STATUS';

export const useEntityEngineStatus = () => {
interface Options {
disabled?: boolean;
polling?: UseQueryOptions<ListEntityEnginesResponse>['refetchInterval'];
}

export const useEntityEngineStatus = (opts: Options = {}) => {
const riskEngineStatus = useRiskEngineStatus();
// QUESTION: Maybe we should have an `EnablementStatus` API route for this?
const { listEntityEngines } = useEntityStoreRoutes();

const { isLoading, data } = useQuery<EngineDescriptor[]>({
const { isLoading, data } = useQuery<ListEntityEnginesResponse>({
queryKey: [ENTITY_STORE_ENGINE_STATUS],
queryFn: listEntityEngines,
queryFn: () => listEntityEngines(),
refetchInterval: opts.polling,
enabled: !opts.disabled,
});

if (isLoading) {
return { status: 'loading' };
}
const status = (() => {
if (data?.count === 0) {
return 'not_installed';
}

if (data?.engines?.every((engine) => engine.status === 'stopped')) {
return 'stopped';
}

if (!data) {
return { status: 'error' };
}
if (data?.engines?.some((engine) => engine.status === 'installing')) {
return 'installing';
}

if (data.length === 0) {
return { status: 'not_installed' };
}
if (isLoading) {
return 'loading';
}

if (data.every((engine) => engine.status === 'stopped')) {
return { status: 'stopped' };
}
if (!data) {
return 'error';
}

if (data.some((engine) => engine.status === 'installing')) {
return { status: 'installing' };
}
return 'enabled';
})();

return { status: 'enabled' };
return {
status,
riskEngineStatus,
};
};
Loading

0 comments on commit a0a0051

Please sign in to comment.