🌊 Streams: Warn about EIS costs and improve current storage display (#216884)

If a managed connector is used, show a icon with a tooltip to remind
them this costs money
<img width="436" alt="Screenshot 2025-04-02 at 17 00 56"
src="https://github.com/user-attachments/assets/495fa834-a0ec-4228-802e-ea2eee7678c5"
/>

I checked on serverless prod that the numbers we report on storage size
is identical with what `/app/management/data/data_usage` based on
auto-ops reports, with some caveats:
* We were using the eui number formatter which was configured to use
megabyte (MB, 1000^2) instead of mebibyte (MiB, 1024^2) - auto ops was
using mebibyte but still formatted the number as MB. I switched it over
to use mebibyte, so the numbers are the same now, but it's rendering
`MiB`. IMHO this is OK since it's more exact, but I wanted to call it
out
<img width="141" alt="Screenshot 2025-04-02 at 17 35 03"
src="https://github.com/user-attachments/assets/6145acfb-9a84-4ba0-81d0-a32718a5fff4"
/>

* On the overview page, the refresh button would not refresh the data
stream stats, which would cause a drift of the numbers over time. Fixed
that

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Joe Reuter 2025-04-04 11:19:07 +02:00 committed by GitHub
parent baf1864332
commit 320f1d2aee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 29 additions and 4 deletions

View file

@ -32,12 +32,15 @@ import useObservable from 'react-use/lib/useObservable';
import { APIReturnType } from '@kbn/streams-plugin/public/api';
import { isEmpty } from 'lodash';
import { css } from '@emotion/css';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { useStreamDetail } from '../../../../../hooks/use_stream_detail';
import { useKibana } from '../../../../../hooks/use_kibana';
import { GrokFormState, ProcessorFormState } from '../../types';
import { useSimulatorSelector } from '../../state_management/stream_enrichment_state_machine';
import { selectPreviewDocuments } from '../../state_management/simulation_state_machine/selectors';
const INTERNAL_INFERENCE_CONNECTORS = ['Elastic-LLM'];
const RefreshButton = ({
generatePatterns,
connectors,
@ -276,6 +279,14 @@ function InnerGrokAiSuggestions({
content = null;
}
const isManagedAIConnector = !INTERNAL_INFERENCE_CONNECTORS.includes(currentConnector || '');
const [isManagedAiConnectorCalloutDismissed, setManagedAiConnectorCalloutDismissed] =
useLocalStorage('streams:managedAiConnectorCalloutDismissed', false);
const onDismissManagedAiConnectorCallout = useCallback(() => {
setManagedAiConnectorCalloutDismissed(true);
}, [setManagedAiConnectorCalloutDismissed]);
if (filteredSuggestions && filteredSuggestions.length) {
content = (
<EuiFlexGroup direction="column" gutterSize="m">
@ -383,6 +394,17 @@ function InnerGrokAiSuggestions({
/>
</EuiFlexGroup>
</EuiFlexItem>
{!isManagedAiConnectorCalloutDismissed && isManagedAIConnector && (
<EuiCallOut onDismiss={onDismissManagedAiConnectorCallout}>
{i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processorFlyout.managedConnectorTooltip',
{
defaultMessage:
'Generating patterns is powered by a preconfigured LLM. Additional charges apply',
}
)}
</EuiCallOut>
)}
</>
);
}

View file

@ -8,7 +8,7 @@
import { formatNumber } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export const formatBytes = (value: number) => formatNumber(value, '0.0 b');
export const formatBytes = (value: number) => formatNumber(value, '0.0 ib');
export const formatIngestionRate = (bytesPerDay: number, perDayOnly = false) => {
const perDay = formatBytes(bytesPerDay);

View file

@ -194,7 +194,7 @@ function IlmPhase({
>
<EuiText size="xs">
<p>
<b>Size</b> {formatNumber(phase.size_in_bytes, '0.0 b')}
<b>Size</b> {formatNumber(phase.size_in_bytes, '0.0 ib')}
</p>
</EuiText>
</EuiPanel>

View file

@ -198,6 +198,8 @@ function getStorageSizeForTimeRange(
if (!storageSize || !totalCount || !countForTimeRange) {
return 0;
}
const bytesPerDoc = totalCount ? storageSize / totalCount : 0;
return bytesPerDoc * countForTimeRange;
if (!totalCount) {
return 0;
}
return storageSize * (countForTimeRange / totalCount);
}

View file

@ -166,6 +166,7 @@ export function StreamDetailOverview({ definition }: { definition: IngestStreamG
<EuiFlexItem grow>
<StreamsAppSearchBar
onQuerySubmit={({ dateRange }, isUpdate) => {
dataStreamStats.refresh();
if (!isUpdate) {
if (!refreshAbsoluteTimeRange()) {
// if absolute time range didn't change, we need to manually refresh the histogram