Skip to content

Commit 00333f2

Browse files
authored
[Streams 🌊] Prevent routing simulation timeout and improve cancellations (#226374)
## 📓 Summary Closes #219494 Refactor the samples fetching logic and address the following issues: - Add a timeout (10 seconds) to avoid requests on incomplete or unmatching conditions to keep running with no feedback. - Update cancellation logic, previously was not catching correctly the request abortions. - Sample data fetching performed on the creation form only.
1 parent 36fc979 commit 00333f2

File tree

12 files changed

+705
-496
lines changed

12 files changed

+705
-496
lines changed

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_enrichment/processor_outcome_preview.tsx‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { GrokProcessorDefinition, SampleDocument } from '@kbn/streams-schema';
2121
import { DocViewsRegistry } from '@kbn/unified-doc-viewer';
2222
import { i18n } from '@kbn/i18n';
2323
import { isEmpty } from 'lodash';
24+
import { getPercentageFormatter } from '../../../util/formatters';
2425
import { useKibana } from '../../../hooks/use_kibana';
2526
import { PreviewTable } from '../preview_table';
2627
import {
@@ -114,10 +115,7 @@ export const ProcessorOutcomePreview = () => {
114115
);
115116
};
116117

117-
const formatter = new Intl.NumberFormat('en-US', {
118-
style: 'percent',
119-
maximumFractionDigits: 1,
120-
});
118+
const formatter = getPercentageFormatter();
121119

122120
const formatRateToPercentage = (rate?: number) =>
123121
(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

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_enrichment/processors/processor_metrics.tsx‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ import React from 'react';
2020
import { i18n } from '@kbn/i18n';
2121
import useToggle from 'react-use/lib/useToggle';
2222
import { css } from '@emotion/react';
23+
import { getPercentageFormatter } from '../../../../util/formatters';
2324
import { ProcessorMetrics } from '../state_management/simulation_state_machine';
2425

2526
type ProcessorMetricBadgesProps = ProcessorMetrics;
2627

27-
const formatter = new Intl.NumberFormat('en-US', {
28-
style: 'percent',
29-
maximumFractionDigits: 1,
30-
});
28+
const formatter = getPercentageFormatter();
3129

3230
export const ProcessorMetricBadges = ({
3331
detected_fields,

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/index.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* 2.0; you may not use this file except in compliance with the Elastic License
55
* 2.0.
66
*/
7+
import React from 'react';
78
import {
89
EuiButton,
910
EuiFlexGroup,
@@ -14,11 +15,11 @@ import {
1415
} from '@elastic/eui';
1516
import { css } from '@emotion/css';
1617
import { Streams } from '@kbn/streams-schema';
17-
import React from 'react';
1818
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
1919
import { i18n } from '@kbn/i18n';
2020
import { toMountPoint } from '@kbn/react-kibana-mount';
2121
import { CoreStart } from '@kbn/core/public';
22+
import { useTimefilter } from '../../../hooks/use_timefilter';
2223
import { useKibana } from '../../../hooks/use_kibana';
2324
import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
2425
import { ChildStreamList } from './child_stream_list';
@@ -47,12 +48,15 @@ export function StreamDetailRouting(props: StreamDetailRoutingProps) {
4748
streams: { streamsRepositoryClient },
4849
} = dependencies.start;
4950

51+
const { timeState$ } = useTimefilter();
52+
5053
return (
5154
<StreamRoutingContextProvider
5255
definition={props.definition}
5356
refreshDefinition={props.refreshDefinition}
5457
core={core}
5558
data={data}
59+
timeState$={timeState$}
5660
streamsRepositoryClient={streamsRepositoryClient}
5761
forkSuccessNofitier={createForkSuccessNofitier({ core, router })}
5862
>

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/preview_matches.tsx‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { EuiText, EuiLoadingSpinner, EuiIconTip } from '@elastic/eui';
99
import { i18n } from '@kbn/i18n';
1010
import React from 'react';
11-
import { AsyncSample } from '../../../hooks/queries/use_async_sample';
11+
import { RoutingSamplesContext } from './state_management/stream_routing_state_machine/routing_samples_state_machine';
1212

1313
const matchText = i18n.translate('xpack.streams.streamRouting.previewMatchesText', {
1414
defaultMessage: 'Approximate match rate',
@@ -23,9 +23,9 @@ export const PreviewMatches = ({
2323
error,
2424
isLoading,
2525
}: {
26-
approximateMatchingPercentage: AsyncSample['approximateMatchingPercentage'];
27-
error: AsyncSample['documentCountsError'];
28-
isLoading: AsyncSample['isLoadingDocumentCounts'];
26+
approximateMatchingPercentage: RoutingSamplesContext['approximateMatchingPercentage'];
27+
error?: RoutingSamplesContext['approximateMatchingPercentageError'];
28+
isLoading: boolean;
2929
}) => {
3030
if (isLoading) {
3131
return (
@@ -39,17 +39,15 @@ export const PreviewMatches = ({
3939
if (error) {
4040
return (
4141
<EuiText size="xs" textAlign="center">
42-
{`${matchText}: `}
43-
{errorText}
42+
{`${matchText}: ${errorText}`}
4443
</EuiText>
4544
);
4645
}
4746

4847
if (approximateMatchingPercentage) {
4948
return (
5049
<EuiText size="xs" textAlign="center">
51-
{`${matchText}: `}
52-
{`${approximateMatchingPercentage}%`}
50+
{`${matchText}: ${approximateMatchingPercentage}`}
5351
<InfoTooltip />
5452
</EuiText>
5553
);

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/preview_panel.tsx‎

Lines changed: 101 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -16,119 +16,121 @@ import {
1616
} from '@elastic/eui';
1717
import { i18n } from '@kbn/i18n';
1818
import { isEmpty } from 'lodash';
19-
import React, { useEffect } from 'react';
20-
import { useAsyncSample } from '../../../hooks/queries/use_async_sample';
21-
import { useTimefilter } from '../../../hooks/use_timefilter';
22-
import { useDebounced } from '../../../util/use_debounce';
19+
import React from 'react';
2320
import { AssetImage } from '../../asset_image';
2421
import { StreamsAppSearchBar } from '../../streams_app_search_bar';
2522
import { PreviewTable } from '../preview_table';
2623
import { PreviewMatches } from './preview_matches';
2724
import {
28-
selectCurrentRule,
25+
useStreamSamplesSelector,
2926
useStreamsRoutingSelector,
3027
} from './state_management/stream_routing_state_machine';
3128

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

35-
const isIdle = routingSnapshot.matches({ ready: 'idle' });
36-
const isCreatingNewRule = routingSnapshot.matches({ ready: 'creatingNewRule' });
37-
const isEditingRule = routingSnapshot.matches({ ready: 'editingRule' });
38-
const isReorideringRules = routingSnapshot.matches({ ready: 'reorderingRules' });
32+
let content;
3933

40-
const condition = isCreatingNewRule ? selectCurrentRule(routingSnapshot.context).if : undefined;
41-
const definition = routingSnapshot.context.definition;
34+
if (routingSnapshot.matches({ ready: 'idle' })) {
35+
content = <IdlePanel />;
36+
} else if (
37+
routingSnapshot.matches({ ready: 'editingRule' }) ||
38+
routingSnapshot.matches({ ready: 'reorderingRules' })
39+
) {
40+
content = <EditingPanel />;
41+
} else if (routingSnapshot.matches({ ready: 'creatingNewRule' })) {
42+
content = <RuleCreationPanel />;
43+
}
4244

43-
const debouncedCondition = useDebounced(condition, 300);
45+
return (
46+
<>
47+
<EuiFlexItem grow={false}>
48+
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" wrap>
49+
<EuiFlexGroup component="span" gutterSize="s" alignItems="center">
50+
<EuiIcon type="inspect" />
51+
<strong>
52+
{i18n.translate('xpack.streams.streamDetail.preview.header', {
53+
defaultMessage: 'Data Preview',
54+
})}
55+
</strong>
56+
</EuiFlexGroup>
57+
<StreamsAppSearchBar showDatePicker />
58+
</EuiFlexGroup>
59+
</EuiFlexItem>
60+
<EuiSpacer size="s" />
61+
<EuiFlexItem grow>{content}</EuiFlexItem>
62+
</>
63+
);
64+
}
4465

45-
const { timeState, timeState$ } = useTimefilter();
66+
const IdlePanel = () => (
67+
<EuiEmptyPrompt
68+
icon={<AssetImage type="yourPreviewWillAppearHere" />}
69+
titleSize="s"
70+
title={
71+
<h2>
72+
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmpty', {
73+
defaultMessage: 'Your preview will appear here',
74+
})}
75+
</h2>
76+
}
77+
body={i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmptyDescription', {
78+
defaultMessage:
79+
'Create a new child stream to see what will be routed to it based on the conditions',
80+
})}
81+
/>
82+
);
4683

84+
const EditingPanel = () => (
85+
<EuiEmptyPrompt
86+
icon={<AssetImage />}
87+
titleSize="s"
88+
title={
89+
<h2>
90+
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessage', {
91+
defaultMessage: 'Preview is not available while editing or reordering streams',
92+
})}
93+
</h2>
94+
}
95+
body={
96+
<>
97+
<p>
98+
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageBody', {
99+
defaultMessage:
100+
'Once you save your changes, the results of your conditions will appear here.',
101+
})}
102+
</p>
103+
<p>
104+
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewReorderingWarning', {
105+
defaultMessage:
106+
'Additionally, you will not be able to edit existing streams while reordering them, you should save or cancel your changes first.',
107+
})}
108+
</p>
109+
</>
110+
}
111+
/>
112+
);
113+
114+
const RuleCreationPanel = () => {
115+
const samplesSnapshot = useStreamSamplesSelector((snapshot) => snapshot);
116+
const isLoadingDocuments = samplesSnapshot.matches({ fetching: { documents: 'loading' } });
117+
const isUpdating =
118+
samplesSnapshot.matches('debouncingCondition') ||
119+
samplesSnapshot.matches({ fetching: { documents: 'loading' } });
47120
const {
48-
isLoadingDocuments,
49121
documents,
50122
documentsError,
51-
refresh,
52123
approximateMatchingPercentage,
53-
isLoadingDocumentCounts,
54-
documentCountsError,
55-
} = useAsyncSample({
56-
condition: debouncedCondition,
57-
start: timeState.start,
58-
end: timeState.end,
59-
size: 100,
60-
streamDefinition: definition,
61-
});
62-
124+
approximateMatchingPercentageError,
125+
} = samplesSnapshot.context;
63126
const hasDocuments = !isEmpty(documents);
127+
const isLoadingDocumentCounts = samplesSnapshot.matches({
128+
fetching: { documentCounts: 'loading' },
129+
});
64130

