Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,26 @@
* 2.0.
*/

import React from 'react';
import { EuiFlexItem, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useState, useEffect } from 'react';
import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiText, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { ReactElement } from 'react';
import { createKeyInsightsPanelLensAttributes } from './lens_attributes';
import { VisualizationEmbeddable } from '../../../../../../common/components/visualization_actions/visualization_embeddable';
import { useEsqlGlobalFilterQuery } from '../../../../../../common/hooks/esql/use_esql_global_filter';
import { useGlobalTime } from '../../../../../../common/containers/use_global_time';
import { useSpaceId } from '../../../../../../common/hooks/use_space_id';
import { useVisualizationResponse } from '../../../../../../common/components/visualization_actions/use_visualization_response';

const LENS_VISUALIZATION_HEIGHT = 126;
const LENS_VISUALIZATION_MIN_WIDTH = 160;
const LENS_VISUALIZATION_HEIGHT = 150;
const LENS_VISUALIZATION_MIN_WIDTH = 220;

interface KeyInsightsTileProps {
/** The title of the tile (i18n FormattedMessage element) */
title: ReactElement;
/** The label for the visualization (i18n FormattedMessage element) */
label: ReactElement;
/** Function that returns the ESQL query for the given namespace */
getEsqlQuery: (namespace: string) => string;
/** Unique ID for the visualization */
id: string;
/** The inspect title element for the visualization */
inspectTitle: ReactElement;
/** Optional override for space ID (if not provided, will use useSpaceId hook) */
spaceId?: string;
}

Expand All @@ -41,7 +36,6 @@ export const KeyInsightsTile: React.FC<KeyInsightsTileProps> = ({
inspectTitle,
spaceId: propSpaceId,
}) => {
const { euiTheme } = useEuiTheme();
const filterQuery = useEsqlGlobalFilterQuery();
const timerange = useGlobalTime();
const hookSpaceId = useSpaceId();
Expand All @@ -61,30 +55,77 @@ export const KeyInsightsTile: React.FC<KeyInsightsTileProps> = ({
filterQuery,
});

return (
<EuiFlexItem grow={false}>
<div
css={css`
height: ${LENS_VISUALIZATION_HEIGHT}px;
min-width: ${LENS_VISUALIZATION_MIN_WIDTH}px;
width: auto;
display: inline-block;
background: ${euiTheme.colors.lightestShade};
border-radius: ${euiTheme.border.radius.medium};
`}
const visualizationResponse = useVisualizationResponse({
visualizationId: id,
});

// Track whether loading has started at least once
const [hasStartedLoading, setHasStartedLoading] = useState(false);

useEffect(() => {
if (visualizationResponse?.loading === true) {
setHasStartedLoading(true);
}
}, [visualizationResponse?.loading]);

// Reset hasStartedLoading when any filter changes to allow fresh error detection
useEffect(() => {
setHasStartedLoading(false);
}, [timerange.from, timerange.to, filterQuery, effectiveSpaceId]);

// Only show error state if:
// 1. Loading has started at least once (hasStartedLoading)
// 2. Loading is now complete (loading === false)
// 3. We have no tables (indicating an error)
if (
hasStartedLoading &&
visualizationResponse &&
visualizationResponse.loading === false &&
!visualizationResponse.tables
) {
return (
<EuiFlexGroup
direction="column"
justifyContent="spaceBetween"
style={{ height: '100%' }} // ensures it uses the full height so 'space-between' works
>
<VisualizationEmbeddable
applyGlobalQueriesAndFilters={true}
applyPageAndTabsFilters={true}
lensAttributes={lensAttributes}
id={id}
timerange={timerange}
width="auto"
height={LENS_VISUALIZATION_HEIGHT}
disableOnClickFilter
inspectTitle={inspectTitle}
/>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4>{titleString}</h4>
</EuiTitle>
</EuiFlexItem>

<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end' }}>
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="warning" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.keyInsightsTile.dataNotAvailable"
defaultMessage="Data not available"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
}

// If we reach here, either still loading or we have a valid response, so show the embeddable
return (
<VisualizationEmbeddable
applyGlobalQueriesAndFilters={true}
applyPageAndTabsFilters={true}
lensAttributes={lensAttributes}
id={id}
timerange={timerange}
width={LENS_VISUALIZATION_MIN_WIDTH}
height={LENS_VISUALIZATION_HEIGHT}
disableOnClickFilter
inspectTitle={inspectTitle}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiFlexGrid, EuiPanel } from '@elastic/eui';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';

import { ActivePrivilegedUsersTile } from './active_privileged_users_tile';
Expand All @@ -17,59 +16,30 @@ import { GrantedRightsTile } from './granted_rights_tile';
import { AccountSwitchesTile } from './account_switches_tile';
import { AuthenticationsTile } from './authentications_tile';

const tileStyles = css`
border: 1px solid #d3dae6;
border-radius: 6px;
padding: 12px;
height: 100%;
`;

export const KeyInsightsPanel: React.FC<{ spaceId: string; sourcerDataView: DataViewSpec }> = ({
spaceId,
sourcerDataView,
}) => {
return (
<EuiFlexGroup
wrap
css={css`
width: 100%;
gap: 16px;
& > * {
min-width: calc(33.33% - 11px) !important;
max-width: calc(33.33% - 11px) !important;
}
`}
>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<ActivePrivilegedUsersTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<AlertsTriggeredTile spaceId={spaceId} />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<AnomaliesDetectedTile spaceId={spaceId} />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<GrantedRightsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<AccountSwitchesTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div css={tileStyles}>
<AuthenticationsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</div>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGrid columns={3} data-test-subj="key-insights-panel">
<EuiPanel hasBorder>
<ActivePrivilegedUsersTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</EuiPanel>
<EuiPanel hasBorder>
<AlertsTriggeredTile spaceId={spaceId} />
</EuiPanel>
<EuiPanel hasBorder>
<AnomaliesDetectedTile spaceId={spaceId} />
</EuiPanel>
<EuiPanel hasBorder>
<GrantedRightsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</EuiPanel>
<EuiPanel hasBorder>
<AccountSwitchesTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</EuiPanel>
<EuiPanel hasBorder>
<AuthenticationsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
</EuiPanel>
</EuiFlexGrid>
);
};