🌊 Streams: Move datepicker into chart panel on overview page (#217014)

This PR moves the datepicker on the overview page into the chart panel
to claim some additional vertical screen space and to make it clearer
what the stats on top of the page mean:

<img width="1016" alt="Screenshot 2025-04-03 at 14 58 27"
src="https://github.com/user-attachments/assets/b0100a3e-e9c4-419e-9803-45558b8a0fad"
/>

It also refactors the code a bit and reduces prop drilling in some
areas.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Joe Reuter 2025-04-04 13:07:33 +02:00 committed by GitHub
parent 6a0c173b1a
commit 10358d6f9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 223 additions and 286 deletions

View file

@ -7,18 +7,19 @@
import React from 'react';
import { EuiLink } from '@elastic/eui';
import { IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { ILM_LOCATOR_ID, IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { IngestStreamLifecycleILM } from '@kbn/streams-schema';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../hooks/use_kibana';
export function IlmLink({
ilmLocator,
lifecycle,
}: {
ilmLocator?: LocatorPublic<IlmLocatorParams>;
lifecycle: IngestStreamLifecycleILM;
}) {
export function IlmLink({ lifecycle }: { lifecycle: IngestStreamLifecycleILM }) {
const {
dependencies: {
start: { share },
},
} = useKibana();
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
return (
<EuiLink
target="_blank"

View file

@ -28,8 +28,6 @@ import {
formatNumber,
useEuiTheme,
} from '@elastic/eui';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
import { useKibana } from '../../../hooks/use_kibana';
import { orderIlmPhases, parseDurationInSeconds } from './helpers';
@ -39,11 +37,9 @@ import { useIlmPhasesColorAndDescription } from './hooks/use_ilm_phases_color_an
export function IlmSummary({
definition,
lifecycle,
ilmLocator,
}: {
definition: IngestStreamGetResponse;
lifecycle: IngestStreamLifecycleILM;
ilmLocator?: LocatorPublic<IlmLocatorParams>;
}) {
const {
dependencies: {
@ -104,7 +100,7 @@ export function IlmSummary({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<IlmLink lifecycle={lifecycle} ilmLocator={ilmLocator} />
<IlmLink lifecycle={lifecycle} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>

View file

@ -16,11 +16,7 @@ import {
isUnwiredStreamGetResponse,
isWiredStreamGetResponse,
} from '@kbn/streams-schema';
import {
ILM_LOCATOR_ID,
IlmLocatorParams,
PolicyFromES,
} from '@kbn/index-lifecycle-management-common-shared';
import { PolicyFromES } from '@kbn/index-lifecycle-management-common-shared';
import { i18n } from '@kbn/i18n';
import { useAbortController } from '@kbn/react-hooks';
import { useKibana } from '../../../hooks/use_kibana';
@ -98,7 +94,6 @@ export function StreamDetailLifecycle({
core: { http, notifications },
dependencies: {
start: {
share,
streams: { streamsRepositoryClient },
},
},
@ -122,8 +117,6 @@ export function StreamDetailLifecycle({
const { signal } = useAbortController();
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
const getIlmPolicies = () =>
http.get<PolicyFromES[]>('/api/index_lifecycle_management/policies', {
signal,
@ -177,7 +170,6 @@ export function StreamDetailLifecycle({
updateLifecycle={updateLifecycle}
getIlmPolicies={getIlmPolicies}
updateInProgress={updateInProgress}
ilmLocator={ilmLocator}
/>
<EuiPanel grow={false} hasShadow={false} hasBorder paddingSize="s">
@ -190,7 +182,6 @@ export function StreamDetailLifecycle({
<RetentionMetadata
definition={definition}
lifecycleActions={lifecycleActions}
ilmLocator={ilmLocator}
openEditModal={(action) => setOpenEditModal(action)}
isLoadingStats={isLoadingStats}
stats={stats}
@ -218,11 +209,7 @@ export function StreamDetailLifecycle({
{isIlmLifecycle(definition.effective_lifecycle) ? (
<EuiFlexItem grow={3}>
<EuiPanel grow={true} hasShadow={false} hasBorder paddingSize="s">
<IlmSummary
definition={definition}
lifecycle={definition.effective_lifecycle}
ilmLocator={ilmLocator}
/>
<IlmSummary definition={definition} lifecycle={definition.effective_lifecycle} />
</EuiPanel>
</EuiFlexItem>
) : null}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { LocatorPublic } from '@kbn/share-plugin/common';
import {
IngestStreamGetResponse,
isDisabledLifecycle,
@ -42,7 +40,6 @@ import { formatIngestionRate } from './helpers/format_bytes';
export function RetentionMetadata({
definition,
ilmLocator,
lifecycleActions,
openEditModal,
stats,
@ -50,7 +47,6 @@ export function RetentionMetadata({
statsError,
}: {
definition: IngestStreamGetResponse;
ilmLocator?: LocatorPublic<IlmLocatorParams>;
lifecycleActions: Array<{ name: string; action: LifecycleEditAction }>;
openEditModal: (action: LifecycleEditAction) => void;
stats?: DataStreamStats;
@ -99,7 +95,7 @@ export function RetentionMetadata({
const ilmLink = isIlmLifecycle(lifecycle) ? (
<EuiBadge color="hollow">
<IlmLink lifecycle={lifecycle} ilmLocator={ilmLocator} />
<IlmLink lifecycle={lifecycle} />
</EuiBadge>
) : null;

View file

@ -7,11 +7,11 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
ILM_LOCATOR_ID,
IlmLocatorParams,
Phases,
PolicyFromES,
} from '@kbn/index-lifecycle-management-common-shared';
import { LocatorPublic } from '@kbn/share-plugin/common';
import {
IngestStreamGetResponse,
IngestStreamLifecycle,
@ -55,6 +55,7 @@ import {
import { i18n } from '@kbn/i18n';
import { useBoolean } from '@kbn/react-hooks';
import useToggle from 'react-use/lib/useToggle';
import { useKibana } from '../../../hooks/use_kibana';
import { rolloverCondition } from './helpers/rollover_condition';
import { useStreamsAppRouter } from '../../../hooks/use_streams_app_router';
import { useWiredStreams } from '../../../hooks/use_wired_streams';
@ -69,7 +70,6 @@ interface ModalOptions {
getIlmPolicies: () => Promise<PolicyFromES[]>;
definition: IngestStreamGetResponse;
updateInProgress: boolean;
ilmLocator?: LocatorPublic<IlmLocatorParams>;
}
export function EditLifecycleModal({
@ -231,9 +231,15 @@ function IlmModal({
updateLifecycle,
updateInProgress,
getIlmPolicies,
ilmLocator,
definition,
}: ModalOptions) {
const {
dependencies: {
start: { share },
},
} = useKibana();
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
const existingLifecycle = definition.stream.ingest.lifecycle;
const [selectedPolicy, setSelectedPolicy] = useState(
isIlmLifecycle(existingLifecycle) ? existingLifecycle.ilm.policy : undefined

View file

@ -171,6 +171,7 @@ export function ControlledEsqlChart<T extends string>({
id="y-axis"
ticks={3}
position={Position.Left}
hide
tickFormat={yTickFormat}
labelFormat={yLabelFormat}
/>

View file

@ -4,33 +4,152 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiText,
formatNumber,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import React from 'react';
import type { DomainRange } from '@elastic/charts';
import { AbortableAsyncState } from '@kbn/react-hooks';
import type { UnparsedEsqlResponse } from '@kbn/traced-es-client';
import React, { useMemo } from 'react';
import { IngestStreamGetResponse } from '@kbn/streams-schema';
import { computeInterval } from '@kbn/visualization-utils';
import moment, { DurationInputArg1, DurationInputArg2 } from 'moment';
import { useKibana } from '../../../hooks/use_kibana';
import { ControlledEsqlChart } from '../../esql_chart/controlled_esql_chart';
import { getIndexPatterns } from '../../../util/hierarchy_helpers';
import { StreamsAppSearchBar } from '../../streams_app_search_bar';
import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
interface StreamChartPanelProps {
histogramQueryFetch: AbortableAsyncState<UnparsedEsqlResponse | undefined>;
discoverLink?: string;
xDomain?: DomainRange;
definition: IngestStreamGetResponse;
}
export function StreamChartPanel({
histogramQueryFetch,
discoverLink,
xDomain,
}: StreamChartPanelProps) {
export function StreamChartPanel({ definition }: StreamChartPanelProps) {
const {
dependencies: {
start: {
data,
dataViews,
streams: { streamsRepositoryClient },
share,
},
},
} = useKibana();
const {
timeRange,
setTimeRange,
absoluteTimeRange: { start, end },
refreshAbsoluteTimeRange,
} = data.query.timefilter.timefilter.useTimefilter();
const indexPatterns = useMemo(() => {
return getIndexPatterns(definition?.stream);
}, [definition]);
const discoverLocator = useMemo(
() => share.url.locators.get('DISCOVER_APP_LOCATOR'),
[share.url.locators]
);
const bucketSize = useMemo(() => computeInterval(timeRange, data), [data, timeRange]);
const queries = useMemo(() => {
if (!indexPatterns) {
return undefined;
}
const baseQuery = `FROM ${indexPatterns.join(', ')}`;
const histogramQuery = `${baseQuery} | STATS metric = COUNT(*) BY @timestamp = BUCKET(@timestamp, ${bucketSize})`;
return {
baseQuery,
histogramQuery,
};
}, [bucketSize, indexPatterns]);
const discoverLink = useMemo(() => {
if (!discoverLocator || !queries?.baseQuery) {
return undefined;
}
return discoverLocator.getRedirectUrl({
query: {
esql: queries.baseQuery,
},
});
}, [queries?.baseQuery, discoverLocator]);
const histogramQueryFetch = useStreamsAppFetch(
async ({ signal }) => {
if (!queries?.histogramQuery || !indexPatterns) {
return undefined;
}
const existingIndices = await dataViews.getExistingIndices(indexPatterns);
if (existingIndices.length === 0) {
return undefined;
}
return streamsRepositoryClient.fetch('POST /internal/streams/esql', {
params: {
body: {
operationName: 'get_histogram_for_stream',
query: queries.histogramQuery,
start,
end,
},
},
signal,
});
},
[indexPatterns, dataViews, streamsRepositoryClient, queries?.histogramQuery, start, end]
);
const docCountFetch = useStreamsAppFetch(
async ({ signal }) => {
if (!definition) {
return undefined;
}
return streamsRepositoryClient.fetch('GET /internal/streams/{name}/_details', {
signal,
params: {
path: {
name: definition.stream.name,
},
query: {
start: String(start),
end: String(end),
},
},
});
},
[definition, streamsRepositoryClient, start, end]
);
const [value, unit] = bucketSize.split(' ') as [DurationInputArg1, DurationInputArg2];
const xDomain = {
min: start,
max: end,
minInterval: moment.duration(value, unit).asMilliseconds(),
};
const docCount = docCountFetch?.value?.details.count;
const formattedDocCount = docCount ? formatNumber(docCount, 'decimal0') : '-';
return (
<EuiPanel hasShadow={false} hasBorder>
<EuiFlexGroup
direction="column"
className={css`
height: 100%;
min-height: 300px;
`}
>
<EuiFlexItem grow={false}>
@ -38,20 +157,60 @@ export function StreamChartPanel({
<EuiFlexItem grow={false}>
<EuiText size="s">
{i18n.translate('xpack.streams.streamDetailOverview.logRate', {
defaultMessage: 'Documents',
defaultMessage: '{number} documents',
values: {
number: formattedDocCount,
},
})}
</EuiText>
</EuiFlexItem>
<EuiButtonEmpty
data-test-subj="streamsDetailOverviewOpenInDiscoverButton"
iconType="discoverApp"
href={discoverLink}
isDisabled={!discoverLink}
>
{i18n.translate('xpack.streams.streamDetailOverview.openInDiscoverButtonLabel', {
defaultMessage: 'Open in Discover',
})}
</EuiButtonEmpty>
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiButtonEmpty
data-test-subj="streamsDetailOverviewOpenInDiscoverButton"
iconType="discoverApp"
href={discoverLink}
isDisabled={!discoverLink}
>
{i18n.translate('xpack.streams.streamDetailOverview.openInDiscoverButtonLabel', {
defaultMessage: 'Open in Discover',
})}
</EuiButtonEmpty>
<StreamsAppSearchBar
onQuerySubmit={({ dateRange }, isUpdate) => {
if (!isUpdate) {
if (!refreshAbsoluteTimeRange()) {
// if absolute time range didn't change, we need to manually refresh the histogram
// otherwise it will be refreshed by the changed absolute time range
histogramQueryFetch.refresh();
docCountFetch.refresh();
}
return;
}
if (dateRange) {
setTimeRange({
from: dateRange.from,
to: dateRange?.to,
mode: dateRange.mode,
});
}
}}
onRefresh={() => {
histogramQueryFetch.refresh();
docCountFetch.refresh();
}}
placeholder={i18n.translate(
'xpack.streams.entityDetailOverview.searchBarPlaceholder',
{
defaultMessage: 'Filter data by using KQL',
}
)}
dateRangeFrom={timeRange.from}
dateRangeTo={timeRange.to}
/>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow>

View file

@ -17,31 +17,19 @@ import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import React, { ReactNode } from 'react';
import { IngestStreamGetResponse, isDslLifecycle, isIlmLifecycle } from '@kbn/streams-schema';
import { IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { LocatorPublic } from '@kbn/share-plugin/public';
import type { StreamDetailsResponse } from '@kbn/streams-plugin/server/routes/internal/streams/crud/route';
import { IlmLink } from '../../data_management/stream_detail_lifecycle/ilm_link';
import {
formatBytes,
formatIngestionRate,
} from '../../data_management/stream_detail_lifecycle/helpers/format_bytes';
import { DataStreamStats } from '../../data_management/stream_detail_lifecycle/hooks/use_data_stream_stats';
import { useDataStreamStats } from '../../data_management/stream_detail_lifecycle/hooks/use_data_stream_stats';
interface StreamStatsPanelProps {
definition?: IngestStreamGetResponse;
dataStreamStats?: DataStreamStats;
docCount?: StreamDetailsResponse;
ilmLocator?: LocatorPublic<IlmLocatorParams>;
definition: IngestStreamGetResponse;
}
const RetentionDisplay = ({
definition,
ilmLocator,
}: {
definition?: IngestStreamGetResponse;
ilmLocator?: LocatorPublic<IlmLocatorParams>;
}) => {
const RetentionDisplay = ({ definition }: { definition: IngestStreamGetResponse }) => {
if (!definition) return <>-</>;
if (isDslLifecycle(definition.effective_lifecycle)) {
@ -56,7 +44,7 @@ const RetentionDisplay = ({
}
if (isIlmLifecycle(definition.effective_lifecycle)) {
return <IlmLink lifecycle={definition.effective_lifecycle} ilmLocator={ilmLocator} />;
return <IlmLink lifecycle={definition.effective_lifecycle} />;
}
return <>-</>;
@ -97,12 +85,8 @@ const StatItem = ({ label, value, withBorder = false }: StatItemProps) => {
);
};
export function StreamStatsPanel({
definition,
dataStreamStats,
docCount,
ilmLocator,
}: StreamStatsPanelProps) {
export function StreamStatsPanel({ definition }: StreamStatsPanelProps) {
const dataStreamStats = useDataStreamStats({ definition }).stats;
const retentionLabel = i18n.translate('xpack.streams.entityDetailOverview.retention', {
defaultMessage: 'Data retention',
});
@ -128,7 +112,7 @@ export function StreamStatsPanel({
{retentionLabel}
</EuiText>
<EuiText size="m">
<RetentionDisplay definition={definition} ilmLocator={ilmLocator} />
<RetentionDisplay definition={definition} />
</EuiText>
</EuiFlexGroup>
</EuiPanel>
@ -138,24 +122,15 @@ export function StreamStatsPanel({
<EuiFlexGroup>
<StatItem
label={documentCountLabel}
value={docCount ? formatNumber(docCount.details.count || 0, 'decimal0') : '-'}
value={
dataStreamStats ? formatNumber(dataStreamStats.totalDocs || 0, 'decimal0') : '-'
}
/>
<StatItem
label={
<>
{storageSizeLabel}
<EuiIconTip
content={i18n.translate('xpack.streams.streamDetailOverview.sizeTip', {
defaultMessage:
'Estimated size based on the number of documents in the current time range and the total size of the stream.',
})}
position="right"
/>
</>
}
label={storageSizeLabel}
value={
dataStreamStats && docCount
? formatBytes(getStorageSizeForTimeRange(dataStreamStats, docCount))
dataStreamStats && dataStreamStats.sizeBytes
? formatBytes(dataStreamStats.sizeBytes)
: '-'
}
withBorder
@ -187,19 +162,3 @@ export function StreamStatsPanel({
</EuiFlexGroup>
);
}
function getStorageSizeForTimeRange(
dataStreamStats: DataStreamStats,
docCount: StreamDetailsResponse
) {
const storageSize = dataStreamStats.sizeBytes;
const totalCount = dataStreamStats.totalDocs;
const countForTimeRange = docCount.details.count;
if (!storageSize || !totalCount || !countForTimeRange) {
return 0;
}
if (!totalCount) {
return 0;
}
return storageSize * (countForTimeRange / totalCount);
}

View file

@ -8,15 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { IngestStreamGetResponse, isWiredStreamDefinition } from '@kbn/streams-schema';
import { ILM_LOCATOR_ID, IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
import { computeInterval } from '@kbn/visualization-utils';
import moment, { DurationInputArg1, DurationInputArg2 } from 'moment';
import { useKibana } from '../../hooks/use_kibana';
import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch';
import { StreamsAppSearchBar } from '../streams_app_search_bar';
import { getIndexPatterns } from '../../util/hierarchy_helpers';
import { useDataStreamStats } from '../data_management/stream_detail_lifecycle/hooks/use_data_stream_stats';
import { QuickLinks } from './quick_links';
import { ChildStreamList } from './child_stream_list';
import { StreamStatsPanel } from './components/stream_stats_panel';
@ -24,112 +16,6 @@ import { StreamChartPanel } from './components/stream_chart_panel';
import { TabsPanel } from './components/tabs_panel';
export function StreamDetailOverview({ definition }: { definition: IngestStreamGetResponse }) {
const {
dependencies: {
start: {
data,
dataViews,
streams: { streamsRepositoryClient },
share,
},
},
} = useKibana();
const {
timeRange,
setTimeRange,
absoluteTimeRange: { start, end },
refreshAbsoluteTimeRange,
} = data.query.timefilter.timefilter.useTimefilter();
const indexPatterns = useMemo(() => {
return getIndexPatterns(definition?.stream);
}, [definition]);
const discoverLocator = useMemo(
() => share.url.locators.get('DISCOVER_APP_LOCATOR'),
[share.url.locators]
);
const bucketSize = useMemo(() => computeInterval(timeRange, data), [data, timeRange]);
const queries = useMemo(() => {
if (!indexPatterns) {
return undefined;
}
const baseQuery = `FROM ${indexPatterns.join(', ')}`;
const histogramQuery = `${baseQuery} | STATS metric = COUNT(*) BY @timestamp = BUCKET(@timestamp, ${bucketSize})`;
return {
baseQuery,
histogramQuery,
};
}, [bucketSize, indexPatterns]);
const discoverLink = useMemo(() => {
if (!discoverLocator || !queries?.baseQuery) {
return undefined;
}
return discoverLocator.getRedirectUrl({
query: {
esql: queries.baseQuery,
},
});
}, [queries?.baseQuery, discoverLocator]);
const histogramQueryFetch = useStreamsAppFetch(
async ({ signal }) => {
if (!queries?.histogramQuery || !indexPatterns) {
return undefined;
}
const existingIndices = await dataViews.getExistingIndices(indexPatterns);
if (existingIndices.length === 0) {
return undefined;
}
return streamsRepositoryClient.fetch('POST /internal/streams/esql', {
params: {
body: {
operationName: 'get_histogram_for_stream',
query: queries.histogramQuery,
start,
end,
},
},
signal,
});
},
[indexPatterns, dataViews, streamsRepositoryClient, queries?.histogramQuery, start, end]
);
const docCountFetch = useStreamsAppFetch(
async ({ signal }) => {
if (!definition) {
return undefined;
}
return streamsRepositoryClient.fetch('GET /internal/streams/{name}/_details', {
signal,
params: {
path: {
name: definition.stream.name,
},
query: {
start: String(start),
end: String(end),
},
},
});
},
[definition, streamsRepositoryClient, start, end]
);
const dataStreamStats = useDataStreamStats({ definition });
const tabs = useMemo(
() => [
...(definition && isWiredStreamDefinition(definition.stream)
@ -154,72 +40,18 @@ export function StreamDetailOverview({ definition }: { definition: IngestStreamG
[definition]
);
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
const [value, unit] = bucketSize.split(' ') as [DurationInputArg1, DurationInputArg2];
return (
<>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" justifyContent="flexEnd">
<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
// otherwise it will be refreshed by the changed absolute time range
histogramQueryFetch.refresh();
docCountFetch.refresh();
}
return;
}
if (dateRange) {
setTimeRange({ from: dateRange.from, to: dateRange?.to, mode: dateRange.mode });
}
}}
onRefresh={() => {
histogramQueryFetch.refresh();
docCountFetch.refresh();
}}
placeholder={i18n.translate(
'xpack.streams.entityDetailOverview.searchBarPlaceholder',
{
defaultMessage: 'Filter data by using KQL',
}
)}
dateRangeFrom={timeRange.from}
dateRangeTo={timeRange.to}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<StreamStatsPanel
definition={definition}
dataStreamStats={dataStreamStats.stats}
docCount={docCountFetch.value}
ilmLocator={ilmLocator}
/>
<StreamStatsPanel definition={definition} />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="m">
<EuiFlexItem grow={4}>{definition && <TabsPanel tabs={tabs} />}</EuiFlexItem>
<EuiFlexItem grow={8}>
<StreamChartPanel
histogramQueryFetch={histogramQueryFetch}
discoverLink={discoverLink}
xDomain={{
min: start,
max: end,
minInterval: moment.duration(value, unit).asMilliseconds(),
}}
/>
<StreamChartPanel definition={definition} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>