65-
useEffect(() => {
66-
const subscription = timeState$.subscribe({
67-
next: ({ kind }) => {
68-
if (kind === 'override') {
69-
refresh();
70-
}
71-
},
72-
});
73-
return () => {
74-
subscription.unsubscribe();
75-
};
76-
}, [timeState$, refresh]);
77-
78-
let content;
131+
let content = null;
79132

80-
if (isIdle) {
81-
content = (
82-
<EuiEmptyPrompt
83-
icon={<AssetImage type="yourPreviewWillAppearHere" />}
84-
titleSize="s"
85-
title={
86-
<h2>
87-
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageEmpty', {
88-
defaultMessage: 'Your preview will appear here',
89-
})}
90-
</h2>
91-
}
92-
body={i18n.translate(
93-
'xpack.streams.streamDetail.preview.editPreviewMessageEmptyDescription',
94-
{
95-
defaultMessage:
96-
'Create a new child stream to see what will be routed to it based on the conditions',
97-
}
98-
)}
99-
/>
100-
);
101-
} else if (isEditingRule || isReorideringRules) {
102-
content = (
103-
<EuiEmptyPrompt
104-
icon={<AssetImage />}
105-
titleSize="s"
106-
title={
107-
<h2>
108-
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessage', {
109-
defaultMessage: 'Preview is not available while editing or reordering streams',
110-
})}
111-
</h2>
112-
}
113-
body={
114-
<>
115-
<p>
116-
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewMessageBody', {
117-
defaultMessage:
118-
'You will find here the result from the conditions you have made once you save the changes',
119-
})}
120-
</p>
121-
<p>
122-
{i18n.translate('xpack.streams.streamDetail.preview.editPreviewReorderingWarning', {
123-
defaultMessage:
124-
'Additionally, you will not be able to edit existing streams while reordering them, you should save or cancel your changes first.',
125-
})}
126-
</p>
127-
</>
128-
}
129-
/>
130-
);
131-
} else if (isCreatingNewRule && isLoadingDocuments && !hasDocuments) {
133+
if (isLoadingDocuments && !hasDocuments) {
132134
content = (
133135
<EuiEmptyPrompt
134136
icon={<EuiLoadingLogo logo="logoLogging" size="xl" />}
@@ -146,7 +148,7 @@ export function PreviewPanel() {
146148
})}
147149
/>
148150
);
149-
} else if (isCreatingNewRule && documentsError) {
151+
} else if (documentsError) {
150152
content = (
151153
<EuiEmptyPrompt
152154
icon={<AssetImage type="noResults" />}
@@ -162,7 +164,7 @@ export function PreviewPanel() {
162164
body={documentsError.message}
163165
/>
164166
);
165-
} else if (isCreatingNewRule && !hasDocuments) {
167+
} else if (!hasDocuments) {
166168
content = (
167169
<EuiEmptyPrompt
168170
icon={<AssetImage type="noResults" />}
@@ -176,41 +178,27 @@ export function PreviewPanel() {
176178
}
177179
/>
178180
);
179-
} else if (isCreatingNewRule && hasDocuments) {
181+
} else if (hasDocuments) {
180182
content = (
181183
<EuiFlexItem grow>
182184
<EuiFlexGroup direction="column">
183185
<EuiFlexItem grow={false}>
184186
<PreviewMatches
185187
approximateMatchingPercentage={approximateMatchingPercentage}
186-
error={documentCountsError}
188+
error={approximateMatchingPercentageError}
187189
isLoading={isLoadingDocumentCounts}
188190
/>
189191
</EuiFlexItem>
190-
<PreviewTable documents={documents ?? []} />
192+
<PreviewTable documents={documents} />
191193
</EuiFlexGroup>
192194
</EuiFlexItem>
193195
);
194196
}
195197

196198
return (
197199
<>
198-
<EuiFlexItem grow={false}>
199-
{isLoadingDocuments && <EuiProgress size="xs" color="accent" position="absolute" />}
200-
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" wrap>
201-
<EuiFlexGroup component="span" gutterSize="s" alignItems="center">
202-
<EuiIcon type="inspect" />
203-
<strong>
204-
{i18n.translate('xpack.streams.streamDetail.preview.header', {
205-
defaultMessage: 'Data Preview',
206-
})}
207-
</strong>
208-
</EuiFlexGroup>
209-
<StreamsAppSearchBar showDatePicker />
210-
</EuiFlexGroup>
211-
</EuiFlexItem>
212-
<EuiSpacer size="s" />
213-
<EuiFlexItem grow>{content}</EuiFlexItem>
200+
{isUpdating && <EuiProgress size="xs" color="accent" position="absolute" />}
201+
{content}
214202
</>
215203
);
216-
}
204+
};

0 commit comments

Comments
 (0)