-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added support for comments * deduped code * updated feedback link --------- (cherry picked from commit 722047d) Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
4cef900
commit 1af26aa
Showing
19 changed files
with
734 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React, { useCallback, useEffect, useState } from "react"; | ||
import { Comment } from "../../models/Comments"; | ||
import { | ||
EuiFlyout, | ||
EuiFlyoutHeader, | ||
EuiFlyoutBody, | ||
EuiCommentList, | ||
EuiText, | ||
EuiButtonIcon, | ||
EuiContextMenuItem, | ||
EuiContextMenuPanel, | ||
EuiPopover, | ||
EuiTitle, | ||
EuiSpacer, | ||
EuiCallOut, | ||
EuiLink | ||
} from "@elastic/eui"; | ||
import { CommentEditor } from "./CommentEditor"; | ||
import moment from "moment"; | ||
import { getTimeZone } from "../../pages/CreateTrigger/utils/helper"; | ||
import { title } from "vega-lite/src/channeldef"; | ||
|
||
export interface AlertCommentsFlyoutProps { | ||
alertId: string; | ||
httpClient: any; | ||
closeFlyout: () => void; | ||
} | ||
|
||
type CommentItem = Comment & { state: 'edit' | 'readonly', draft: string; }; | ||
|
||
export const AlertCommentsFlyout: React.FC<AlertCommentsFlyoutProps> = ({ alertId, httpClient, closeFlyout }) => { | ||
const [comments, setComments] = useState<CommentItem[]>([]); | ||
const [commentIdWithOpenActionMenu, setCommentIdWithOpenActionMenu] = useState<string | undefined>(undefined); | ||
const [draftCommentContent, setDraftCommentContent] = useState(''); | ||
const [createPending, setCreatePending] = useState(false); | ||
const [updatePending, setUpdatePending] = useState(false); | ||
const toggleCommentActionMenu = (commentId: string) => { | ||
setCommentIdWithOpenActionMenu(commentIdWithOpenActionMenu ? undefined : commentId); | ||
}; | ||
const isACommentBeingEdited = comments.some(comment => comment.state === 'edit'); | ||
|
||
const loadComments = useCallback(async () => { | ||
const getComments = async () => { | ||
const res = await httpClient.post('../api/alerting/comments/_search', { body: JSON.stringify({ | ||
query: { | ||
match: { | ||
entity_id: alertId | ||
} | ||
} | ||
}) | ||
}); | ||
|
||
if (res.ok) { | ||
setComments(res.resp.comments.map((comment: Comment) => ({ | ||
...comment, | ||
state: 'readonly', | ||
draft: comment.content | ||
})).sort((a: Comment, b: Comment) => b.created_time - a.created_time)); | ||
} | ||
} | ||
|
||
getComments(); | ||
}, [httpClient, alertId]); | ||
|
||
useEffect(() => { | ||
loadComments(); | ||
}, [alertId]); | ||
|
||
const closeCommentActionMenu = () => { | ||
setCommentIdWithOpenActionMenu(undefined); | ||
}; | ||
|
||
const createComment = async () => { | ||
setCreatePending(true); | ||
await httpClient.post(`../api/alerting/comments/${alertId}`, { body: JSON.stringify({ | ||
content: draftCommentContent | ||
})}); | ||
|
||
setDraftCommentContent(''); | ||
loadComments(); | ||
setCreatePending(false); | ||
} | ||
|
||
const updateComment = async (commentId: string, content: string) => { | ||
setUpdatePending(true); | ||
await httpClient.put(`../api/alerting/comments/${commentId}`, { body: JSON.stringify({ content })}); | ||
loadComments(); | ||
setUpdatePending(false); | ||
} | ||
|
||
const deleteComment = async (commentId: string) => { | ||
await httpClient.delete(`../api/alerting/comments/${commentId}`); | ||
loadComments(); | ||
} | ||
|
||
const onCommentContentChange = (comment: CommentItem, commentIdx: number, newContent: string) => { | ||
setComments([ | ||
...comments.slice(0, commentIdx), | ||
{ | ||
...comment, | ||
draft: newContent | ||
}, | ||
...comments.slice(commentIdx + 1) | ||
]); | ||
} | ||
|
||
const onEditClick = (comment: CommentItem, idx: number) => { | ||
setComments([ | ||
...comments.slice(0, idx), | ||
{ | ||
...comment, | ||
state: 'edit' | ||
}, | ||
...comments.slice(idx + 1) | ||
]); | ||
setCommentIdWithOpenActionMenu(undefined); | ||
} | ||
|
||
const onEditCancel = (comment: CommentItem, idx: number) => { | ||
setComments([ | ||
...comments.slice(0, idx), | ||
{ | ||
...comment, | ||
state: 'readonly' | ||
}, | ||
...comments.slice(idx + 1) | ||
]); | ||
} | ||
|
||
const commentListItems = comments.map((comment, idx) => { | ||
const content = comment.state === 'readonly' ? ( | ||
<EuiText size="s"> | ||
<p> | ||
{comment.content} | ||
</p> | ||
</EuiText> | ||
) : ( | ||
<CommentEditor | ||
isLoading={updatePending} | ||
draftCommentContent={comment.draft} | ||
onContentChange={(event) => { | ||
onCommentContentChange(comment, idx, event.target.value); | ||
}} | ||
onSave={() => updateComment(comment.id, comment.draft)} | ||
onCancel={() => onEditCancel(comment, idx)} | ||
/> | ||
); | ||
|
||
const customActions = comment.state === 'readonly' && ( | ||
<EuiPopover | ||
button={ | ||
<EuiButtonIcon | ||
aria-label="Actions" | ||
iconType="boxesHorizontal" | ||
size="s" | ||
color="text" | ||
onClick={() => toggleCommentActionMenu(comment.id)} | ||
/> | ||
} | ||
isOpen={commentIdWithOpenActionMenu === comment.id} | ||
closePopover={closeCommentActionMenu} | ||
panelPaddingSize="none" | ||
anchorPosition="leftCenter"> | ||
<EuiContextMenuPanel | ||
items={[ | ||
<EuiContextMenuItem | ||
key="A" | ||
icon="pencil" | ||
onClick={() => onEditClick(comment, idx)}> | ||
Edit | ||
</EuiContextMenuItem>, | ||
<EuiContextMenuItem | ||
key="B" | ||
icon="trash" | ||
onClick={() => { | ||
deleteComment(comment.id); | ||
}}> | ||
Delete | ||
</EuiContextMenuItem> | ||
]} | ||
/> | ||
</EuiPopover> | ||
); | ||
|
||
return { | ||
username: comment.user || 'Unknown', | ||
event: `${comment.last_updated_time ? 'edited' : 'added'} comment on`, | ||
timestamp: moment.utc(comment.last_updated_time ?? comment.created_time).tz(getTimeZone()).format(), | ||
children: content, | ||
actions: customActions, | ||
} | ||
}); | ||
|
||
return ( | ||
<EuiFlyout onClose={closeFlyout}> | ||
<EuiFlyoutHeader hasBorder> | ||
<EuiTitle size="m"> | ||
<h2>Comments</h2> | ||
</EuiTitle> | ||
</EuiFlyoutHeader> | ||
<EuiFlyoutBody> | ||
<EuiCallOut | ||
iconType='iInCircle' | ||
title='Experimental'> | ||
<span>The feature is experimental and should not be used in a production environment. | ||
The posted comments will be impacted if the feature is deactivated. | ||
For more information see <EuiLink href="https://opensearch.org/docs/latest/observing-your-data/alerting/index/" target="_blank">Documentation.</EuiLink> | ||
To leave feedback, visit <EuiLink href="https://github.com/opensearch-project/OpenSearch-Dashboards/issues/6999" target="_blank">github.com</EuiLink>. | ||
</span> | ||
</EuiCallOut> | ||
<EuiSpacer /> | ||
<EuiTitle size="xs"> | ||
<h4>Add comment</h4> | ||
</EuiTitle> | ||
<EuiSpacer size="m" /> | ||
<CommentEditor | ||
isLoading={createPending} | ||
draftCommentContent={draftCommentContent} | ||
onContentChange={(event) => { | ||
setDraftCommentContent(event.target.value); | ||
}} | ||
onSave={createComment} | ||
saveDisabled={isACommentBeingEdited} | ||
/> | ||
<EuiSpacer /> | ||
<EuiTitle size="xs"> | ||
<h4>Comments ({comments.length})</h4> | ||
</EuiTitle> | ||
<EuiSpacer /> | ||
<EuiCommentList comments={commentListItems}/> | ||
</EuiFlyoutBody> | ||
</EuiFlyout> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from "react"; | ||
import { | ||
EuiFlexGroup, | ||
EuiFlexItem, | ||
EuiButton | ||
} from "@elastic/eui"; | ||
|
||
export interface CommentEditorProps { | ||
isLoading: boolean; | ||
saveDisabled?: boolean; | ||
draftCommentContent: string; | ||
onSave: React.MouseEventHandler; | ||
onCancel?: React.MouseEventHandler; | ||
onContentChange: React.ChangeEventHandler<HTMLTextAreaElement>; | ||
} | ||
|
||
export const CommentEditor: React.FC<CommentEditorProps> = ({ | ||
isLoading, | ||
draftCommentContent, | ||
saveDisabled, | ||
onSave, | ||
onCancel, | ||
onContentChange, | ||
}) => ( | ||
<EuiFlexGroup gutterSize="s" direction="column" > | ||
<EuiFlexItem> | ||
<textarea style={{ resize: 'vertical', fontSize: 14, minHeight: 45 }} value={draftCommentContent} onChange={onContentChange}/> | ||
</EuiFlexItem> | ||
<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end' }}> | ||
<EuiFlexGroup gutterSize="s"> | ||
{onCancel && ( | ||
<EuiFlexItem grow={false}> | ||
<EuiButton onClick={onCancel}> | ||
Cancel | ||
</EuiButton> | ||
</EuiFlexItem> | ||
)} | ||
<EuiFlexItem grow={false}> | ||
<EuiButton onClick={onSave} color="primary" isLoading={isLoading} disabled={saveDisabled} fill> | ||
Save | ||
</EuiButton> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React, { useCallback, useEffect, useState } from "react"; | ||
import { Comment } from "../../models/Comments"; | ||
import { EuiButtonIcon, EuiToolTip } from "@elastic/eui"; | ||
import { AlertCommentsFlyout } from "./AlertCommentsFlyout"; | ||
|
||
export interface ShowAlertCommentsProps { | ||
alert: any; | ||
httpClient: any; | ||
} | ||
|
||
export const ShowAlertComments: React.FC<ShowAlertCommentsProps> = ({ alert, httpClient }) => { | ||
const [commentsFlyout, setCommentsFlyout] = useState<React.ReactNode | null>(null); | ||
|
||
const showCommentsFlyout = useCallback(() => { | ||
setCommentsFlyout(<AlertCommentsFlyout | ||
alertId={alert.id} | ||
httpClient={httpClient} | ||
closeFlyout={() => setCommentsFlyout(null)} | ||
/>); | ||
}, [setCommentsFlyout]); | ||
|
||
return ( | ||
<> | ||
<EuiToolTip content={'Show comments'}> | ||
<EuiButtonIcon | ||
aria-label={'Show comments'} | ||
data-test-subj={`show-comments-icon`} | ||
iconType={'editorComment'} | ||
onClick={showCommentsFlyout} | ||
/> | ||
</EuiToolTip> | ||
{commentsFlyout} | ||
</> | ||
) | ||
} |
Oops, something went wrong.