Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dd2cf8c
refactor(streams): udpate routing data fetching
tonyghiani Jun 30, 2025
8c8fe42
wip(streams): wire samples machine
tonyghiani Jul 1, 2025
5807f04
wip(streams): update samples machine
tonyghiani Jul 2, 2025
8839654
wip(streams): update samples machine
tonyghiani Jul 2, 2025
b4379b8
wip(streams): update samples machine for time handling
tonyghiani Jul 2, 2025
861003c
feat(streams): refactor and fix data fetching
tonyghiani Jul 3, 2025
a62fe3d
refactor(streams): simplify machine
tonyghiani Jul 3, 2025
80591d1
refactor(streams): move code around
tonyghiani Jul 3, 2025
14ef1fe
refactor(streams): fix ts issue
tonyghiani Jul 3, 2025
81bed30
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 3, 2025
4a5bb9f
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 4, 2025
688ef97
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 7, 2025
799345f
refactor(streams): udpate async search
tonyghiani Jul 7, 2025
3a19d8b
refactor(streams): address request changes
tonyghiani Jul 7, 2025
833900a
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 7, 2025
d966fb1
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 8, 2025
a85ca1b
Update processor_outcome_preview.tsx
tonyghiani Jul 8, 2025
1fc47a2
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 8, 2025
cfb9eb8
refactor(streams): update list when not matching data
tonyghiani Jul 8, 2025
666f7a1
Merge branch 'main' into 219494-prevent-routing-timeout
tonyghiani Jul 9, 2025
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 @@ -21,6 +21,7 @@ import { GrokProcessorDefinition, SampleDocument } from '@kbn/streams-schema';
import { DocViewsRegistry } from '@kbn/unified-doc-viewer';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { getPercentageFormatter } from '../../../util/formatters';
import { useKibana } from '../../../hooks/use_kibana';
import { PreviewTable } from '../preview_table';
import {
Expand Down Expand Up @@ -114,10 +115,7 @@ export const ProcessorOutcomePreview = () => {
);
};

const formatter = new Intl.NumberFormat('en-US', {
Comment thread
tonyghiani marked this conversation as resolved.
style: 'percent',
maximumFractionDigits: 1,
});
const formatter = getPercentageFormatter();

const formatRateToPercentage = (rate?: number) =>
(rate ? formatter.format(rate) : undefined) as any; // This is a workaround for the type error, since the numFilters & numActiveFilters props are defined as number | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import useToggle from 'react-use/lib/useToggle';
import { css } from '@emotion/react';
import { getPercentageFormatter } from '../../../../util/formatters';
import { ProcessorMetrics } from '../state_management/simulation_state_machine';

type ProcessorMetricBadgesProps = ProcessorMetrics;

const formatter = new Intl.NumberFormat('en-US', {
style: 'percent',
maximumFractionDigits: 1,
});
const formatter = getPercentageFormatter();

