From acaa844440811ea8a460f4f1b7dd25a6ed3b0d96 Mon Sep 17 00:00:00 2001 From: toepkerd <120457569+toepkerd@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:10:42 -0700 Subject: [PATCH] Alerting Enhancements: Alerting Comments (Experimental) (#663) * initial commit, functional but needs refactoring Signed-off-by: Dennis Toepker * added lastUpdatedTime to Note object Signed-off-by: Dennis Toepker * changed name from alert id to entity id Signed-off-by: Dennis Toepker * import cleanup Signed-off-by: Dennis Toepker * changing name from Alerting Notes to Alerting Comments Signed-off-by: Dennis Toepker * misc fixes and cleanup Signed-off-by: Dennis Toepker * adding unit test coverage Signed-off-by: Dennis Toepker * misc cleanup Signed-off-by: Dennis Toepker * misc review-based cleanup Signed-off-by: Dennis Toepker * added validation exception messages Signed-off-by: Dennis Toepker --------- Signed-off-by: Dennis Toepker Co-authored-by: Dennis Toepker --- .../alerting/action/AlertingActions.kt | 15 ++ .../alerting/action/DeleteCommentRequest.kt | 34 ++++ .../alerting/action/DeleteCommentResponse.kt | 32 ++++ .../alerting/action/IndexCommentRequest.kt | 73 +++++++++ .../alerting/action/IndexCommentResponse.kt | 57 +++++++ .../alerting/action/SearchCommentRequest.kt | 33 ++++ .../commons/alerting/model/Comment.kt | 155 ++++++++++++++++++ .../commons/alerting/model/DataSources.kt | 25 +++ .../commons/alerting/util/IndexUtils.kt | 7 + .../action/DeleteCommentRequestTests.kt | 22 +++ .../action/DeleteCommentResponseTests.kt | 22 +++ .../action/IndexCommentRequestTests.kt | 42 +++++ .../action/IndexCommentResponseTests.kt | 34 ++++ .../action/SearchCommentRequestTests.kt | 27 +++ 14 files changed, 578 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/alerting/model/Comment.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequestTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt index f2ada6a5..ba4e33fe 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/AlertingActions.kt @@ -21,6 +21,9 @@ object AlertingActions { const val SUBSCRIBE_FINDINGS_ACTION_NAME = "cluster:admin/opensearch/alerting/findings/subscribe" const val GET_MONITOR_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/get" const val SEARCH_MONITORS_ACTION_NAME = "cluster:admin/opendistro/alerting/monitor/search" + const val INDEX_COMMENT_ACTION_NAME = "cluster:admin/opensearch/alerting/alerts/comments/write" + const val SEARCH_COMMENTS_ACTION_NAME = "cluster:admin/opensearch/alerting/alerts/comments/search" + const val DELETE_COMMENT_ACTION_NAME = "cluster:admin/opensearch/alerting/alerts/comments/delete" @JvmField val INDEX_MONITOR_ACTION_TYPE = @@ -73,4 +76,16 @@ object AlertingActions { @JvmField val SEARCH_MONITORS_ACTION_TYPE = ActionType(SEARCH_MONITORS_ACTION_NAME, ::SearchResponse) + + @JvmField + val INDEX_COMMENT_ACTION_TYPE = + ActionType(INDEX_COMMENT_ACTION_NAME, ::IndexCommentResponse) + + @JvmField + val SEARCH_COMMENTS_ACTION_TYPE = + ActionType(SEARCH_COMMENTS_ACTION_NAME, ::SearchResponse) + + @JvmField + val DELETE_COMMENT_ACTION_TYPE = + ActionType(DELETE_COMMENT_ACTION_NAME, ::DeleteCommentResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequest.kt new file mode 100644 index 00000000..811dcd9e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequest.kt @@ -0,0 +1,34 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import java.io.IOException + +class DeleteCommentRequest : ActionRequest { + val commentId: String + + constructor(commentId: String) : super() { + this.commentId = commentId + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + commentId = sin.readString() + ) + + override fun validate(): ActionRequestValidationException? { + if (commentId.isBlank()) { + val exception = ActionRequestValidationException() + exception.addValidationError("comment id must not be blank") + return exception + } + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(commentId) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponse.kt new file mode 100644 index 00000000..f00fe266 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponse.kt @@ -0,0 +1,32 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.commons.alerting.util.IndexUtils +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder + +class DeleteCommentResponse : BaseResponse { + var commentId: String + + constructor( + id: String + ) : super() { + this.commentId = id + } + + constructor(sin: StreamInput) : this( + sin.readString() // commentId + ) + + override fun writeTo(out: StreamOutput) { + out.writeString(commentId) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(IndexUtils._ID, commentId) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequest.kt new file mode 100644 index 00000000..6b66cb64 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequest.kt @@ -0,0 +1,73 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.rest.RestRequest +import java.io.IOException + +/** + * Request to index/create a Comment + * + * entityId: the entity that the Comment is attached to and therefore associated with (e.g. in Alerting, + * the entity is an Alert). This field is expected to be non-blank if the request is to create a new Comment. + * + * commentId: the ID of an existing Comment. This field is expected to be non-blank if the request is to + * update an existing Comment. + */ +class IndexCommentRequest : ActionRequest { + val entityId: String + val commentId: String + val seqNo: Long + val primaryTerm: Long + val method: RestRequest.Method + var content: String + + constructor( + entityId: String, + commentId: String, + seqNo: Long, + primaryTerm: Long, + method: RestRequest.Method, + content: String + ) : super() { + this.entityId = entityId + this.commentId = commentId + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.method = method + this.content = content + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + entityId = sin.readString(), + commentId = sin.readString(), + seqNo = sin.readLong(), + primaryTerm = sin.readLong(), + method = sin.readEnum(RestRequest.Method::class.java), + content = sin.readString() + ) + + override fun validate(): ActionRequestValidationException? { + if (method == RestRequest.Method.POST && entityId.isBlank() || + method == RestRequest.Method.PUT && commentId.isBlank() + ) { + val exception = ActionRequestValidationException() + exception.addValidationError("id must not be blank") + return exception + } + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(entityId) + out.writeString(commentId) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + out.writeEnum(method) + out.writeString(content) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponse.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponse.kt new file mode 100644 index 00000000..7c9bb9b7 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponse.kt @@ -0,0 +1,57 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.commons.alerting.model.Comment +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.IndexUtils.Companion._PRIMARY_TERM +import org.opensearch.commons.alerting.util.IndexUtils.Companion._SEQ_NO +import org.opensearch.commons.notifications.action.BaseResponse +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import java.io.IOException + +class IndexCommentResponse : BaseResponse { + var id: String + var seqNo: Long + var primaryTerm: Long + var comment: Comment + + constructor( + id: String, + seqNo: Long, + primaryTerm: Long, + comment: Comment + ) : super() { + this.id = id + this.seqNo = seqNo + this.primaryTerm = primaryTerm + this.comment = comment + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + sin.readString(), // id + sin.readLong(), // seqNo + sin.readLong(), // primaryTerm + Comment.readFrom(sin) // comment + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeLong(seqNo) + out.writeLong(primaryTerm) + comment.writeTo(out) + } + + @Throws(IOException::class) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject() + .field(_ID, id) + .field(_SEQ_NO, seqNo) + .field(_PRIMARY_TERM, primaryTerm) + .field("comment", comment) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequest.kt b/src/main/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequest.kt new file mode 100644 index 00000000..e0d150d0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequest.kt @@ -0,0 +1,33 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.search.SearchRequest +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import java.io.IOException + +class SearchCommentRequest : ActionRequest { + + val searchRequest: SearchRequest + + constructor( + searchRequest: SearchRequest + ) : super() { + this.searchRequest = searchRequest + } + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + searchRequest = SearchRequest(sin) + ) + + override fun validate(): ActionRequestValidationException? { + return null + } + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + searchRequest.writeTo(out) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/Comment.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/Comment.kt new file mode 100644 index 00000000..68ae7bf5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/Comment.kt @@ -0,0 +1,155 @@ +package org.opensearch.commons.alerting.model + +import org.opensearch.commons.alerting.util.IndexUtils.Companion._ID +import org.opensearch.commons.alerting.util.instant +import org.opensearch.commons.alerting.util.optionalTimeField +import org.opensearch.commons.alerting.util.optionalUserField +import org.opensearch.commons.alerting.util.optionalUsernameField +import org.opensearch.commons.authuser.User +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.core.common.io.stream.StreamOutput +import org.opensearch.core.common.io.stream.Writeable +import org.opensearch.core.xcontent.ToXContent +import org.opensearch.core.xcontent.XContentBuilder +import org.opensearch.core.xcontent.XContentParser +import org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken +import java.io.IOException +import java.time.Instant + +data class Comment( + val id: String = NO_ID, + val entityId: String = NO_ID, + val content: String, + val createdTime: Instant, + val lastUpdatedTime: Instant?, + val user: User? +) : Writeable, ToXContent { + + @Throws(IOException::class) + constructor(sin: StreamInput) : this( + id = sin.readString(), + entityId = sin.readString(), + content = sin.readString(), + createdTime = sin.readInstant(), + lastUpdatedTime = sin.readOptionalInstant(), + user = if (sin.readBoolean()) User(sin) else null + ) + + constructor( + entityId: String, + content: String, + createdTime: Instant, + user: User? + ) : this ( + entityId = entityId, + content = content, + createdTime = createdTime, + lastUpdatedTime = null, + user = user + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(id) + out.writeString(entityId) + out.writeString(content) + out.writeInstant(createdTime) + out.writeOptionalInstant(lastUpdatedTime) + out.writeBoolean(user != null) + user?.writeTo(out) + } + + fun asTemplateArg(): Map { + return mapOf( + _ID to id, + ENTITY_ID_FIELD to entityId, + COMMENT_CONTENT_FIELD to content, + COMMENT_CREATED_TIME_FIELD to createdTime, + COMMENT_LAST_UPDATED_TIME_FIELD to lastUpdatedTime, + COMMENT_USER_FIELD to user?.name + ) + } + + // used to create the Comment JSON object for an API response (displayed to user) + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return createXContentBuilder(builder, false) + } + + // used to create the Comment JSON object for indexing a doc into an index (not displayed to user) + fun toXContentWithUser(builder: XContentBuilder): XContentBuilder { + return createXContentBuilder(builder, true) + } + + private fun createXContentBuilder(builder: XContentBuilder, includeFullUser: Boolean): XContentBuilder { + builder.startObject() + .field(ENTITY_ID_FIELD, entityId) + .field(COMMENT_CONTENT_FIELD, content) + .optionalTimeField(COMMENT_CREATED_TIME_FIELD, createdTime) + .optionalTimeField(COMMENT_LAST_UPDATED_TIME_FIELD, lastUpdatedTime) + + if (includeFullUser) { + // if we're storing a Comment into an internal index, include full User + builder.optionalUserField(COMMENT_USER_FIELD, user) + } else { + // if we're displaying the Comment as part of an API call response, only include username + builder.optionalUsernameField(COMMENT_USER_FIELD, user) + } + + builder.endObject() + return builder + } + + companion object { + const val ENTITY_ID_FIELD = "entity_id" + const val COMMENT_CONTENT_FIELD = "content" + const val COMMENT_CREATED_TIME_FIELD = "created_time" + const val COMMENT_LAST_UPDATED_TIME_FIELD = "last_updated_time" + const val COMMENT_USER_FIELD = "user" + const val NO_ID = "" + + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun parse(xcp: XContentParser, id: String = NO_ID): Comment { + lateinit var entityId: String + var content = "" + lateinit var createdTime: Instant + var lastUpdatedTime: Instant? = null + var user: User? = null + + ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + ENTITY_ID_FIELD -> entityId = xcp.text() + COMMENT_CONTENT_FIELD -> content = xcp.text() + COMMENT_CREATED_TIME_FIELD -> createdTime = requireNotNull(xcp.instant()) + COMMENT_LAST_UPDATED_TIME_FIELD -> lastUpdatedTime = xcp.instant() + COMMENT_USER_FIELD -> + user = if (xcp.currentToken() == XContentParser.Token.VALUE_NULL) { + null + } else { + User.parse(xcp) + } + } + } + + return Comment( + id = id, + entityId = entityId, + content = content, + createdTime = createdTime, + lastUpdatedTime = lastUpdatedTime, + user = user + ) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): Comment { + return Comment(sin) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt index b922a706..6ff38548 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/DataSources.kt @@ -31,6 +31,12 @@ data class DataSources( /** Configures a custom index pattern for alertHistoryIndex alias.*/ val alertsHistoryIndexPattern: String? = "<.opendistro-alerting-alert-history-{now/d}-1>", // AlertIndices.ALERT_HISTORY_INDEX_PATTERN + /** Configures a custom index alias to store comments associated with alerts.*/ + val commentsIndex: String = ".opensearch-alerting-comments-history-write", // AlertIndices.COMMENTS_HISTORY_WRITE_INDEX + + /** Configures a custom index pattern for commentsIndex alias.*/ + val commentsIndexPattern: String? = "<.opensearch-alerting-comments-history-{now/d}-1>", // AlertIndices.COMMENTS_HISTORY_INDEX_PATTERN + /** Configures custom mappings by field type for query index. * Custom query index mappings are configurable, only if a custom query index is configured too. */ val queryIndexMappingsByType: Map> = mapOf(), @@ -50,6 +56,9 @@ data class DataSources( require(alertsIndex.isNotEmpty()) { "Alerts index cannot be empty" } + require(commentsIndex.isNotEmpty()) { + "Comments index cannot be empty" + } if (queryIndexMappingsByType.isNotEmpty()) { require(queryIndex != ScheduledJob.DOC_LEVEL_QUERIES_INDEX) { "Custom query index mappings are configurable only if a custom query index is configured too." @@ -74,6 +83,8 @@ data class DataSources( alertsIndex = sin.readString(), alertsHistoryIndex = sin.readOptionalString(), alertsHistoryIndexPattern = sin.readOptionalString(), + commentsIndex = sin.readString(), + commentsIndexPattern = sin.readOptionalString(), queryIndexMappingsByType = sin.readMap() as Map>, findingsEnabled = sin.readOptionalBoolean() ) @@ -87,6 +98,8 @@ data class DataSources( ALERTS_INDEX_FIELD to alertsIndex, ALERTS_HISTORY_INDEX_FIELD to alertsHistoryIndex, ALERTS_HISTORY_INDEX_PATTERN_FIELD to alertsHistoryIndexPattern, + COMMENTS_INDEX_FIELD to commentsIndex, + COMMENTS_INDEX_PATTERN_FIELD to commentsIndexPattern, QUERY_INDEX_MAPPINGS_BY_TYPE to queryIndexMappingsByType, FINDINGS_ENABLED_FIELD to findingsEnabled ) @@ -100,6 +113,8 @@ data class DataSources( builder.field(ALERTS_INDEX_FIELD, alertsIndex) builder.field(ALERTS_HISTORY_INDEX_FIELD, alertsHistoryIndex) builder.field(ALERTS_HISTORY_INDEX_PATTERN_FIELD, alertsHistoryIndexPattern) + builder.field(COMMENTS_INDEX_FIELD, commentsIndex) + builder.field(COMMENTS_INDEX_PATTERN_FIELD, commentsIndexPattern) builder.field(QUERY_INDEX_MAPPINGS_BY_TYPE, queryIndexMappingsByType as Map) builder.field(FINDINGS_ENABLED_FIELD, findingsEnabled) builder.endObject() @@ -113,6 +128,8 @@ data class DataSources( const val ALERTS_INDEX_FIELD = "alerts_index" const val ALERTS_HISTORY_INDEX_FIELD = "alerts_history_index" const val ALERTS_HISTORY_INDEX_PATTERN_FIELD = "alerts_history_index_pattern" + const val COMMENTS_INDEX_FIELD = "comments_index" + const val COMMENTS_INDEX_PATTERN_FIELD = "comments_index_pattern" const val QUERY_INDEX_MAPPINGS_BY_TYPE = "query_index_mappings_by_type" const val FINDINGS_ENABLED_FIELD = "findings_enabled" @@ -126,6 +143,8 @@ data class DataSources( var alertsIndex = "" var alertsHistoryIndex = "" var alertsHistoryIndexPattern = "" + var commentsIndex = "" + var commentsIndexPattern = "" var queryIndexMappingsByType: Map> = mapOf() var findingsEnabled = false @@ -141,6 +160,8 @@ data class DataSources( ALERTS_INDEX_FIELD -> alertsIndex = xcp.text() ALERTS_HISTORY_INDEX_FIELD -> alertsHistoryIndex = xcp.text() ALERTS_HISTORY_INDEX_PATTERN_FIELD -> alertsHistoryIndexPattern = xcp.text() + COMMENTS_INDEX_FIELD -> commentsIndex = xcp.text() + COMMENTS_INDEX_PATTERN_FIELD -> commentsIndexPattern = xcp.text() QUERY_INDEX_MAPPINGS_BY_TYPE -> queryIndexMappingsByType = xcp.map() as Map> FINDINGS_ENABLED_FIELD -> findingsEnabled = xcp.booleanValue() } @@ -152,6 +173,8 @@ data class DataSources( alertsIndex = alertsIndex, alertsHistoryIndex = alertsHistoryIndex, alertsHistoryIndexPattern = alertsHistoryIndexPattern, + commentsIndex = commentsIndex, + commentsIndexPattern = commentsIndexPattern, queryIndexMappingsByType = queryIndexMappingsByType, findingsEnabled = findingsEnabled ) @@ -166,6 +189,8 @@ data class DataSources( out.writeString(alertsIndex) out.writeOptionalString(alertsHistoryIndex) out.writeOptionalString(alertsHistoryIndexPattern) + out.writeString(commentsIndex) + out.writeOptionalString(commentsIndexPattern) out.writeMap(queryIndexMappingsByType as Map) out.writeOptionalBoolean(findingsEnabled) } diff --git a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt index 9fa11d73..887e8430 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/util/IndexUtils.kt @@ -59,6 +59,13 @@ fun XContentBuilder.optionalUserField(name: String, user: User?): XContentBuilde return this.field(name, user) } +fun XContentBuilder.optionalUsernameField(name: String, user: User?): XContentBuilder { + if (user == null) { + return nullField(name) + } + return this.field(name, user.name) +} + fun XContentBuilder.optionalTimeField(name: String, instant: Instant?): XContentBuilder { if (instant == null) { return nullField(name) diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequestTests.kt new file mode 100644 index 00000000..70a22953 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentRequestTests.kt @@ -0,0 +1,22 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.core.common.io.stream.StreamInput + +class DeleteCommentRequestTests { + @Test + fun `test delete comment request writing and parsing`() { + val req = DeleteCommentRequest("1234") + assertNotNull(req) + assertEquals("1234", req.commentId) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = DeleteCommentRequest(sin) + assertEquals("1234", newReq.commentId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponseTests.kt new file mode 100644 index 00000000..f10067ac --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/DeleteCommentResponseTests.kt @@ -0,0 +1,22 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.core.common.io.stream.StreamInput + +class DeleteCommentResponseTests { + @Test + fun `test delete comment response writing and parsing`() { + val res = DeleteCommentResponse(id = "123") + assertNotNull(res) + assertEquals("123", res.commentId) + + val out = BytesStreamOutput() + res.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newRes = DeleteCommentResponse(sin) + assertEquals("123", newRes.commentId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequestTests.kt new file mode 100644 index 00000000..1ed540c4 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentRequestTests.kt @@ -0,0 +1,42 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.rest.RestRequest + +class IndexCommentRequestTests { + @Test + fun `test index comment post request`() { + val req = IndexCommentRequest("123", "456", 1L, 2L, RestRequest.Method.POST, "comment") + assertNotNull(req) + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexCommentRequest(sin) + assertEquals("123", newReq.entityId) + assertEquals("456", newReq.commentId) + assertEquals(1L, newReq.seqNo) + assertEquals(2L, newReq.primaryTerm) + assertEquals(RestRequest.Method.POST, newReq.method) + assertEquals("comment", newReq.content) + } + + @Test + fun `test index comment put request`() { + val req = IndexCommentRequest("123", "456", 1L, 2L, RestRequest.Method.PUT, "comment") + assertNotNull(req) + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexCommentRequest(sin) + assertEquals("123", newReq.entityId) + assertEquals("456", newReq.commentId) + assertEquals(1L, newReq.seqNo) + assertEquals(2L, newReq.primaryTerm) + assertEquals(RestRequest.Method.PUT, newReq.method) + assertEquals("comment", newReq.content) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponseTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponseTests.kt new file mode 100644 index 00000000..834cc972 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/IndexCommentResponseTests.kt @@ -0,0 +1,34 @@ +package org.opensearch.commons.alerting.action + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.commons.alerting.model.Comment +import org.opensearch.commons.alerting.randomUser +import org.opensearch.core.common.io.stream.StreamInput +import java.time.Instant + +class IndexCommentResponseTests { + @Test + fun `test index comment response with comment`() { + val comment = Comment( + "123", + "456", + "comment", + Instant.now(), + Instant.now(), + randomUser() + ) + val req = IndexCommentResponse("1234", 1L, 2L, comment) + Assertions.assertNotNull(req) + + val out = BytesStreamOutput() + req.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = IndexCommentResponse(sin) + Assertions.assertEquals("1234", newReq.id) + Assertions.assertEquals(1L, newReq.seqNo) + Assertions.assertEquals(2L, newReq.primaryTerm) + Assertions.assertNotNull(newReq.comment) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequestTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequestTests.kt new file mode 100644 index 00000000..596d16c4 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/alerting/action/SearchCommentRequestTests.kt @@ -0,0 +1,27 @@ +package org.opensearch.commons.alerting.action + +import org.opensearch.action.search.SearchRequest +import org.opensearch.common.io.stream.BytesStreamOutput +import org.opensearch.common.unit.TimeValue +import org.opensearch.core.common.io.stream.StreamInput +import org.opensearch.search.builder.SearchSourceBuilder +import org.opensearch.test.OpenSearchTestCase +import org.opensearch.test.rest.OpenSearchRestTestCase +import java.util.concurrent.TimeUnit + +class SearchCommentRequestTests : OpenSearchTestCase() { + fun `test search comments request`() { + val searchSourceBuilder = SearchSourceBuilder().from(0).size(100).timeout(TimeValue(60, TimeUnit.SECONDS)) + val searchRequest = SearchRequest().indices(OpenSearchRestTestCase.randomAlphaOfLength(10)).source(searchSourceBuilder) + val searchCommentRequest = SearchCommentRequest(searchRequest) + assertNotNull(searchCommentRequest) + + val out = BytesStreamOutput() + searchCommentRequest.writeTo(out) + val sin = StreamInput.wrap(out.bytes().toBytesRef().bytes) + val newReq = SearchCommentRequest(sin) + + assertNotNull(newReq.searchRequest) + assertEquals(1, newReq.searchRequest.indices().size) + } +}