77
88import {
99 EuiButton ,
10+ EuiButtonEmpty ,
1011 EuiFlexGroup ,
1112 EuiFlexItem ,
1213 EuiMarkdownEditor ,
1314 EuiPanel ,
1415 EuiText ,
1516} from '@elastic/eui' ;
1617import { i18n } from '@kbn/i18n' ;
17- import { useAbortController } from '@kbn/react-hooks' ;
18- import { Streams } from '@kbn/streams-schema' ;
19- import { isEmpty , omit } from 'lodash' ;
20- import React , { useCallback , useEffect , useState } from 'react' ;
21- import { useKibana } from '../../../hooks/use_kibana' ;
22- import { getFormattedError } from '../../../util/errors' ;
23- import { useTimefilter } from '../../../hooks/use_timefilter' ;
24- import { useUpdateStreams } from '../../../hooks/use_update_streams' ;
18+ import type { Streams } from '@kbn/streams-schema' ;
19+ import React from 'react' ;
20+ import { useStreamDescriptionApi } from './stream_description/use_stream_description_api' ;
2521import { ConnectorListButton } from '../../connector_list_button/connector_list_button' ;
26- import { useAIFeatures } from '../../stream_detail_significant_events_view/add_significant_event_flyout/generated_flow_form/use_ai_features' ;
2722
2823export interface AISummaryProps {
2924 definition : Streams . all . GetResponse ;
25+ refreshDefinition : ( ) => void ;
3026}
3127
3228const STREAM_DESCRIPTION_PANEL_TITLE = i18n . translate (
@@ -65,124 +61,40 @@ const SAVE_DESCRIPTION_BUTTON_LABEL = i18n.translate(
6561 }
6662) ;
6763
68- export const StreamDescription : React . FC < AISummaryProps > = ( { definition } ) => {
69- const updateStream = useUpdateStreams ( definition . stream . name ) ;
70-
71- const {
72- core : { notifications } ,
73- dependencies : {
74- start : { streams } ,
75- } ,
76- } = useKibana ( ) ;
77-
78- const { timeState } = useTimefilter ( ) ;
79-
80- const [ description , setDescription ] = useState ( definition . stream . description || '' ) ;
81-
82- const [ isGenerating , setIsGenerating ] = useState ( false ) ;
83-
84- const [ isUpdating , setIsUpdating ] = useState ( false ) ;
85-
86- const aiFeatures = useAIFeatures ( ) ;
87-
88- const { signal } = useAbortController ( ) ;
89-
90- // Save the updated description; show success and error toasts
91- const save = useCallback (
92- async ( nextDescription : string ) => {
93- setIsUpdating ( true ) ;
94- updateStream (
95- Streams . all . UpsertRequest . parse ( {
96- dashboards : definition . dashboards ,
97- queries : definition . queries ,
98- rules : definition . rules ,
99- stream : {
100- ...omit ( definition . stream , 'name' ) ,
101- description : nextDescription ,
102- } ,
103- } )
104- )
105- . then ( ( ) => {
106- notifications . toasts . addSuccess ( {
107- title : i18n . translate (
108- 'xpack.streams.streamDetailView.streamDescription.saveSuccessTitle' ,
109- {
110- defaultMessage : 'Description saved' ,
111- }
112- ) ,
113- } ) ;
114- } )
115- . catch ( ( error ) => {
116- notifications . toasts . addError ( error , {
117- title : i18n . translate (
118- 'xpack.streams.streamDetailView.streamDescription.saveErrorTitle' ,
119- {
120- defaultMessage : 'Failed to save description' ,
121- }
122- ) ,
123- toastMessage : getFormattedError ( error ) . message ,
124- } ) ;
125- } )
126- . finally ( ( ) => {
127- setIsUpdating ( false ) ;
128- } ) ;
129- } ,
130- [ definition , updateStream , notifications . toasts ]
131- ) ;
132-
133- const generate = useCallback ( ( ) => {
134- if ( ! aiFeatures ?. genAiConnectors . selectedConnector ) {
135- return ;
136- }
64+ const EDIT_DESCRIPTION_BUTTON_LABEL = i18n . translate (
65+ 'xpack.streams.streamDetailView.streamDescription.editDescriptionButtonLabel' ,
66+ {
67+ defaultMessage : 'Edit' ,
68+ }
69+ ) ;
13770
138- setIsGenerating ( true ) ;
71+ const MANUAL_ENTRY_BUTTON_LABEL = i18n . translate (
72+ 'xpack.streams.streamDetailView.streamDescription.manualEntryButtonLabel' ,
73+ {
74+ defaultMessage : 'Enter manually' ,
75+ }
76+ ) ;
13977
140- streams . streamsRepositoryClient
141- . stream ( 'POST /internal/streams/{name}/_describe_stream' , {
142- signal,
143- params : {
144- path : {
145- name : definition . stream . name ,
146- } ,
147- query : {
148- connectorId : aiFeatures . genAiConnectors . selectedConnector ,
149- from : timeState . asAbsoluteTimeRange . from ,
150- to : timeState . asAbsoluteTimeRange . to ,
151- } ,
152- } ,
153- } )
154- . subscribe ( {
155- next ( { description : generatedDescription } ) {
156- setDescription ( generatedDescription ) ;
157- } ,
158- complete ( ) {
159- setIsGenerating ( false ) ;
160- } ,
161- error ( error ) {
162- setIsGenerating ( false ) ;
163- notifications . toasts . addError ( error , {
164- title : i18n . translate (
165- 'xpack.streams.streamDetailView.streamDescription.generateErrorTitle' ,
166- { defaultMessage : 'Failed to generate description' }
167- ) ,
168- toastMessage : getFormattedError ( error ) . message ,
169- } ) ;
170- } ,
171- } ) ;
172- } , [
173- definition . stream . name ,
174- streams . streamsRepositoryClient ,
175- timeState . asAbsoluteTimeRange . from ,
176- timeState . asAbsoluteTimeRange . to ,
177- aiFeatures ?. genAiConnectors . selectedConnector ,
178- signal ,
179- notifications . toasts ,
180- ] ) ;
78+ const CANCEL_LABEL = i18n . translate (
79+ 'xpack.streams.streamDetailView.streamDescription.cancelButtonLabel' ,
80+ {
81+ defaultMessage : 'Cancel' ,
82+ }
83+ ) ;
18184
182- useEffect ( ( ) => {
183- const connectorId = aiFeatures ?. genAiConnectors . selectedConnector ;
184- if ( ! connectorId || ! isEmpty ( description ) ) return ;
185- } , [ aiFeatures ?. genAiConnectors . selectedConnector , description ] ) ;
85+ export const StreamDescription : React . FC < AISummaryProps > = ( { definition, refreshDefinition } ) => {
86+ const {
87+ isGenerating,
88+ description,
89+ isUpdating,
90+ isEditing,
91+ setDescription,
92+ onCancelEdit,
93+ onGenerateDescription,
94+ onSaveDescription,
95+ onStartEditing,
96+ areButtonsDisabled,
97+ } = useStreamDescriptionApi ( { definition, refreshDefinition } ) ;
18698
18799 return (
188100 < EuiPanel hasBorder = { true } hasShadow = { false } paddingSize = "none" grow = { false } >
@@ -192,60 +104,107 @@ export const StreamDescription: React.FC<AISummaryProps> = ({ definition }) => {
192104 </ EuiText >
193105 </ EuiPanel >
194106 < EuiPanel paddingSize = "m" hasShadow = { false } hasBorder = { false } >
195- < EuiFlexGroup direction = "column" gutterSize = "m" >
196- < EuiText size = "s" color = "subdued" >
197- { STREAM_DESCRIPTION_HELP }
198- </ EuiText >
199- < EuiMarkdownEditor
200- value = { description }
201- onChange = { ( next ) => {
202- setDescription ( next ) ;
203- } }
204- aria-labelledby = "stream-description-editor"
205- placeholder = { STREAM_DESCRIPTION_EMPTY }
206- readOnly = { isGenerating || isUpdating }
207- toolbarProps = { {
208- right : (
209- < EuiFlexGroup
210- direction = "row"
211- gutterSize = "s"
212- justifyContent = "flexEnd"
213- alignItems = "center"
214- >
215- < EuiFlexItem grow = { false } >
216- < ConnectorListButton
217- buttonProps = { {
218- size : 's' ,
219- iconType : 'sparkles' ,
220- children : GENERATE_DESCRIPTION_BUTTON_LABEL ,
221- onClick ( ) {
222- generate ( ) ;
223- } ,
224- isDisabled : isGenerating || isUpdating ,
225- isLoading : isGenerating ,
226- } }
227- />
228- </ EuiFlexItem >
229- < EuiFlexItem grow = { false } >
230- < EuiButton
231- iconType = "save"
232- size = "s"
233- iconSize = "s"
234- fill
235- isLoading = { isUpdating }
236- isDisabled = { isUpdating || isGenerating }
237- onClick = { ( ) => {
238- save ( description ) ;
239- } }
240- >
241- { SAVE_DESCRIPTION_BUTTON_LABEL }
242- </ EuiButton >
243- </ EuiFlexItem >
244- </ EuiFlexGroup >
245- ) ,
246- } }
247- />
248- </ EuiFlexGroup >
107+ { definition . stream . description || description || isEditing ? (
108+ < EuiFlexGroup direction = "column" gutterSize = "m" >
109+ < EuiText size = "s" color = "subdued" >
110+ { STREAM_DESCRIPTION_HELP }
111+ </ EuiText >
112+ < EuiMarkdownEditor
113+ value = { description }
114+ onChange = { setDescription }
115+ aria-labelledby = "stream-description-editor"
116+ placeholder = { STREAM_DESCRIPTION_EMPTY }
117+ readOnly = { areButtonsDisabled || ! isEditing }
118+ toolbarProps = { {
119+ right : (
120+ < EuiFlexGroup
121+ direction = "row"
122+ gutterSize = "s"
123+ justifyContent = "flexEnd"
124+ alignItems = "center"
125+ >
126+ { isEditing && (
127+ < EuiFlexItem grow = { false } >
128+ < EuiButtonEmpty
129+ aria-label = { CANCEL_LABEL }
130+ size = "s"
131+ isLoading = { isUpdating }
132+ isDisabled = { areButtonsDisabled }
133+ onClick = { onCancelEdit }
134+ >
135+ { CANCEL_LABEL }
136+ </ EuiButtonEmpty >
137+ </ EuiFlexItem >
138+ ) }
139+ < EuiFlexItem grow = { false } >
140+ < ConnectorListButton
141+ buttonProps = { {
142+ size : 's' ,
143+ iconType : 'sparkles' ,
144+ children : GENERATE_DESCRIPTION_BUTTON_LABEL ,
145+ onClick : onGenerateDescription ,
146+ isDisabled : areButtonsDisabled ,
147+ isLoading : isGenerating ,
148+ } }
149+ />
150+ </ EuiFlexItem >
151+ < EuiFlexItem grow = { false } >
152+ < EuiButton
153+ iconType = { isEditing ? 'save' : 'pencil' }
154+ size = "s"
155+ iconSize = "s"
156+ fill
157+ isLoading = { isUpdating }
158+ isDisabled = { areButtonsDisabled }
159+ onClick = { ( ) => {
160+ if ( ! isEditing ) {
161+ onStartEditing ( ) ;
162+ } else {
163+ onSaveDescription ( ) ;
164+ }
165+ } }
166+ >
167+ { isEditing ? SAVE_DESCRIPTION_BUTTON_LABEL : EDIT_DESCRIPTION_BUTTON_LABEL }
168+ </ EuiButton >
169+ </ EuiFlexItem >
170+ </ EuiFlexGroup >
171+ ) ,
172+ } }
173+ />
174+ </ EuiFlexGroup >
175+ ) : (
176+ < EuiFlexGroup direction = "row" gutterSize = "s" alignItems = "center" >
177+ < EuiFlexItem grow = { false } >
178+ < EuiText size = "s" color = "subdued" >
179+ { STREAM_DESCRIPTION_HELP }
180+ </ EuiText >
181+ </ EuiFlexItem >
182+
183+ < EuiFlexItem grow = { false } >
184+ < EuiButton
185+ size = "s"
186+ isLoading = { isUpdating }
187+ isDisabled = { areButtonsDisabled }
188+ onClick = { onStartEditing }
189+ >
190+ { MANUAL_ENTRY_BUTTON_LABEL }
191+ </ EuiButton >
192+ </ EuiFlexItem >
193+ < EuiFlexItem grow = { false } >
194+ < ConnectorListButton
195+ buttonProps = { {
196+ fill : true ,
197+ size : 's' ,
198+ iconType : 'sparkles' ,
199+ children : GENERATE_DESCRIPTION_BUTTON_LABEL ,
200+ onClick : onGenerateDescription ,
201+ isDisabled : areButtonsDisabled ,
202+ isLoading : isGenerating ,
203+ } }
204+ />
205+ </ EuiFlexItem >
206+ </ EuiFlexGroup >
207+ ) }
249208 </ EuiPanel >
250209 </ EuiPanel >
251210 ) ;
0 commit comments