Skip to content

Commit 61b4cb5

Browse files
shahzad31cesco-f
authored andcommitted
[Streams] Update stream description UI (elastic#238148)
## Summary Update stream description UI Added edit mode and cancel button , and also Manual entry button when user lands for the fist time. ### Initial view <img width="1728" height="879" alt="image" src="https://github.com/user-attachments/assets/89687f84-fe73-4336-9cf5-60e29e9ac570" /> ### Edit mode <img width="1727" height="882" alt="image" src="https://github.com/user-attachments/assets/47c60e2f-9ed5-4148-a1da-a375418b2025" /> <img width="1728" height="792" alt="image" src="https://github.com/user-attachments/assets/fb20e4f8-b0d9-4e88-8645-f4a6f3c72a3f" /> --------- Co-authored-by: Francesco Fagnani <[email protected]>
1 parent b254d39 commit 61b4cb5

File tree

4 files changed

+321
-180
lines changed

4 files changed

+321
-180
lines changed

x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_management/classic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function ClassicStreamDetailManagement({
164164
<>
165165
{otherTabs.significantEvents ? (
166166
<>
167-
<StreamDescription definition={definition} />
167+
<StreamDescription definition={definition} refreshDefinition={refreshDefinition} />
168168
<EuiSpacer />
169169
</>
170170
) : null}

x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_management/stream_description.tsx

Lines changed: 137 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,22 @@
77

88
import {
99
EuiButton,
10+
EuiButtonEmpty,
1011
EuiFlexGroup,
1112
EuiFlexItem,
1213
EuiMarkdownEditor,
1314
EuiPanel,
1415
EuiText,
1516
} from '@elastic/eui';
1617
import { 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';
2521
import { 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

2823
export interface AISummaryProps {
2924
definition: Streams.all.GetResponse;
25+
refreshDefinition: () => void;
3026
}
3127

3228
const 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

Comments
 (0)