From f215d604279cfa1e6828643ec21a4f0a6b2db403 Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed Date: Fri, 31 Oct 2025 11:22:35 +0100 Subject: [PATCH] [Streams] Refine Suggestions Before Save --- .../data_management/shared/disabled_badge.tsx | 20 ++ .../data_management/shared/index.ts | 2 + .../data_management/shared/vertical_rule.tsx | 31 +++ .../child_stream_list.tsx | 2 + .../stream_detail_routing/control_bars.tsx | 32 ++- .../idle_routing_stream_entry.tsx | 30 +-- .../stream_detail_routing/index.tsx | 4 +- .../stream_detail_routing/preview_panel.tsx | 3 +- .../create_stream_confirmation_modal.tsx | 6 +- .../review_suggestions_form.tsx | 41 +++- .../suggested_stream_panel.tsx | 202 ++++++++++++++++-- .../use_review_suggestions_form.tsx | 8 + .../routing_condition_editor.tsx | 54 ++--- .../routing_samples_state_machine.ts | 23 ++ .../stream_routing_state_machine.ts | 122 ++++++++++- .../stream_routing_state_machine/types.ts | 9 +- .../use_stream_routing.tsx | 27 ++- .../stream_name_form_row.tsx | 7 + 18 files changed, 539 insertions(+), 84 deletions(-) create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/disabled_badge.tsx create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/vertical_rule.tsx diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/disabled_badge.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/disabled_badge.tsx new file mode 100644 index 0000000000000..5de8c84a9bace --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/disabled_badge.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function DisabledBadge() { + return ( + + {i18n.translate('xpack.streams.streamDetailRouting.disabled', { + defaultMessage: 'Disabled', + })} + + ); +} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/index.ts b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/index.ts index 1be81cfe4fd32..bf90f02f24482 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/index.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/index.ts @@ -8,3 +8,5 @@ export { PreviewFlyout } from './preview_flyout'; export type { DataTableRecordWithIndex } from './preview_flyout'; export { MemoPreviewTable } from './preview_table'; export { ConditionPanel, ConditionDisplay, EditableConditionPanel } from './condition_display'; +export { VerticalRule } from './vertical_rule'; +export { DisabledBadge } from './disabled_badge'; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/vertical_rule.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/vertical_rule.tsx new file mode 100644 index 0000000000000..0680d1d237ea6 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/shared/vertical_rule.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import styled from '@emotion/styled'; +import { useEuiTheme } from '@elastic/eui'; + +export function VerticalRule() { + const { euiTheme } = useEuiTheme(); + + const CentralizedContainer = styled.div` + display: flex; + align-items: center; + padding: 0 ${euiTheme.size.xs}; + `; + + const Border = styled.div` + height: 20px; + border-right: ${euiTheme.border.thin}; + `; + + return ( + + + + ); +} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/child_stream_list.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/child_stream_list.tsx index dd7fd52d6ae09..45bb60a644a2b 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/child_stream_list.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/child_stream_list.tsx @@ -68,6 +68,7 @@ export function ChildStreamList({ availableStreams }: { availableStreams: string previewSuggestion, acceptSuggestion, rejectSuggestion, + updateSuggestion, } = useReviewSuggestionsForm(); const { currentRuleId, definition, routing } = routingSnapshot.context; @@ -233,6 +234,7 @@ export function ChildStreamList({ availableStreams }: { availableStreams: string rejectSuggestion={rejectSuggestion} resetForm={resetForm} suggestions={suggestions} + updateSuggestion={updateSuggestion} /> ) ) : null} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/control_bars.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/control_bars.tsx index 0f8efe743cde0..75e454a6f6cd3 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/control_bars.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/control_bars.tsx @@ -79,6 +79,34 @@ export const EditRoutingRuleControls = ({ ); }; +export const EditSuggestedRuleControls = ({ onSave }: { onSave?: () => void }) => { + const routingSnapshot = useStreamsRoutingSelector((snapshot) => snapshot); + const { cancelChanges, saveEditedSuggestion } = useStreamRoutingEvents(); + + const canSave = routingSnapshot.can({ type: 'suggestion.saveSuggestion' }); + const hasPrivileges = routingSnapshot.context.definition.privileges.manage; + + const handleUpdate = () => { + if (onSave) { + onSave(); + } + saveEditedSuggestion(); + }; + + return ( + + + + + + + + + + + ); +}; + const RemoveButton = ({ isDisabled, onDelete, @@ -125,8 +153,8 @@ const SaveButton = (props: EuiButtonPropsForButton) => ( const UpdateButton = (props: EuiButtonPropsForButton) => ( - {i18n.translate('xpack.streams.streamDetailRouting.change', { - defaultMessage: 'Change routing', + {i18n.translate('xpack.streams.streamDetailRouting.update', { + defaultMessage: 'Update', })} ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/idle_routing_stream_entry.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/idle_routing_stream_entry.tsx index 8a5d1811071d3..5602dc53a85ba 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/idle_routing_stream_entry.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/idle_routing_stream_entry.tsx @@ -24,30 +24,10 @@ import { i18n } from '@kbn/i18n'; import { isDescendantOf, isRoutingEnabled } from '@kbn/streams-schema'; import { css } from '@emotion/css'; import { css as cssReact } from '@emotion/react'; -import styled from '@emotion/styled'; import { useStreamsAppRouter } from '../../../hooks/use_streams_app_router'; -import { ConditionPanel } from '../shared'; +import { ConditionPanel, VerticalRule } from '../shared'; import type { RoutingDefinitionWithUIAttributes } from './types'; - -function VerticalRule() { - const { euiTheme } = useEuiTheme(); - const CentralizedContainer = styled.div` - display: flex; - align-items: center; - padding: 0 ${euiTheme.size.xs}; - `; - - const Border = styled.div` - height: 20px; - border-right: ${euiTheme.border.thin}; - `; - - return ( - - - - ); -} +import { DisabledBadge } from '../shared'; export function IdleRoutingStreamEntry({ availableStreams, @@ -143,11 +123,7 @@ export function IdleRoutingStreamEntry({ > {!isRoutingEnabled(routingRule.status) && ( <> - - {i18n.translate('xpack.streams.streamDetailRouting.disabled', { - defaultMessage: 'Disabled', - })} - + )} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/index.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/index.tsx index 5ba3aa97655f8..03e83b1c2de1b 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/index.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/index.tsx @@ -11,7 +11,6 @@ import { EuiFlexItem, EuiPanel, EuiResizableContainer, - useIsWithinBreakpoints, } from '@elastic/eui'; import { css } from '@emotion/css'; import type { Streams } from '@kbn/streams-schema'; @@ -107,7 +106,6 @@ export function StreamDetailRoutingImpl() { }); const availableStreams = streamsListFetch.value?.streams.map((stream) => stream.name) ?? []; - const isVerticalLayout = useIsWithinBreakpoints(['xs', 's']); return ( - + {(EuiResizablePanel, EuiResizableButton) => ( <> ; } else if ( routingSnapshot.matches({ ready: 'creatingNewRule' }) || - routingSnapshot.matches({ ready: 'reviewSuggestedRule' }) + routingSnapshot.matches({ ready: 'reviewSuggestedRule' }) || + routingSnapshot.matches({ ready: 'editingSuggestedRule' }) ) { content = ; } diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/create_stream_confirmation_modal.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/create_stream_confirmation_modal.tsx index 161e45b900638..9b2f8a465b23d 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/create_stream_confirmation_modal.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/create_stream_confirmation_modal.tsx @@ -84,7 +84,11 @@ export function CreateStreamConfirmationModal({ forkStream({ destination: partition.name, where: partition.condition, - }).then(() => onSuccess()) + }).then((result) => { + if (result.success) { + onSuccess(); + } + }) } fill > diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/review_suggestions_form.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/review_suggestions_form.tsx index 9f429505a9476..b3f5b97ab2d3d 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/review_suggestions_form.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/review_suggestions_form.tsx @@ -20,6 +20,7 @@ import type { import { useStreamSamplesSelector, useStreamsRoutingSelector, + useStreamRoutingEvents, } from '../state_management/stream_routing_state_machine'; import { CreateStreamConfirmationModal } from './create_stream_confirmation_modal'; import type { AIFeatures } from '../../../../hooks/use_ai_features'; @@ -32,6 +33,7 @@ export interface ReviewSuggestionsFormProps | 'previewSuggestion' | 'acceptSuggestion' | 'rejectSuggestion' + | 'updateSuggestion' > { suggestions: PartitionSuggestion[]; onRegenerate: (connectorId: string) => void; @@ -48,11 +50,20 @@ export function ReviewSuggestionsForm({ previewSuggestion, acceptSuggestion, rejectSuggestion, + updateSuggestion, onRegenerate, }: ReviewSuggestionsFormProps) { const ruleUnderReview = useStreamsRoutingSelector((snapshot) => snapshot.matches({ ready: 'reviewSuggestedRule' }) ? snapshot.context.suggestedRuleId : null ); + const editingSuggestion = useStreamsRoutingSelector((snapshot) => + snapshot.matches({ ready: 'editingSuggestedRule' }) ? snapshot.context.editedSuggestion : null + ); + + // For the confirmation modal, use edited suggestion if available, otherwise find by name + const partitionForModal = + editingSuggestion || suggestions.find(({ name }) => name === ruleUnderReview)!; + const selectedPreviewName = useStreamSamplesSelector( ({ context }) => context.selectedPreview && @@ -60,17 +71,34 @@ export function ReviewSuggestionsForm({ context.selectedPreview.name ); + const { editSuggestion } = useStreamRoutingEvents(); + const routingSnapshot = useStreamsRoutingSelector((snapshot) => snapshot); + + const handleSave = () => { + const currentEditingIndex = routingSnapshot.context.editingSuggestionIndex; + const currentEditedSuggestion = routingSnapshot.context.editedSuggestion; + + if (currentEditingIndex !== null && currentEditedSuggestion) { + updateSuggestion(currentEditingIndex, currentEditedSuggestion); + } + }; + return ( <> - {ruleUnderReview && ( + {ruleUnderReview && partitionForModal && ( name === ruleUnderReview)!} - onSuccess={() => - acceptSuggestion(suggestions.findIndex(({ name }) => name === ruleUnderReview)!) - } + partition={partitionForModal} + onSuccess={() => { + acceptSuggestion( + editingSuggestion + ? routingSnapshot.context.editingSuggestionIndex! + : suggestions.findIndex(({ name }) => name === ruleUnderReview)! + ); + }} /> )} previewSuggestion(index, toggle)} onDismiss={() => rejectSuggestion(index, selectedPreviewName === partition.name)} + onEdit={editSuggestion} + onSave={handleSave} /> diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/suggested_stream_panel.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/suggested_stream_panel.tsx index f091118ebf616..e379492408df4 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/suggested_stream_panel.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/suggested_stream_panel.tsx @@ -15,45 +15,189 @@ import { EuiText, EuiIcon, EuiLoadingSpinner, + EuiButtonIcon, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import type { Streams } from '@kbn/streams-schema'; +import { type Streams } from '@kbn/streams-schema'; +import { isCondition } from '@kbn/streamlang'; import type { PartitionSuggestion } from './use_review_suggestions_form'; import { useMatchRate } from './use_match_rate'; import { useStreamRoutingEvents, + useStreamsRoutingSelector, useStreamSamplesSelector, } from '../state_management/stream_routing_state_machine/use_stream_routing'; import { SelectablePanel } from './selectable_panel'; -import { ConditionPanel } from '../../shared'; +import { ConditionPanel, VerticalRule } from '../../shared'; +import { StreamNameFormRow } from '../stream_name_form_row'; +import { RoutingConditionEditor } from '../routing_condition_editor'; +import { processCondition } from '../utils'; export function SuggestedStreamPanel({ definition, partition, onDismiss, onPreview, + index, + onEdit, + onSave, }: { definition: Streams.WiredStream.GetResponse; partition: PartitionSuggestion; onDismiss(): void; onPreview(toggle: boolean): void; + index: number; + onEdit(index: number, suggestion: PartitionSuggestion): void; + onSave?: () => void; }) { - const matchRate = useMatchRate(definition, partition); + const routingSnapshot = useStreamsRoutingSelector((snapshot) => snapshot); + const { + changeSuggestionName, + changeSuggestionCondition, + reviewSuggestedRule, + cancelChanges, + saveEditedSuggestion, + } = useStreamRoutingEvents(); + + const [nameError, setNameError] = React.useState(undefined); + const [conditionError, setConditionError] = React.useState(undefined); + + const editedSuggestion = routingSnapshot.context.editedSuggestion; + const isEditing = + routingSnapshot.matches({ ready: 'editingSuggestedRule' }) && + routingSnapshot.context.editingSuggestionIndex === index; + + // Use edited suggestion when editing, otherwise use original partition + const currentSuggestion = isEditing && editedSuggestion ? editedSuggestion : partition; + const matchRate = useMatchRate(definition, currentSuggestion); + const selectedPreview = useStreamSamplesSelector((snapshot) => snapshot.context.selectedPreview); const isSelected = Boolean( selectedPreview && selectedPreview.type === 'suggestion' && - selectedPreview.name === partition.name + selectedPreview.name === currentSuggestion.name ); - const { reviewSuggestedRule } = useStreamRoutingEvents(); + + const handleNameChange = (name: string) => { + if (!isEditing) return; + const isDuplicateName = routingSnapshot.context.routing.some((r) => r.destination === name); + + if (isDuplicateName) { + setNameError( + i18n.translate('xpack.streams.streamDetailRouting.nameConflictError', { + defaultMessage: 'A stream with this name already exists', + }) + ); + } else { + setNameError(undefined); + } + + changeSuggestionName(name); + }; + + const handleConditionChange = (condition: any) => { + if (!isEditing) return; + const processedCondition = processCondition(condition); + const isProcessedCondition = processedCondition ? isCondition(processedCondition) : true; + + if (!isProcessedCondition) { + setConditionError( + i18n.translate('xpack.streams.streamDetailRouting.conditionRequiredError', { + defaultMessage: 'Condition is required', + }) + ); + } else { + setConditionError(undefined); + } + + changeSuggestionCondition(condition); + }; + + if (isEditing) { + return ( + + + + {}} + isSuggestionRouting={true} + /> + + + + {i18n.translate('xpack.streams.streamDetailRouting.cancel', { + defaultMessage: 'Cancel', + })} + + + + + + { + if (onSave) { + onSave(); + } + saveEditedSuggestion(); + }} + > + {i18n.translate('xpack.streams.streamDetailRouting.update', { + defaultMessage: 'Update', + })} + + + + { + if (isEditing && onSave) { + onSave(); + } + reviewSuggestedRule(currentSuggestion.name || partition.name); + }} + fill + > + {i18n.translate( + 'xpack.streams.streamDetailRouting.suggestedStreamPanel.accept', + { + defaultMessage: 'Update & Accept', + } + )} + + + + + + + + ); + } return ( - + -

{partition.name}

+

{currentSuggestion.name}

{matchRate.loading ? ( @@ -62,19 +206,26 @@ export function SuggestedStreamPanel({
) : matchRate.value !== undefined ? ( <> - - - - - - {matchRate.value} - - + + + {matchRate.value} + ) : null} + + + + onEdit(index, currentSuggestion)} + aria-label={i18n.translate('xpack.streams.streamDetailRouting.edit', { + defaultMessage: 'Edit', + })} + data-test-subj={`suggestionEditButton-${currentSuggestion.name}`} + /> - + @@ -83,6 +234,12 @@ export function SuggestedStreamPanel({ isSelected={isSelected} size="s" onClick={() => onPreview(!isSelected)} + aria-label={i18n.translate( + 'xpack.streams.streamDetailRouting.suggestedStreamPanel.preview', + { + defaultMessage: 'Preview', + } + )} > {i18n.translate('xpack.streams.streamDetailRouting.suggestedStreamPanel.preview', { defaultMessage: 'Preview', @@ -92,7 +249,16 @@ export function SuggestedStreamPanel({ - + {i18n.translate('xpack.streams.streamDetailRouting.suggestedStreamPanel.dismiss', { defaultMessage: 'Reject', })} @@ -102,7 +268,7 @@ export function SuggestedStreamPanel({ reviewSuggestedRule(partition.name)} + onClick={() => reviewSuggestedRule(currentSuggestion.name || partition.name)} fill > {i18n.translate('xpack.streams.streamDetailRouting.suggestedStreamPanel.accept', { diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/use_review_suggestions_form.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/use_review_suggestions_form.tsx index 2f5856df4c67a..fec528063e58b 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/use_review_suggestions_form.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/review_suggestions_form/use_review_suggestions_form.tsx @@ -90,6 +90,13 @@ export function useReviewSuggestionsForm() { } }; + const updateSuggestion = (index: number, updates: Partial) => { + if (!suggestions) return; + const updatedSuggestion = { ...suggestions[index], ...updates }; + const updatedSuggestions = suggestions.toSpliced(index, 1, updatedSuggestion); + setSuggestions(updatedSuggestions); + }; + const resetPreview = () => { streamsRoutingActorRef.send({ type: 'suggestion.preview', @@ -118,6 +125,7 @@ export function useReviewSuggestionsForm() { isLoadingSuggestions, fetchSuggestions, resetForm, + updateSuggestion, previewSuggestion: (index: number, toggle?: boolean) => { if (suggestions) { const partition = suggestions[index]; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/routing_condition_editor.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/routing_condition_editor.tsx index fca3d3652bb25..ef40a16c74a1f 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/routing_condition_editor.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/routing_condition_editor.tsx @@ -18,38 +18,44 @@ type RoutingConditionChangeParams = Omit; export type RoutingConditionEditorProps = Omit & { onStatusChange: (params: RoutingConditionChangeParams['status']) => void; + isSuggestionRouting?: boolean; }; export function RoutingConditionEditor(props: RoutingConditionEditorProps) { - const isEnabled = isRoutingEnabled(props.status); + const { isSuggestionRouting, status } = props; + const isEnabled = isRoutingEnabled(status); const fieldSuggestions = useRoutingFieldSuggestions(); return ( - - {i18n.translate('xpack.streams.routing.conditionEditor.title', { - defaultMessage: 'Status', - })} - + {i18n.translate('xpack.streams.routing.conditionEditor.title', { + defaultMessage: 'Status', })} - /> - - } - > - props.onStatusChange(event.target.checked ? 'enabled' : 'disabled')} - /> - + + + } + > + + props.onStatusChange(event.target.checked ? 'enabled' : 'disabled') + } + /> + + )} ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/routing_samples_state_machine.ts b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/routing_samples_state_machine.ts index 9d181c6f65e4c..a883b05503be0 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/routing_samples_state_machine.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/routing_samples_state_machine.ts @@ -57,6 +57,10 @@ export type RoutingSamplesEvent = | { type: 'routingSamples.setSelectedPreview'; preview: RoutingSamplesContext['selectedPreview']; + } + | { + type: 'routingSamples.updatePreviewName'; + name: string; }; export interface SearchParams extends RoutingSamplesInput { @@ -109,6 +113,17 @@ export const routingSamplesMachine = setup({ selectedPreview: params.preview, }) ), + updatePreviewName: assign(({ context }, params: { name: string }) => { + if (!context.selectedPreview || context.selectedPreview.type !== 'suggestion') { + return {}; + } + return { + selectedPreview: { + ...context.selectedPreview, + name: params.name, + }, + }; + }), }, delays: { conditionUpdateDebounceTime: 500, @@ -165,6 +180,14 @@ export const routingSamplesMachine = setup({ }, ], }, + 'routingSamples.updatePreviewName': { + actions: [ + { + type: 'updatePreviewName', + params: ({ event }) => event, + }, + ], + }, }, states: { debouncingCondition: { diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/stream_routing_state_machine.ts b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/stream_routing_state_machine.ts index 877da5376e710..15ce8fe2d983e 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/stream_routing_state_machine.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/stream_routing_state_machine.ts @@ -31,6 +31,7 @@ import { createRoutingSamplesMachineImplementations, routingSamplesMachine, } from './routing_samples_state_machine'; +import type { PartitionSuggestion } from '../../review_suggestions_form/use_review_suggestions_form'; export type StreamRoutingActorRef = ActorRefFrom; @@ -105,6 +106,23 @@ export const streamRoutingMachine = setup({ resetSuggestedRuleId: assign(() => ({ suggestedRuleId: null, })), + storeEditingSuggestion: assign( + (_, params: { index: number; suggestion: PartitionSuggestion }) => ({ + editingSuggestionIndex: params.index, + editedSuggestion: params.suggestion, + }) + ), + updateEditedSuggestion: assign( + ({ context }, params: { updates: Partial }) => ({ + editedSuggestion: context.editedSuggestion + ? { ...context.editedSuggestion, ...params.updates } + : null, + }) + ), + clearEditingSuggestion: assign(() => ({ + editingSuggestionIndex: null, + editedSuggestion: null, + })), }, guards: { canForkStream: and(['hasManagePrivileges', 'isValidRouting']), @@ -118,7 +136,7 @@ export const streamRoutingMachine = setup({ isSchema(routingDefinitionListSchema, context.routing.map(routingConverter.toAPIDefinition)), }, }).createMachine({ - /** @xstate-layout N4IgpgJg5mDOIC5QCcD2BXALgSwHZQGVNkwBDAWwDo9sdSAbbALzygGIBtABgF1FQADqli1sqXPxAAPRACZZATkoA2AIwAWABwL1sgKybZG1bIA0IAJ6IAzKr2V1XLgs3WA7Jq7uFb5QF8-czQsViISCkpwiAs2WGIyKhIAYzBsADdIbj4kECERHHFJGQRVBS5ZSh9y8uU9PWtrPTdzKwRZdWVKTWVZQz0FOx7ZNz0AoIwcfDCEyLJo6gh6MDZgyagAJXQlyiTwzDAsyTzRQpzi9V1KPWUFAe1Pa2UevRbEVVLOrmv39W9DdTGIFWoXiESiFgWSxWE1Ym22kFohxyxwKEjONk09lkQ00vgMei4yk0rxKhK4lDcdzc1gGqlqdkBwKmoMScwh2EWyyZGy2YFmqGQEDAyCRgmEJzRoGKAFpVNYuuo3HYdKpCQplE5iZY3rIuOpKIpugomnpdN0AYEgTDmeFWaR5rsyGsAHJgADucL5SQAFqR8KxoSF8J6dn6UvRRblxaiim8vJouho9HLNJjk8p1CSbhUCXTGj51WprIzrYQWbN7RDHaQXe6Qz6-VAA9z6778AdeEdo2JJdI3o8lNZ1Ap3Lj2t0ta0iRVbCPDO9rJ4TCWg2XbRWHXtWK6Pbydm2m-hA2sQwjMJGUT3YyUMzm9L1hh03PpVCTKaoHNcDI0NNiRyu1mmME2R2Ld8B3VtG2bUsQwAMwFABrC9u1OKU41+FRHBpW5qWUIdM21Uk9QpDV500Ewbn-S1uSAu1Nydbc6z3eDkAQgMIHEPk8DSVAEL5FiENogBBJJMAFZD8ivdEEGpD8enKNxFMcZ8BjfYdKBpJpFEJG4mlUACQXXcFQIY8CmO2ASA2FNBkEoAR6BrFiqAE4TRPEztkRQ3tilVHwHGpLRVBGWx3jMQjtHsJofFqU03EVTQLXGVdaI3CEz1hPcG39I8W0yvYO2yMVJNQvsSkaTolQ8Wd3gSkcSVC-UPF1OpFRcPVi2o0sUuM9Lg0yg9oNXeswzACMPKKiVr0MeVXD1dV3D0lx6tNckOgUXUjC+dp5AMm0Zh6iBRD67YssPdhcpOg8Cq7YrvLeNxnEoPUCWxZQ4sVRd6rehMLmfDQuA8e9Rk65LywOo6eUuqCcpgvczwkybpLsIwHFUWbzX0Wd6pChwCwUh7XCaXa132kDeshr0Bphoa4cO89VEKqNbuvIKOieoldB6X5+n0erfiUfouA0dx3jnNxie6sm6YyqHsvO2HthIchUAyBGYyRoLOkcbp3Cedb1HqPn2hUP8Ae2g2EolsGpYhyC5ePGW+VgUhVfGpnEbQkoPgNXodF6AxdEeerfHJVVuiFgHbhMcWQcA63K0ocmQyVlXHbYDjcC43AeL4yghSWfZXLEkU3cvErikaNwunI-n4rUIKXkIkwLie75hcpf4raMm2Tz3FO0jT6yBTshzMCcvPRrAQuWRE4u1akz2TEUfy6gDnRasnN56i12bCXkWRGkMLvSYTpO93QAQIBrNOM6znO+Qv2BhUwIv3MZsu7oQSuDSCw+LnKRUFxg76EoHYU0Pg6RaHXsfYCp9pbHQfpfa+CC2BD1svZRyAoqCP2fq-Eu78vIs3kAmciGhfB-SGF9IcVxGh6QzN8PUMC6IQhIAKIUyBHawH5IKYUg1e6KzAGw4U89y79h6D7awAMCRaCHAoEkzVyRxV-v0OUdJ9Kx0MifeYrCeEcIQVwnR7C+GO1DLgcMIjP4Eh+joY0NIYqmkbq0XUQUnoAIaLoAGHQmGpW4UY-RvjeHU34U7F211PLMyRl8ckTxhw6BuAlRQyh5ENCrpRcoi40Z4UYRovasDtGCN0ZwygF8r5x1tOnTi1Bs68UQU-ZAL8Z5uXwTdD2pVA5XHXs+A+9Rky-HkUYTopR1ClEeIqL43jjKGMCRTLhJTkEkwoKg5ANkR6YOQNggQdSGm2lnm-Fp6tF6qnJLqDwHclRAMIvIR6AwMkJSVO4AIlpcCoCFPAHINEWT7IXqVWUj07nKmGWqDUr4m4t3oWUDwPhXDWHaN4mgdBGAsHwF80RbQ6RdCeEYIKQVPDDmsMkioSYyg3LRnmYGSUylaNaBNA5Py1AKiVP0QFNxgUkkXFXWcjgfAjHvOMnJCzmGQjACiz++glBqjuG1R4zwSQJQTD0P6DR1TkRjhSzReSqxgSgBBXkIrrxKnJF8LQwx9CeF6CSe8H5yIjluPoYZosJkgWrLWXcsszp6qRv7BwQxKTbVxORWVGgHANB+KmLa6i1W5MFc6xirr+KIVYB6z2IwExGsMM+Aw5RN4IG0PKOoeELiY2-AoR1cDba6vCa04oNwq4G3Ws+fM1I5FN01v5bmdqPH+H5ZLMtwT9zQygEm0qdJ5AaV9QYXwBtxzLRHBpGxdR1QE1VVaUG3de0mP7o7IdxQRgVCqvXOVcU9TY1VJUd6e94wDFLfMM+2w5nBO3YgOK9g60msbTSYOjwVBcExPofQ1JTSaGvSwgpfiZmPoQLpCkcVfC-qFsMfFTd5oGmGcaHl7hhyyGAwEvRMycOJsrbSnyki80UUUC4GkQ4kmXLId6-ouIyjOEkeSldlKNX4f8fe9V5AINxXlEpWDvLo6IacScroZR3AwphZSU0jy-BAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QCcD2BXALgSwHZQGVNkwBDAWwDo9sdSAbbALzygGIBtABgF1FQADqli1sqXPxAAPRAFYATABoQAT0TyuANk2UuATlld9hzQGZNAdgC+V5WiysiJCpWcQVbWMTJUSAYzBsADdIbj4kECERHHFJGQQARllzSj0EgA5tZPSuC1N0i2U1BHMLABZKWU10hU0y-IT5cxs7DBx8Jx9XMnc2e3bCCgF6OEpYMEwAEVQ-dHIwXEwAWVJMPwALADFsekwwZDDJKNFYiPiEsrLZVLqasvT8h9MlVURTC009SnT6pvT-ixpBItED9RzeFxuDywdBQGBeMS4SgCEhBbBgADuhwixxiEjOiASeh0ZVyslkaT0XCeZUKrwQGnkFkoFnJ8k0sgsCS48gMmhBYI6EN8PQ8gqgACV0CNumjMQRYfC9hBsYJhCd8aBzsTKKTWRSidTTKZaUV1Jp2al6oDTFx8rl0gK2uDnCLSL0YXC4HjKJBaKrIuq8XFCTq9eTKUaTXTis8tJRPgorgZzPUnQ4ha7uu6VNQICM+s78FKZX5nHsA7jESHEpYdBYtEDOfIKWbEtyvrSLLzZJcElyyumBp1IaK8wXxSWwL6IP7eEcg9WCQz4+GDVSaTG5FwEqlZOkNJ9SeyLUOXV0oeOwIWM5LpdOSKhkBB9pXF6ctYgytUE5od1d5DKBJ+1kNteX+XQyl5fsCnkJIz0zC8xzLMgBgAOUxKdKA2Uh8FYG8BiwvxcICeg32iJdPwZTldz-eRMhqdkDVA+kLm-XRynZKkG0+TRgVsUEi0IYVs3cbDy1YDCMSI9ZcKgfDJ3vbDZPwMByI1Gt+2AllG2A5tW1Y2pUhqBJTGST5+z0awBPFEc3TElDVkkzClJwvD8AI1gsL9TB1ODZdD11Ml1yjU1WP+a4+IsB5qT0NJTxsoS7NE3NHPQlzSxU+SPMUmUADMnwAaz8yjpFDDRUmeJlzBqUwklMNsEiPFk9EAuq4IeUz+US29ksvNLnOkpSCuQQr8IgcRpzwIJUEK6cRsKuyAEE-EwJ8So-MrqK5BMeQY2pmMavj0iC5I0kBXJ2UdHrhxE-qJPwKSsIW-D9jQZBkXoVYRqoBbltW9b5xxd9NS2jQLl0LhZCSVrWQKBrWLoyoMis-JSSg7rWl6u6xx8rzXKyhShKI8s1KBtUKM284qsoEDyjq+p93ZRqyji2mtGAg8ufkFsEOErNLzx4sCbkonbyIkiwDI8nA0p0HqZ52nWXpi4zIPTQ2zqa5jDqECLiMeQ+b63HZ0IkX3PYXLpzcmANvl9QakoE0zHSNImTgrlGvyUwnaaxouueKp+Kx26BZN0Rhcy0WcuJpSfLtzTjXkJXOXqVWmY1+kPh0PQaihxoN0xwTsbDnMZwju8o4tzzI+neOEnCCmNICoCgv1SNN0a+iTq4ICW37Ulc9do2cbLoXK+twmY-FpSSHIVAQgT5dORO6He-Alte70BHikaJkWRNd51YKP9TBH0uxPHmTo8t2OZVgUhF5lqsqbkaLkfX3PN9ZnfCXML4nj5AMK7d4tJz5ITHqbfGMo54L2gdeCauApq4BmnNSgL4Rh7H+mtA4z8QaJyginFWjN1ZtkyMnFscUNBpHaqycBo5IEVywrAtEtc2BvSfJ9b6T4qAYImGAbBgNG6y2blRLkq8oIGGJByA8ZRGoZC4KkfQrVobmChqzeh9lcxXyUugAQEAnJsMQcg1B049HjGQJgQRuDhEv3tokMyyc6ZpxIczekBgKjJCqsBDIyReyaJSuXM2Mo9EGOCdeDhH1hjcOQFQcx+wrHChWjgpeYj+yUDgrSbecVWochYrvJkzIqhGBAQ8Yk1kQ7ngYWJR8z59jwNgN0J8L5kBi3CU0upNiFxyxrIYH22h-xJmAqyMCxodBMjrBoKG5hyQBMvLUlpDSOmLOnu04iuBSKpK2vrHQvZ6i0h5tSS4egwLu10HBVGFoKRzLHAs+ptdGl3Naas+BYxH5k1sfg5cDZk7Q32eULx28wKGGZL8BsmQoJ9huWXJ5SzQmGP5j4NgxjqAoNmmYgQFjEmumSUI7pojtmkl2ZcaMhyfiszAkBZkeRMhmX-FZPQ0KalgGafciejT4WhyRZErhmAfqUHiZY6xWz4g5F3PuY0udcgciZCc+kgEki0wpKSH47w7TNBulUrRsp0QYgVF6LwkBmFgDlBiNpryFoisQGYH2toNAWEBFcbk3YwKZEUWZLJTM4rklkEy3MqJdX6qVEa2eJrdXmtrthSW0tPk9OXI0B4qRuyWCZLS0h8qLQVD0NaBQ9FiT7j9Tq+UipvQhvykVcak1UWmMoH9JJAMunAzjVRBNPsrLsm7NFF2bjiiFMUQMoC0r0imUHJqxC1T-VhuLQa5Uz0K0eR5dEvlPDa1FWFXg5tW1k1O3KLnaoGhaR1DAh8Huw6MhciMICUyhbx5BtLRAbyUCPKeiVIiZSckwBoQoB8-F-kqJARJEyO0PNLhVFZnI+kh8dD-A6lBQwBoz5jsRROoJjgS2GofXHJ97AX3ejfTbMAABhcQUDxBWoQMeWmacahmHJA6iDsYTS0VyLSBR1Je43uw3ejDj6K41wnu+1S5H6g6CJAaV2w68gGVjIBHQSc6IwXMr6pDxtGHDnQ7OrDfGrZRo2VLcjDYKi0lMK1UyO53gtjbMaK4lQ-wWlZIBYdDxOMV245pmU49PAabfQ-EIbnEQGd7rqPIpnbSmW7Pkt44HdBaw+LnC4XIbACVwKgF88AIi2WFL+0q8QAC0mdij5YCTQOgjAWD4Gy6-CjLxijDqdjzVqTqqg8zyH6yr9iLjXDXB3azW4SiK2HYBXIPIrImELdgfMYB2s1notDFkUzqiXRBX1+1PcoJQy5ESFGo7Knju1QNR6GUptNoJecbQPseJNgi3KgpKQaNJGqNyV2cFC0HagE9c22UoDTfjayftdori90cVUSLtZ96UJ+NFFGlhXsPXe0d1do1WA-aojzRW0UHW9ipIe3IR0mpOwPFyBQ9G0gufCSj7Z2824RkNJ3ViPJFEfCSJkC4Fl2Rk9eTbZHJ2-1bWeBUQC9E9IyMAn1i4iaKRaGzqSKo7wOeRpYfAin8Rs27mioYKyTqk5i62+zRoFIqp2flwJzlSuec5cQKrlkedNeqOeGL40Psplxj-HcIumWL6TtZc89lyuvzkl-IMhVIFTk8lpoBaG-cdzXpU6PZl3ulmwoq+bqr2RaZNT2dSaGoO2q7NUf2eiHbEO7eQ9qpP7KBX6IRXZP3iQeSieSA2JIs2UyUt5Oc20XJ6Lq+c7Hz3Ra9UabLcdpuvP4hXEin+TrweRkZpyCyfcT3njPCpMX4uXKUMBuncGzDMCp1muT6Pi3CA+mB+n0BEP8roqdjyJvDQMVt6Fq34Pmdw-EdjUPyIsfluzJKvbk1CfOCY9OrbNQwdeEdAwY3NzYfWvD1KjOqGjMyTkcoKzYCJxMVAwNeHIHbdfLVQJW9IfXfOubDWve4fpfsQEfcYnJIb8KzRvJVaocpIwd4RlJLIAA */ id: 'routingStream', context: ({ input }) => ({ currentRuleId: null, @@ -126,6 +144,8 @@ export const streamRoutingMachine = setup({ initialRouting: [], routing: [], suggestedRuleId: null, + editingSuggestionIndex: null, + editedSuggestion: null, }), initial: 'initializing', states: { @@ -173,6 +193,24 @@ export const streamRoutingMachine = setup({ target: '#ready.reviewSuggestedRule', actions: [{ type: 'storeSuggestedRuleId', params: ({ event }) => event }], }, + 'suggestion.edit': { + target: '#ready.editingSuggestedRule', + actions: enqueueActions(({ enqueue, event }) => { + enqueue({ type: 'storeEditingSuggestion', params: event }); + + // Set the preview for the suggestion being edited + enqueue.sendTo('routingSamplesMachine', { + type: 'routingSamples.setSelectedPreview', + preview: { type: 'suggestion', name: event.suggestion.name, index: event.index }, + }); + + // Update condition for preview + enqueue.sendTo('routingSamplesMachine', { + type: 'routingSamples.updateCondition', + condition: event.suggestion.condition, + }); + }), + }, }, invoke: { id: 'routingSamplesMachine', @@ -472,6 +510,88 @@ export const streamRoutingMachine = setup({ }, }, }, + editingSuggestedRule: { + id: 'editingSuggestedRule', + initial: 'editing', + exit: [{ type: 'clearEditingSuggestion' }], + states: { + editing: { + on: { + 'suggestion.changeName': { + actions: enqueueActions(({ context, enqueue, event }) => { + enqueue({ + type: 'updateEditedSuggestion', + params: { updates: { name: event.name } }, + }); + + // Update the preview name (without triggering refetch) + enqueue.sendTo('routingSamplesMachine', { + type: 'routingSamples.updatePreviewName', + name: event.name, + }); + }), + }, + 'suggestion.changeCondition': { + actions: enqueueActions(({ enqueue, event }) => { + enqueue({ + type: 'updateEditedSuggestion', + params: { updates: { condition: event.condition } }, + }); + + // Update the condition for preview (triggers refetch) + enqueue.sendTo('routingSamplesMachine', { + type: 'routingSamples.updateCondition', + condition: event.condition, + }); + }), + }, + 'routingRule.change': { + actions: enqueueActions(({ enqueue, event }) => { + if (event.routingRule.where) { + enqueue({ + type: 'updateEditedSuggestion', + params: { updates: { condition: event.routingRule.where } }, + }); + + enqueue.sendTo('routingSamplesMachine', { + type: 'routingSamples.updateCondition', + condition: event.routingRule.where, + }); + } + }), + }, + 'routingRule.cancel': { + target: '#ready.idle', + actions: [ + { type: 'clearEditingSuggestion' }, + sendTo('routingSamplesMachine', { + type: 'routingSamples.setSelectedPreview', + preview: undefined, + }), + sendTo('routingSamplesMachine', { + type: 'routingSamples.updateCondition', + condition: undefined, + }), + ], + }, + 'suggestion.saveSuggestion': { + target: '#ready.idle', + actions: [ + { type: 'clearEditingSuggestion' }, + sendTo('routingSamplesMachine', { + type: 'routingSamples.setSelectedPreview', + preview: undefined, + }), + sendTo('routingSamplesMachine', { + type: 'routingSamples.updateCondition', + condition: undefined, + }), + ], + }, + }, + }, + }, + }, }, }, }, diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/types.ts b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/types.ts index cd67eeb45c85c..24ce300ca24e8 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/types.ts +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/types.ts @@ -16,6 +16,7 @@ import type { StreamsTelemetryClient } from '../../../../../telemetry/client'; import type { RoutingDefinitionWithUIAttributes } from '../../types'; import type { DocumentMatchFilterOptions } from '.'; import type { RoutingSamplesContext } from './routing_samples_state_machine'; +import type { PartitionSuggestion } from '../../review_suggestions_form/use_review_suggestions_form'; export interface StreamRoutingServiceDependencies { forkSuccessNofitier: (streamName: string) => void; @@ -37,6 +38,8 @@ export interface StreamRoutingContext { initialRouting: RoutingDefinitionWithUIAttributes[]; routing: RoutingDefinitionWithUIAttributes[]; suggestedRuleId: string | null; + editingSuggestionIndex: number | null; + editedSuggestion: PartitionSuggestion | null; } export type StreamRoutingEvent = @@ -58,4 +61,8 @@ export type StreamRoutingEvent = index: number; toggle?: boolean; } - | { type: 'routingRule.reviewSuggested'; id: string }; + | { type: 'routingRule.reviewSuggested'; id: string } + | { type: 'suggestion.edit'; index: number; suggestion: PartitionSuggestion } + | { type: 'suggestion.changeName'; name: string } + | { type: 'suggestion.changeCondition'; condition: Condition } + | { type: 'suggestion.saveSuggestion' }; diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/use_stream_routing.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/use_stream_routing.tsx index a7ad303e6b879..1b840d1175491 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/use_stream_routing.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/use_stream_routing.tsx @@ -10,6 +10,7 @@ import { createActorContext, useSelector } from '@xstate5/react'; import { createConsoleInspector } from '@kbn/xstate-utils'; import { waitFor } from 'xstate5'; import type { RoutingDefinition } from '@kbn/streams-schema'; +import type { Condition } from '@kbn/streamlang'; import { streamRoutingMachine, createStreamRoutingMachineImplementations, @@ -21,6 +22,7 @@ import type { RoutingSamplesActorRef, RoutingSamplesActorSnapshot, } from './routing_samples_state_machine'; +import type { PartitionSuggestion } from '../../review_suggestions_form/use_review_suggestions_form'; const consoleInspector = createConsoleInspector(); @@ -57,7 +59,18 @@ export const useStreamRoutingEvents = () => { }, forkStream: async (routingRule?: RoutingDefinition) => { service.send({ type: 'routingRule.fork', routingRule }); - await waitFor(service, (snapshot) => snapshot.matches({ ready: 'idle' })); + + await waitFor( + service, + (snapshot) => + snapshot.matches({ ready: 'idle' }) || + snapshot.matches({ ready: { reviewSuggestedRule: 'reviewing' } }) + ); + + const finalSnapshot = service.getSnapshot(); + return { + success: finalSnapshot.matches({ ready: 'idle' }), + }; }, saveChanges: () => { service.send({ type: 'routingRule.save' }); @@ -68,6 +81,18 @@ export const useStreamRoutingEvents = () => { reviewSuggestedRule: (id: string) => { service.send({ type: 'routingRule.reviewSuggested', id }); }, + editSuggestion: (index: number, suggestion: PartitionSuggestion) => { + service.send({ type: 'suggestion.edit', index, suggestion }); + }, + changeSuggestionName: (name: string) => { + service.send({ type: 'suggestion.changeName', name }); + }, + changeSuggestionCondition: (condition: Condition) => { + service.send({ type: 'suggestion.changeCondition', condition }); + }, + saveEditedSuggestion: () => { + service.send({ type: 'suggestion.saveSuggestion' }); + }, }), [service] ); diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/stream_name_form_row.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/stream_name_form_row.tsx index 2ce55aed84d29..4804f2c68bb31 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/stream_name_form_row.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/stream_name_form_row.tsx @@ -14,6 +14,8 @@ interface StreamNameFormRowProps { onChange?: (value: string) => void; disabled?: boolean; autoFocus?: boolean; + error?: string; + isInvalid?: boolean; } const MAX_NAME_LENGTH = 200; @@ -23,6 +25,8 @@ export function StreamNameFormRow({ onChange = () => {}, disabled = false, autoFocus = false, + error, + isInvalid = false, }: StreamNameFormRowProps) { const helpText = value.length >= MAX_NAME_LENGTH && !disabled @@ -41,6 +45,8 @@ export function StreamNameFormRow({ defaultMessage: 'Stream name', })} helpText={helpText} + error={error} + isInvalid={isInvalid} > onChange(e.target.value)} maxLength={200} + isInvalid={isInvalid} /> );