export const ProcessorMetricBadges = ({
detected_fields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import {
EuiButton,
EuiFlexGroup,
Expand All @@ -14,11 +15,11 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/css';
import { Streams } from '@kbn/streams-schema';
import React from 'react';
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { CoreStart } from '@kbn/core/public';
import { useTimefilter } from '../../../hooks/use_timefilter';
import { useKibana } from '../../../hooks/use_kibana';
import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
import { ChildStreamList } from './child_stream_list';
Expand Down Expand Up @@ -47,12 +48,15 @@ export function StreamDetailRouting(props: StreamDetailRoutingProps) {
streams: { streamsRepositoryClient },
} = dependencies.start;

const { timeState$ } = useTimefilter();

return (
<StreamRoutingContextProvider
definition={props.definition}
refreshDefinition={props.refreshDefinition}
core={core}
data={data}
timeState$={timeState$}
streamsRepositoryClient={streamsRepositoryClient}
forkSuccessNofitier={createForkSuccessNofitier({ core, router })}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { EuiText, EuiLoadingSpinner, EuiIconTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { AsyncSample } from '../../../hooks/queries/use_async_sample';
import { RoutingSamplesContext } from './state_management/stream_routing_state_machine/routing_samples_state_machine';

const matchText = i18n.translate('xpack.streams.streamRouting.previewMatchesText', {
defaultMessage: 'Approximate match rate',
Expand All @@ -23,9 +23,9 @@ export const PreviewMatches = ({
error,
isLoading,
}: {
approximateMatchingPercentage: AsyncSample['approximateMatchingPercentage'];
error: AsyncSample['documentCountsError'];
isLoading: AsyncSample['isLoadingDocumentCounts'];
approximateMatchingPercentage: RoutingSamplesContext['approximateMatchingPercentage'];
error?: RoutingSamplesContext['approximateMatchingPercentageError'];
isLoading: boolean;
}) => {
if (isLoading) {
return (
Expand All @@ -39,17 +39,15 @@ export const PreviewMatches = ({
if (error) {
return (
<EuiText size="xs" textAlign="center">
{`${matchText}: `}
{errorText}
{`${matchText}: ${errorText}`}
</EuiText>
);
}

if (approximateMatchingPercentage) {
return (
<EuiText size="xs" textAlign="center">
{`${matchText}: `}
{`${approximateMatchingPercentage}%`}
{`${matchText}: ${approximateMatchingPercentage}`}
<InfoTooltip />
</EuiText>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,119 +16,121 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import React, { useEffect } from 'react';
import { useAsyncSample } from '../../../hooks/queries/use_async_sample';
import { useTimefilter } from '../../../hooks/use_timefilter';
import { useDebounced } from '../../../util/use_debounce';
import React from 'react';
import { AssetImage } from '../../asset_image';
import { StreamsAppSearchBar } from '../../streams_app_search_bar';
import { PreviewTable } from '../preview_table';
import { PreviewMatches } from './preview_matches';
import {
selectCurrentRule,
useStreamSamplesSelector,
useStreamsRoutingSelector,
} from './state_management/stream_routing_state_machine';

export function PreviewPanel() {
const routingSnapshot = useStreamsRoutingSelector((snapshot) => snapshot);

const isIdle = routingSnapshot.matches({ ready: 'idle' });
const isCreatingNewRule = routingSnapshot.matches({ ready: 'creatingNewRule' });
const isEditingRule = routingSnapshot.matches({ ready: 'editingRule' });
const isReorideringRules = routingSnapshot.matches({ ready: 'reorderingRules' });
let content;

const condition = isCreatingNewRule ? selectCurrentRule(routingSnapshot.context).if : undefined;
const definition = routingSnapshot.context.definition;
if (routingSnapshot.matches({ ready: 'idle' })) {
content = <IdlePanel />;
} else if (
routingSnapshot.matches({ ready: 'editingRule' }) ||
routingSnapshot.matches({ ready: 'reorderingRules' })
) {
content = <EditingPanel />;
} else if (routingSnapshot.matches({ ready: 'creatingNewRule' })) {
content = <RuleCreationPanel />;
}

const debouncedCondition = useDebounced(condition, 300);
return (
<>
<EuiFlexItem grow={false}>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" wrap>
<EuiFlexGroup component="span" gutterSize="s" alignItems="center">
<EuiIcon type="inspect" />
<strong>
{i18n.translate('xpack.streams.streamDetail.preview.header', {
defaultMessage: 'Data Preview',
})}
</strong>
</EuiFlexGroup>
<StreamsAppSearchBar showDatePicker />
</EuiFlexGroup>
</EuiFlexItem>
<EuiSpacer size="s" />
<EuiFlexItem grow>{content}</EuiFlexItem>
</>
);
}

const { timeState, timeState$ } = useTimefilter();
const IdlePanel = () => (
<EuiEmptyPrompt
icon={<AssetImage type="yourPreviewWillAppearHere" />}
titleSize="s"
title={
<h2>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmpty', {
defaultMessage: 'Your preview will appear here',
})}
</h2>
}
body={i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmptyDescription', {
defaultMessage:
'Create a new child stream to see what will be routed to it based on the conditions',
})}
/>
);

const EditingPanel = () => (
<EuiEmptyPrompt
icon={<AssetImage />}
titleSize="s"
title={
<h2>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessage', {
defaultMessage: 'Preview is not available while editing or reordering streams',
})}
</h2>
}
body={
<>
<p>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageBody', {
defaultMessage:
'Once you save your changes, the results of your conditions will appear here.',
})}
</p>
<p>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewReorderingWarning', {
defaultMessage:
'Additionally, you will not be able to edit existing streams while reordering them, you should save or cancel your changes first.',
})}
</p>
</>
}
/>
);

const RuleCreationPanel = () => {
const samplesSnapshot = useStreamSamplesSelector((snapshot) => snapshot);
const isLoadingDocuments = samplesSnapshot.matches({ fetching: { documents: 'loading' } });
const isUpdating =
samplesSnapshot.matches('debouncingCondition') ||
samplesSnapshot.matches({ fetching: { documents: 'loading' } });
const {
isLoadingDocuments,
documents,
documentsError,
refresh,
approximateMatchingPercentage,
isLoadingDocumentCounts,
documentCountsError,
} = useAsyncSample({
condition: debouncedCondition,
start: timeState.start,
end: timeState.end,
size: 100,
streamDefinition: definition,
});

approximateMatchingPercentageError,
} = samplesSnapshot.context;
const hasDocuments = !isEmpty(documents);
const isLoadingDocumentCounts = samplesSnapshot.matches({
fetching: { documentCounts: 'loading' },
});

useEffect(() => {
const subscription = timeState$.subscribe({
next: ({ kind }) => {
if (kind === 'override') {
refresh();
}
},
});
return () => {
subscription.unsubscribe();
};
}, [timeState$, refresh]);

let content;
let content = null;

if (isIdle) {
content = (
<EuiEmptyPrompt
icon={<AssetImage type="yourPreviewWillAppearHere" />}
titleSize="s"
title={
<h2>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmpty', {
defaultMessage: 'Your preview will appear here',
})}
</h2>
}
body={i18n.translate(
'xpack.streams.streamDetail.preview.editPreviewMessageEmptyDescription',
{
defaultMessage:
'Create a new child stream to see what will be routed to it based on the conditions',
}
)}
/>
);
} else if (isEditingRule || isReorideringRules) {
content = (
<EuiEmptyPrompt
icon={<AssetImage />}
titleSize="s"
title={
<h2>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessage', {
defaultMessage: 'Preview is not available while editing or reordering streams',
})}
</h2>
}
body={
<>
<p>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageBody', {
defaultMessage:
'You will find here the result from the conditions you have made once you save the changes',
})}
</p>
<p>
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewReorderingWarning', {
defaultMessage:
'Additionally, you will not be able to edit existing streams while reordering them, you should save or cancel your changes first.',
})}
</p>
</>
}
/>
);
} else if (isCreatingNewRule && isLoadingDocuments && !hasDocuments) {
if (isLoadingDocuments && !hasDocuments) {
content = (
<EuiEmptyPrompt
icon={<EuiLoadingLogo logo="logoLogging" size="xl" />}
Expand All @@ -146,7 +148,7 @@ export function PreviewPanel() {
})}
/>
);
} else if (isCreatingNewRule && documentsError) {
} else if (documentsError) {
content = (
<EuiEmptyPrompt
icon={<AssetImage type="noResults" />}
Expand All @@ -162,7 +164,7 @@ export function PreviewPanel() {
body={documentsError.message}
/>
);
} else if (isCreatingNewRule && !hasDocuments) {
} else if (!hasDocuments) {
content = (
<EuiEmptyPrompt
icon={<AssetImage type="noResults" />}
Expand All @@ -176,41 +178,27 @@ export function PreviewPanel() {
}
/>
);
} else if (isCreatingNewRule && hasDocuments) {
} else if (hasDocuments) {
content = (
<EuiFlexItem grow>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<PreviewMatches
approximateMatchingPercentage={approximateMatchingPercentage}
error={documentCountsError}
error={approximateMatchingPercentageError}
isLoading={isLoadingDocumentCounts}
/>
</EuiFlexItem>
<PreviewTable documents={documents ?? []} />
<PreviewTable documents={documents} />
</EuiFlexGroup>
</EuiFlexItem>
);
}

return (
<>
<EuiFlexItem grow={false}>
{isLoadingDocuments && <EuiProgress size="xs" color="accent" position="absolute" />}
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" wrap>
<EuiFlexGroup component="span" gutterSize="s" alignItems="center">
<EuiIcon type="inspect" />
<strong>
{i18n.translate('xpack.streams.streamDetail.preview.header', {
defaultMessage: 'Data Preview',
})}
</strong>
</EuiFlexGroup>
<StreamsAppSearchBar showDatePicker />
</EuiFlexGroup>
</EuiFlexItem>
<EuiSpacer size="s" />
<EuiFlexItem grow>{content}</EuiFlexItem>
{isUpdating && <EuiProgress size="xs" color="accent" position="absolute" />}
{content}
</>
);
}
};
Loading