From 96aa4270857747d2f17ea2a87f066222d2b3bc6e Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 18 Oct 2021 14:46:28 -0700 Subject: [PATCH] Add models for objects and requests Signed-off-by: Joshua Li --- .../model/CreateObservabilityObjectRequest.kt | 142 +++++ .../CreateObservabilityObjectResponse.kt | 102 ++++ .../model/DeleteObservabilityObjectRequest.kt | 122 +++++ .../DeleteObservabilityObjectResponse.kt | 120 +++++ .../model/GetObservabilityObjectRequest.kt | 198 +++++++ .../model/GetObservabilityObjectResponse.kt | 85 +++ .../observability/model/Notebook.kt | 464 ++++++++++++++++ .../ObservabilityObjectDataProperties.kt | 78 +++ .../model/ObservabilityObjectDoc.kt | 153 ++++++ .../model/ObservabilityObjectDocInfo.kt | 14 + .../model/ObservabilityObjectSearchResult.kt | 77 +++ .../model/ObservabilityObjectType.kt | 77 +++ .../observability/model/OperationalPanel.kt | 501 ++++++++++++++++++ .../opensearch/observability/model/RestTag.kt | 61 +++ .../observability/model/SavedQuery.kt | 417 +++++++++++++++ .../observability/model/SavedVisualization.kt | 179 +++++++ .../observability/model/SearchResults.kt | 220 ++++++++ .../observability/model/Timestamp.kt | 134 +++++ .../model/UpdateObservabilityObjectRequest.kt | 148 ++++++ .../UpdateObservabilityObjectResponse.kt | 103 ++++ 20 files changed, 3395 insertions(+) create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectRequest.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectResponse.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectRequest.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectResponse.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectRequest.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectResponse.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Notebook.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDataProperties.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDoc.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDocInfo.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectSearchResult.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectType.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/OperationalPanel.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/RestTag.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedQuery.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedVisualization.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SearchResults.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Timestamp.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectRequest.kt create mode 100644 opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectResponse.kt diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectRequest.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectRequest.kt new file mode 100644 index 000000000..8ceaaea52 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectRequest.kt @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import java.io.IOException + +/** + * Action request for creating new configuration. + */ +internal class CreateObservabilityObjectRequest : ActionRequest, ToXContentObject { + val objectId: String? + val type: ObservabilityObjectType + val objectData: BaseObjectData? + + companion object { + private val log by logger(CreateObservabilityObjectRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { CreateObservabilityObjectRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + * @param id optional id to use if missed in XContent + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser, id: String? = null): CreateObservabilityObjectRequest { + var objectId: String? = id + var type: ObservabilityObjectType? = null + var baseObjectData: BaseObjectData? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_FIELD -> objectId = parser.text() + else -> { + val objectTypeForTag = ObservabilityObjectType.fromTagOrDefault(fieldName) + if (objectTypeForTag != ObservabilityObjectType.NONE && baseObjectData == null) { + baseObjectData = + ObservabilityObjectDataProperties.createObjectData(objectTypeForTag, parser) + type = objectTypeForTag + } else { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing CreateObservabilityObjectRequest") + } + } + } + } + type ?: throw IllegalArgumentException("Object data field absent") + baseObjectData ?: throw IllegalArgumentException("Object data field absent") + return CreateObservabilityObjectRequest(objectId, type, baseObjectData) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .fieldIfNotNull(OBJECT_ID_FIELD, objectId) + .field(type.tag, objectData) + .endObject() + } + + /** + * constructor for creating the class + * @param objectId optional id to use for ObservabilityObject + * @param type type of ObservabilityObject + * @param objectData the ObservabilityObject + */ + constructor(objectId: String? = null, type: ObservabilityObjectType, objectData: BaseObjectData) { + this.objectId = objectId + this.type = type + this.objectData = objectData + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectId = input.readOptionalString() + type = input.readEnum(ObservabilityObjectType::class.java) + objectData = input.readOptionalWriteable( + ObservabilityObjectDataProperties.getReaderForObjectType( + input.readEnum( + ObservabilityObjectType::class.java + ) + ) + ) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeOptionalString(objectId) + output.writeEnum(type) + output.writeOptionalWriteable(objectData) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + return null + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectResponse.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectResponse.kt new file mode 100644 index 000000000..528c6cf95 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/CreateObservabilityObjectResponse.kt @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.logger +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +internal class CreateObservabilityObjectResponse : BaseResponse { + val objectId: String + + companion object { + private val log by logger(CreateObservabilityObjectResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { CreateObservabilityObjectResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): CreateObservabilityObjectResponse { + var objectId: String? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_FIELD -> objectId = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing CreateObservabilityObjectResponse") + } + } + } + objectId ?: throw IllegalArgumentException("$OBJECT_ID_FIELD field absent") + return CreateObservabilityObjectResponse(objectId) + } + } + + /** + * constructor for creating the class + * @param id the id of the created ObservabilityObject + */ + constructor(id: String) { + this.objectId = id + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectId = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeString(objectId) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(OBJECT_ID_FIELD, objectId) + .endObject() + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectRequest.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectRequest.kt new file mode 100644 index 000000000..8dfa6c2b3 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectRequest.kt @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import org.opensearch.observability.model.RestTag.OBJECT_ID_LIST_FIELD +import java.io.IOException + +/** + * Action request for creating new configuration. + */ +internal class DeleteObservabilityObjectRequest : ActionRequest, ToXContentObject { + val objectIds: Set + + companion object { + private val log by logger(DeleteObservabilityObjectRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { DeleteObservabilityObjectRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + * @param id optional id to use if missed in XContent + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): DeleteObservabilityObjectRequest { + var objectIds: Set? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_LIST_FIELD -> objectIds = parser.stringList().toSet() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + objectIds ?: throw IllegalArgumentException("$OBJECT_ID_FIELD field absent") + return DeleteObservabilityObjectRequest(objectIds) + } + } + + /** + * constructor for creating the class + * @param objectIds the id of the observability object + */ + constructor(objectIds: Set) { + this.objectIds = objectIds + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectIds = input.readStringList().toSet() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeStringCollection(objectIds) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(OBJECT_ID_LIST_FIELD, objectIds) + .endObject() + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (objectIds.isNullOrEmpty()) { + validationException = ValidateActions.addValidationError("objectIds is null or empty", validationException) + } + return validationException + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectResponse.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectResponse.kt new file mode 100644 index 000000000..f5f699fb5 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/DeleteObservabilityObjectResponse.kt @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.enumReader +import org.opensearch.commons.utils.enumWriter +import org.opensearch.commons.utils.logger +import org.opensearch.observability.model.RestTag.DELETE_RESPONSE_LIST_TAG +import org.opensearch.rest.RestStatus +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +internal class DeleteObservabilityObjectResponse : BaseResponse { + val objectIdToStatus: Map + + companion object { + private val log by logger(DeleteObservabilityObjectResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { DeleteObservabilityObjectResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): DeleteObservabilityObjectResponse { + var objectIdToStatus: Map? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + DELETE_RESPONSE_LIST_TAG -> objectIdToStatus = convertMapStrings(parser.mapStrings()) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing DeleteObservabilityObjectResponse") + } + } + } + objectIdToStatus ?: throw IllegalArgumentException("$DELETE_RESPONSE_LIST_TAG field absent") + return DeleteObservabilityObjectResponse(objectIdToStatus) + } + + private fun convertMapStrings(inputMap: Map): Map { + return inputMap.mapValues { RestStatus.valueOf(it.value) } + } + } + + /** + * constructor for creating the class + * @param objectIdToStatus the ids of the deleted observability object with status + */ + constructor(objectIdToStatus: Map) { + this.objectIdToStatus = objectIdToStatus + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectIdToStatus = input.readMap(STRING_READER, enumReader(RestStatus::class.java)) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeMap(objectIdToStatus, STRING_WRITER, enumWriter(RestStatus::class.java)) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(DELETE_RESPONSE_LIST_TAG, objectIdToStatus) + .endObject() + } + + override fun getStatus(): RestStatus { + val distinctStatus = objectIdToStatus.values.distinct() + return when { + distinctStatus.size > 1 -> RestStatus.MULTI_STATUS + distinctStatus.size == 1 -> distinctStatus[0] + else -> RestStatus.NOT_MODIFIED + } + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectRequest.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectRequest.kt new file mode 100644 index 000000000..d026a94d5 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectRequest.kt @@ -0,0 +1,198 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.enumReader +import org.opensearch.commons.utils.enumSet +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.observability.model.RestTag.FILTER_PARAM_LIST_FIELD +import org.opensearch.observability.model.RestTag.FROM_INDEX_FIELD +import org.opensearch.observability.model.RestTag.MAX_ITEMS_FIELD +import org.opensearch.observability.model.RestTag.OBJECT_ID_LIST_FIELD +import org.opensearch.observability.model.RestTag.OBJECT_TYPE_FIELD +import org.opensearch.observability.model.RestTag.SORT_FIELD_FIELD +import org.opensearch.observability.model.RestTag.SORT_ORDER_FIELD +import org.opensearch.observability.settings.PluginSettings +import org.opensearch.search.sort.SortOrder +import java.io.IOException +import java.util.EnumSet + +/** + * Action Request for getting ObservabilityObject. + */ +class GetObservabilityObjectRequest : ActionRequest, ToXContentObject { + val objectIds: Set + val types: EnumSet + val fromIndex: Int + val maxItems: Int + val sortField: String? + val sortOrder: SortOrder? + val filterParams: Map + + companion object { + private val log by logger(GetObservabilityObjectRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetObservabilityObjectRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetObservabilityObjectRequest { + var objectIdList: Set = setOf() + var types: EnumSet = EnumSet.noneOf(ObservabilityObjectType::class.java) + var fromIndex = 0 + var maxItems = PluginSettings.defaultItemsQueryCount + var sortField: String? = null + var sortOrder: SortOrder? = null + var filterParams: Map = mapOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_LIST_FIELD -> objectIdList = parser.stringList().toSet() + OBJECT_TYPE_FIELD -> types = parser.enumSet(ObservabilityObjectType.enumParser) + FROM_INDEX_FIELD -> fromIndex = parser.intValue() + MAX_ITEMS_FIELD -> maxItems = parser.intValue() + SORT_FIELD_FIELD -> sortField = parser.text() + SORT_ORDER_FIELD -> sortOrder = SortOrder.fromString(parser.text()) + FILTER_PARAM_LIST_FIELD -> filterParams = parser.mapStrings() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing GetObservabilityObjectRequest") + } + } + } + return GetObservabilityObjectRequest( + objectIdList, + types, + fromIndex, + maxItems, + sortField, + sortOrder, + filterParams + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(OBJECT_ID_LIST_FIELD, objectIds) + .field(OBJECT_TYPE_FIELD, types) + .field(FROM_INDEX_FIELD, fromIndex) + .field(MAX_ITEMS_FIELD, maxItems) + .fieldIfNotNull(SORT_FIELD_FIELD, sortField) + .fieldIfNotNull(SORT_ORDER_FIELD, sortOrder) + .field(FILTER_PARAM_LIST_FIELD, filterParams) + .endObject() + } + + /** + * constructor for creating the class + * @param objectIds the ids of the observability objects (other parameters are not relevant if ids are present) + * @param fromIndex the starting index for paginated response + * @param maxItems the maximum number of items to return for paginated response + * @param sortField the sort field if response has many items + * @param sortOrder the sort order if response has many items + * @param filterParams the filter parameters + */ + @Suppress("LongParameterList") + constructor( + objectIds: Set = setOf(), + types: EnumSet = EnumSet.noneOf(ObservabilityObjectType::class.java), + fromIndex: Int = 0, + maxItems: Int = PluginSettings.defaultItemsQueryCount, + sortField: String? = null, + sortOrder: SortOrder? = null, + filterParams: Map = mapOf() + ) { + this.objectIds = objectIds + this.types = types + this.fromIndex = fromIndex + this.maxItems = maxItems + this.sortField = sortField + this.sortOrder = sortOrder + this.filterParams = filterParams + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectIds = input.readStringList().toSet() + types = input.readEnumSet(ObservabilityObjectType::class.java) + fromIndex = input.readInt() + maxItems = input.readInt() + sortField = input.readOptionalString() + sortOrder = input.readOptionalWriteable(enumReader(SortOrder::class.java)) + filterParams = input.readMap(STRING_READER, STRING_READER) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeStringCollection(objectIds) + output.writeEnumSet(types) + output.writeInt(fromIndex) + output.writeInt(maxItems) + output.writeOptionalString(sortField) + output.writeOptionalWriteable(sortOrder) + output.writeMap(filterParams, STRING_WRITER, STRING_WRITER) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (fromIndex < 0) { + validationException = ValidateActions.addValidationError("fromIndex is -ve", validationException) + } + if (maxItems <= 0) { + validationException = ValidateActions.addValidationError("maxItems is not +ve", validationException) + } + return validationException + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectResponse.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectResponse.kt new file mode 100644 index 000000000..0ed1247e3 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/GetObservabilityObjectResponse.kt @@ -0,0 +1,85 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import java.io.IOException + +/** + * Action Response for getting ObservabilityObject. + */ +internal class GetObservabilityObjectResponse : BaseResponse { + val searchResult: ObservabilityObjectSearchResult + private val filterSensitiveInfo: Boolean + + companion object { + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetObservabilityObjectResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetObservabilityObjectResponse { + return GetObservabilityObjectResponse(ObservabilityObjectSearchResult(parser), false) + } + } + + /** + * constructor for creating the class + * @param searchResult the ObservabilityObject list + */ + constructor(searchResult: ObservabilityObjectSearchResult, filterSensitiveInfo: Boolean) { + this.searchResult = searchResult + this.filterSensitiveInfo = filterSensitiveInfo + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + searchResult = ObservabilityObjectSearchResult(input) + filterSensitiveInfo = input.readBoolean() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + searchResult.writeTo(output) + output.writeBoolean(filterSensitiveInfo) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + val xContentParams = if (filterSensitiveInfo) { + RestTag.FILTERED_REST_OUTPUT_PARAMS + } else { + RestTag.REST_OUTPUT_PARAMS + } + return searchResult.toXContent(builder, xContentParams) + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Notebook.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Notebook.kt new file mode 100644 index 000000000..0418850fc --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Notebook.kt @@ -0,0 +1,464 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.util.fieldIfNotNull +import org.opensearch.observability.util.logger + +/** + * Notebook main data class. + * *
 JSON format
+ * {@code
+ * {
+ *   "dateCreated" : "2020-12-11T20:51:15.509Z",
+ *   "name" : "test",
+ *   "dateModified" : "2020-12-11T21:04:55.336Z",
+ *   "backend" : "Default",
+ *   "paragraphs" : [
+ *     {
+ *       "output" : [
+ *         {
+ *           "result" : "# This is a markdown paragraph",
+ *           "outputType" : "MARKDOWN",
+ *           "execution_time" : "0s"
+ *         }
+ *       ],
+ *       "input" : {
+ *         "inputText" : "# This is a markdown paragraph",
+ *         "inputType" : "MARKDOWN"
+ *       },
+ *       "dateCreated" : "2020-12-11T21:04:39.997Z",
+ *       "dateModified" : "2020-12-11T21:04:48.207Z",
+ *       "id" : "paragraph_61e96a10-af19-4c7d-ae4e-d2e449c65dff"
+ *     }
+ *   ]
+ * }
+ * }
+ */ + +internal data class Notebook( + val name: String?, + val dateCreated: String?, + val dateModified: String?, + val backend: String?, + val paragraphs: List? +) : BaseObjectData { + + internal companion object { + private val log by logger(Notebook::class.java) + private const val NAME_TAG = "name" + private const val DATE_CREATED_TAG = "dateCreated" + private const val DATE_MODIFIED_TAG = "dateModified" + private const val BACKEND_TAG = "backend" + private const val PARAGRAPHS_TAG = "paragraphs" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Notebook(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(Paragraph.parse(parser)) + } + return retList + } + + /** + * Parse the data from parser and create Notebook object + * @param parser data referenced at parser + * @return created Notebook object + */ + fun parse(parser: XContentParser): Notebook { + var name: String? = null + var dateCreated: String? = null + var dateModified: String? = null + var backend: String? = null + var paragraphs: List? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + DATE_CREATED_TAG -> dateCreated = parser.text() + DATE_MODIFIED_TAG -> dateModified = parser.text() + BACKEND_TAG -> backend = parser.text() + PARAGRAPHS_TAG -> paragraphs = parseItemList(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Notebook Skipping Unknown field $fieldName") + } + } + } + return Notebook(name, dateCreated, dateModified, backend, paragraphs) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + dateCreated = input.readString(), + dateModified = input.readString(), + backend = input.readString(), + paragraphs = input.readList(Paragraph.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(dateCreated) + output.writeString(dateModified) + output.writeString(backend) + output.writeCollection(paragraphs) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + val xContentParams = params ?: RestTag.REST_OUTPUT_PARAMS + builder!! + builder.startObject() + .fieldIfNotNull(NAME_TAG, name) + .fieldIfNotNull(DATE_CREATED_TAG, dateCreated) + .fieldIfNotNull(DATE_MODIFIED_TAG, dateModified) + .fieldIfNotNull(BACKEND_TAG, backend) + if (paragraphs != null) { + builder.startArray(PARAGRAPHS_TAG) + paragraphs.forEach { it.toXContent(builder, xContentParams) } + builder.endArray() + } + return builder.endObject() + } + + /** + * Notebook source data class + */ + internal data class Paragraph( + val output: List, + val input: Input?, + val dateCreated: String, + val dateModified: String, + val id: String + ) : BaseModel { + internal companion object { + private const val OUTPUT_TAG = "output" + private const val INPUT_TAG = "input" + private const val DATE_CREATED_TAG = "dateCreated" + private const val DATE_MODIFIED_TAG = "dateModified" + private const val ID_TAG = "id" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Paragraph(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(Output.parse(parser)) + } + return retList + } + + /** + * Parse the data from parser and create Source object + * @param parser data referenced at parser + * @return created Source object + */ + fun parse(parser: XContentParser): Paragraph { + var output: List? = null + var input: Input? = null + var dateCreated: String? = null + var dateModified: String? = null + var id: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OUTPUT_TAG -> output = parseItemList(parser) + INPUT_TAG -> input = Input.parse(parser) + DATE_CREATED_TAG -> dateCreated = parser.text() + DATE_MODIFIED_TAG -> dateModified = parser.text() + ID_TAG -> id = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Source Skipping Unknown field $fieldName") + } + } + } + output ?: throw IllegalArgumentException("$OUTPUT_TAG field absent") + input ?: throw IllegalArgumentException("$INPUT_TAG field absent") + dateCreated ?: throw IllegalArgumentException("$DATE_CREATED_TAG field absent") + dateModified ?: throw IllegalArgumentException("$DATE_MODIFIED_TAG field absent") + id ?: throw IllegalArgumentException("$ID_TAG field absent") + return Paragraph(output, input, dateCreated, dateModified, id) + } + } + + constructor(streamInput: StreamInput) : this( + output = streamInput.readList(Output.reader), + input = streamInput.readOptionalWriteable(Input.reader), + dateCreated = streamInput.readString(), + dateModified = streamInput.readString(), + id = streamInput.readString() + ) + + override fun writeTo(streamOutput: StreamOutput) { + streamOutput.writeCollection(output) + streamOutput.writeOptionalWriteable(input) + streamOutput.writeString(dateCreated) + streamOutput.writeString(dateModified) + streamOutput.writeString(id) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + val xContentParams = params ?: RestTag.REST_OUTPUT_PARAMS + builder!! + builder.startObject() + .startArray(OUTPUT_TAG) + output.forEach { it.toXContent(builder, xContentParams) } + builder.endArray() + .field(INPUT_TAG, input) + .field(DATE_CREATED_TAG, dateCreated) + .field(DATE_MODIFIED_TAG, dateModified) + .field(ID_TAG, id) + return builder.endObject() + } + } + + /** + * Notebook output data class + */ + internal data class Output( + val result: String?, + val outputType: String?, + val executionTime: String? + ) : BaseModel { + internal companion object { + private const val RESULT_TAG = "result" + private const val OUTPUT_TYPE_TAG = "outputType" + private const val EXECUTION_TIME_TAG = "execution_time" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Output(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Format object + * @param parser data referenced at parser + * @return created Format object + */ + fun parse(parser: XContentParser): Output { + var result: String? = null + var outputType: String? = null + var executionTime: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + RESULT_TAG -> result = parser.text() + OUTPUT_TYPE_TAG -> outputType = parser.text() + EXECUTION_TIME_TAG -> executionTime = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Format Skipping Unknown field $fieldName") + } + } + } + result ?: throw IllegalArgumentException("$RESULT_TAG field absent") + outputType ?: throw IllegalArgumentException("$OUTPUT_TYPE_TAG field absent") + executionTime ?: throw IllegalArgumentException("$EXECUTION_TIME_TAG field absent") + return Output(result, outputType, executionTime) + } + } + + constructor(input: StreamInput) : this( + result = input.readString(), + outputType = input.readString(), + executionTime = input.readString() + ) + + override fun writeTo(output: StreamOutput) { + output.writeString(result) + output.writeString(outputType) + output.writeString(executionTime) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(RESULT_TAG, result) + .field(OUTPUT_TYPE_TAG, outputType) + .field(EXECUTION_TIME_TAG, executionTime) + builder.endObject() + return builder + } + } + + /** + * Notebook input data class + */ + internal data class Input( + val inputText: String?, + val inputType: String? + ) : BaseModel { + internal companion object { + private const val INPUT_TEXT_TAG = "inputText" + private const val INPUT_TYPE_TAG = "inputType" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Input(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Trigger object + * @param parser data referenced at parser + * @return created Trigger object + */ + fun parse(parser: XContentParser): Input { + var inputText: String? = null + var inputType: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + INPUT_TEXT_TAG -> inputText = parser.text() + INPUT_TYPE_TAG -> inputType = parser.text() + else -> log.info("$LOG_PREFIX: Trigger Skipping Unknown field $fieldName") + } + } + inputText ?: throw IllegalArgumentException("$INPUT_TEXT_TAG field absent") + inputType ?: throw IllegalArgumentException("$INPUT_TYPE_TAG field absent") + return Input(inputText, inputType) + } + } + + constructor(input: StreamInput) : this( + inputText = input.readString(), + inputType = input.readString() + ) + + override fun writeTo(output: StreamOutput) { + output.writeString(inputText) + output.writeString(inputType) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(INPUT_TEXT_TAG, inputText) + .field(INPUT_TYPE_TAG, inputType) + builder.endObject() + return builder + } + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDataProperties.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDataProperties.kt new file mode 100644 index 000000000..c64f7fce5 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDataProperties.kt @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.XContentParser + +internal object ObservabilityObjectDataProperties { + /** + * Properties for ConfigTypes. + * This data class is used to provide contract across configTypes without reading into config data classes. + */ + private data class ObjectProperty( + val objectDataReader: Writeable.Reader?, + val objectDataParser: XParser + ) + + private val OBJECT_PROPERTIES_MAP = mapOf( + Pair(ObservabilityObjectType.NOTEBOOK, ObjectProperty(Notebook.reader, Notebook.xParser)), + Pair(ObservabilityObjectType.SAVED_QUERY, ObjectProperty(SavedQuery.reader, SavedQuery.xParser)), + Pair( + ObservabilityObjectType.SAVED_VISUALIZATION, + ObjectProperty(SavedVisualization.reader, SavedVisualization.xParser) + ), + Pair( + ObservabilityObjectType.OPERATIONAL_PANEL, + ObjectProperty(OperationalPanel.reader, OperationalPanel.xParser) + ), + Pair( + ObservabilityObjectType.TIMESTAMP, + ObjectProperty(Timestamp.reader, Timestamp.xParser) + ) + ) + + /** + * Get Reader for provided config type + * @param @ConfigType + * @return Reader + */ + fun getReaderForObjectType(objectType: ObservabilityObjectType): Writeable.Reader { + return OBJECT_PROPERTIES_MAP[objectType]?.objectDataReader + ?: throw IllegalArgumentException("Transport action used with unknown ConfigType:$objectType") + } + + /** + * Validate config data is of ConfigType + */ + fun validateObjectData(objectType: ObservabilityObjectType, objectData: BaseObjectData?): Boolean { + return when (objectType) { + ObservabilityObjectType.NOTEBOOK -> objectData is Notebook + ObservabilityObjectType.SAVED_QUERY -> objectData is SavedQuery + ObservabilityObjectType.SAVED_VISUALIZATION -> objectData is SavedVisualization + ObservabilityObjectType.OPERATIONAL_PANEL -> objectData is OperationalPanel + ObservabilityObjectType.TIMESTAMP -> objectData is Timestamp + ObservabilityObjectType.NONE -> true + } + } + + /** + * Creates config data from parser for given configType + * @param objectType the ConfigType + * @param parser parser for ConfigType + * @return created BaseObjectData on success. null if configType is not recognized + * + */ + fun createObjectData(objectType: ObservabilityObjectType, parser: XContentParser): BaseObjectData? { + return OBJECT_PROPERTIES_MAP[objectType]?.objectDataParser?.parse(parser) + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDoc.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDoc.kt new file mode 100644 index 000000000..db44d6d89 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDoc.kt @@ -0,0 +1,153 @@ +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.observability.model.ObservabilityObjectDataProperties.getReaderForObjectType +import org.opensearch.observability.model.RestTag.ACCESS_LIST_FIELD +import org.opensearch.observability.model.RestTag.CREATED_TIME_FIELD +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import org.opensearch.observability.model.RestTag.TENANT_FIELD +import org.opensearch.observability.model.RestTag.UPDATED_TIME_FIELD +import org.opensearch.observability.security.UserAccessManager +import java.io.IOException +import java.time.Instant + +/** + * Data class representing ObservabilityObject. + */ +data class ObservabilityObjectDoc( + val objectId: String, + val updatedTime: Instant, + val createdTime: Instant, + val tenant: String, + val access: List, // "User:user", "Role:sample_role", "BERole:sample_backend_role" + val type: ObservabilityObjectType, + val objectData: BaseObjectData? +) : BaseModel { + + companion object { + private val log by logger(ObservabilityObjectDoc::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { ObservabilityObjectDoc(it) } + + /** + * Parse the data from parser and create object + * @param parser data referenced at parser + * @return created object + */ + @JvmStatic + @Throws(IOException::class) + @Suppress("ComplexMethod") + fun parse(parser: XContentParser, useId: String? = null): ObservabilityObjectDoc { + var objectId: String? = useId + var updatedTime: Instant? = null + var createdTime: Instant? = null + var tenant: String? = null + var access: List = listOf() + var type: ObservabilityObjectType? = null + var objectData: BaseObjectData? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_FIELD -> objectId = parser.text() + UPDATED_TIME_FIELD -> updatedTime = Instant.ofEpochMilli(parser.longValue()) + CREATED_TIME_FIELD -> createdTime = Instant.ofEpochMilli(parser.longValue()) + TENANT_FIELD -> tenant = parser.text() + ACCESS_LIST_FIELD -> access = parser.stringList() + else -> { + val objectTypeForTag = ObservabilityObjectType.fromTagOrDefault(fieldName) + if (objectTypeForTag != ObservabilityObjectType.NONE && objectData == null) { + objectData = + ObservabilityObjectDataProperties.createObjectData(objectTypeForTag, parser) + type = objectTypeForTag + } else { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing ObservabilityObjectDoc") + } + } + } + } + objectId ?: throw IllegalArgumentException("$OBJECT_ID_FIELD field absent") + updatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_FIELD field absent") + createdTime ?: throw IllegalArgumentException("$CREATED_TIME_FIELD field absent") + tenant = tenant ?: UserAccessManager.DEFAULT_TENANT + type ?: throw IllegalArgumentException("Object data field absent") + objectData ?: throw IllegalArgumentException("Object data field absent") + return ObservabilityObjectDoc(objectId, updatedTime, createdTime, tenant, access, type, objectData) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + objectId = input.readString(), + updatedTime = input.readInstant(), + createdTime = input.readInstant(), + tenant = input.readString(), + access = input.readStringList(), + type = input.readEnum(ObservabilityObjectType::class.java), + objectData = input.readOptionalWriteable(getReaderForObjectType(input.readEnum(ObservabilityObjectType::class.java))) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(objectId) + output.writeInstant(updatedTime) + output.writeInstant(createdTime) + output.writeString(tenant) + output.writeStringCollection(access) + output.writeEnum(type) + output.writeOptionalWriteable(objectData) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + if (params?.paramAsBoolean(OBJECT_ID_FIELD, false) == true) { + builder.field(OBJECT_ID_FIELD, objectId) + } + builder.field(UPDATED_TIME_FIELD, updatedTime.toEpochMilli()) + .field(CREATED_TIME_FIELD, createdTime.toEpochMilli()) + .field(TENANT_FIELD, tenant) + if (params?.paramAsBoolean(ACCESS_LIST_FIELD, true) == true && access.isNotEmpty()) { + builder.field(ACCESS_LIST_FIELD, access) + } + builder.field(type.tag, objectData) + .endObject() + return builder + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDocInfo.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDocInfo.kt new file mode 100644 index 000000000..f6dc577a6 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectDocInfo.kt @@ -0,0 +1,14 @@ +package org.opensearch.observability.model + +import org.opensearch.index.seqno.SequenceNumbers + +/** + * Class for storing the observability object document with document properties. + */ +data class ObservabilityObjectDocInfo( + val id: String? = null, + val version: Long = -1L, + val seqNo: Long = SequenceNumbers.UNASSIGNED_SEQ_NO, + val primaryTerm: Long = SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + val observabilityObjectDoc: ObservabilityObjectDoc +) diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectSearchResult.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectSearchResult.kt new file mode 100644 index 000000000..3a4cc085a --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectSearchResult.kt @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.apache.lucene.search.TotalHits +import org.opensearch.action.search.SearchResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.observability.model.RestTag.OBJECT_LIST_FIELD + +/** + * ObservabilityObject search results + */ +internal class ObservabilityObjectSearchResult : SearchResults { + + /** + * single item result constructor + */ + constructor(objectItem: ObservabilityObjectDoc) : super(OBJECT_LIST_FIELD, objectItem) + + /** + * multiple items result constructor + */ + constructor(objectList: List) : this( + 0, + objectList.size.toLong(), + TotalHits.Relation.EQUAL_TO, + objectList + ) + + /** + * all param constructor + */ + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: TotalHits.Relation, + objectList: List + ) : super(startIndex, totalHits, totalHitRelation, OBJECT_LIST_FIELD, objectList) + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : super(input, ObservabilityObjectDoc.reader) + + /** + * Construct object from XContentParser + */ + constructor(parser: XContentParser) : super(parser, OBJECT_LIST_FIELD) + + /** + * Construct object from SearchResponse + */ + constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( + from, + response, + searchHitParser, + OBJECT_LIST_FIELD + ) + + /** + * {@inheritDoc} + */ + override fun parseItem(parser: XContentParser, useId: String?): ObservabilityObjectDoc { + return ObservabilityObjectDoc.parse(parser, useId) + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectType.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectType.kt new file mode 100644 index 000000000..9305013ab --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/ObservabilityObjectType.kt @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.commons.utils.EnumParser +import org.opensearch.observability.model.RestTag.NOTEBOOK_FIELD +import org.opensearch.observability.model.RestTag.OPERATIONAL_PANEL_FIELD +import org.opensearch.observability.model.RestTag.SAVED_QUERY_FIELD +import org.opensearch.observability.model.RestTag.SAVED_VISUALIZATION_FIELD +import org.opensearch.observability.model.RestTag.TIMESTAMP_FIELD +import java.util.EnumSet + +/** + * Enum for ObservabilityObject type + */ +enum class ObservabilityObjectType(val tag: String) { + NONE("none") { + override fun toString(): String { + return tag + } + }, + NOTEBOOK(NOTEBOOK_FIELD) { + override fun toString(): String { + return tag + } + }, + SAVED_QUERY(SAVED_QUERY_FIELD) { + override fun toString(): String { + return tag + } + }, + SAVED_VISUALIZATION(SAVED_VISUALIZATION_FIELD) { + override fun toString(): String { + return tag + } + }, + OPERATIONAL_PANEL(OPERATIONAL_PANEL_FIELD) { + override fun toString(): String { + return tag + } + }, + TIMESTAMP(TIMESTAMP_FIELD) { + override fun toString(): String { + return tag + } + }; + + companion object { + private val tagMap = values().associateBy { it.tag } + + val enumParser = EnumParser { fromTagOrDefault(it) } + + /** + * Get ConfigType from tag or NONE if not found + * @param tag the tag + * @return ConfigType corresponding to tag. NONE if invalid tag. + */ + fun fromTagOrDefault(tag: String): ObservabilityObjectType { + return tagMap[tag] ?: NONE + } + + fun getAll(): EnumSet { + val allTypes = EnumSet.allOf(ObservabilityObjectType::class.java) + allTypes.remove(NONE) + return allTypes + } + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/OperationalPanel.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/OperationalPanel.kt new file mode 100644 index 000000000..c1845fcb3 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/OperationalPanel.kt @@ -0,0 +1,501 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.util.fieldIfNotNull +import org.opensearch.observability.util.logger + +/** + * OperationalPanel main data class. + * *
 JSON format
+ * {@code
+ * {
+ *   "operationalPanel": {
+ *     "name": "Demo Panel 1",
+ *     "dateCreated": "2021-07-19T21:01:14.871Z",
+ *     "dateModified": "2021-07-19T21:01:14.871Z",
+ *     "visualizations": [
+ *       {
+ *         "id": "panelViz_7ba28e34-6fd8-489d-9b9f-1f83e006fb17",
+ *         "title": "Demo Viz 1",
+ *         "x": 0,
+ *         "y": 0,
+ *         "w": 10,
+ *         "h": 10,
+ *         "query": "source=index | fields Carrier,FlightDelayMin | stats sum(FlightDelayMin) as delays by Carrier",
+ *         "timeField": "timestamp",
+ *         "type": "bar"
+ *       },
+ *       {
+ *         "id": "panelViz_7ba28e34-6fd8-489d-9b9f-165fdv6wd611",
+ *         "title": "Demo Viz 2",
+ *         "x": 20,
+ *         "y": 20,
+ *         "w": 30,
+ *         "h": 20,
+ *         "query": "source=index | fields Carrier,Origin | stats count() by Origin",
+ *         "timeField": "utc_time",
+ *         "type": "bar"
+ *       }
+ *     ],
+ *     "timeRange": {
+ *       "to": "now",
+ *       "from": "now-1d"
+ *     },
+ *     "queryFilter": {
+ *       "query": "| where Carrier='OpenSearch-Air'",
+ *       "language": "ppl"
+ *     }
+ *   }
+ * }
+ * }
+ */ + +internal data class OperationalPanel( + val name: String?, + val dateCreated: String?, + val dateModified: String?, + val visualizations: List?, + val timeRange: TimeRange?, + val queryFilter: QueryFilter?, +) : BaseObjectData { + + internal companion object { + private val log by logger(OperationalPanel::class.java) + private const val NAME_TAG = "name" + private const val DATE_CREATED_TAG = "dateCreated" + private const val DATE_MODIFIED_TAG = "dateModified" + private const val VISUALIZATIONS_TAG = "visualizations" + private const val TIME_RANGE_TAG = "timeRange" + private const val QUERY_FILTER_TAG = "queryFilter" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { OperationalPanel(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(Visualization.parse(parser)) + } + return retList + } + + /** + * Parse the data from parser and create OperationalPanel object + * @param parser data referenced at parser + * @return created OperationalPanel object + */ + fun parse(parser: XContentParser): OperationalPanel { + var name: String? = null + var dateCreated: String? = null + var dateModified: String? = null + var visualizations: List? = null + var timeRange: TimeRange? = null + var queryFilter: QueryFilter? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + DATE_CREATED_TAG -> dateCreated = parser.text() + DATE_MODIFIED_TAG -> dateModified = parser.text() + VISUALIZATIONS_TAG -> visualizations = parseItemList(parser) + TIME_RANGE_TAG -> timeRange = TimeRange.parse(parser) + QUERY_FILTER_TAG -> queryFilter = QueryFilter.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:OperationalPanel Skipping Unknown field $fieldName") + } + } + } + return OperationalPanel( + name, + dateCreated, + dateModified, + visualizations, + timeRange, + queryFilter + ) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + dateCreated = input.readString(), + dateModified = input.readString(), + visualizations = input.readList(Visualization.reader), + timeRange = input.readOptionalWriteable(TimeRange.reader), + queryFilter = input.readOptionalWriteable(QueryFilter.reader), + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(dateCreated) + output.writeString(dateModified) + output.writeCollection(visualizations) + output.writeOptionalWriteable(timeRange) + output.writeOptionalWriteable(queryFilter) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + val xContentParams = params ?: RestTag.REST_OUTPUT_PARAMS + builder!! + builder.startObject() + .fieldIfNotNull(NAME_TAG, name) + .fieldIfNotNull(DATE_CREATED_TAG, dateCreated) + .fieldIfNotNull(DATE_MODIFIED_TAG, dateModified) + if (visualizations != null) { + builder.startArray(VISUALIZATIONS_TAG) + visualizations.forEach { it.toXContent(builder, xContentParams) } + builder.endArray() + } + builder.fieldIfNotNull(TIME_RANGE_TAG, timeRange) + .fieldIfNotNull(QUERY_FILTER_TAG, queryFilter) + return builder.endObject() + } + + /** + * OperationalPanel visualization data class + */ + internal data class Visualization( + val id: String, + val title: String, + val x: Int, + val y: Int, + val w: Int, + val h: Int, + val query: String, + val timeField: String, + val type: String + ) : BaseModel { + internal companion object { + private const val ID_TAG = "id" + private const val TITLE_TAG = "title" + private const val X_TAG = "x" + private const val Y_TAG = "y" + private const val W_TAG = "w" + private const val H_TAG = "h" + private const val QUERY_TAG = "query" + private const val TIME_FIELD_TAG = "timeField" + private const val TYPE_TAG = "type" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Visualization(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Source object + * @param parser data referenced at parser + * @return created Source object + */ + @Suppress("ComplexMethod") + fun parse(parser: XContentParser): Visualization { + var id: String? = null + var title: String? = null + var x: Int? = null + var y: Int? = null + var w: Int? = null + var h: Int? = null + var query: String? = null + var timeField: String? = null + var type: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + ID_TAG -> id = parser.text() + TITLE_TAG -> title = parser.text() + X_TAG -> x = parser.intValue() + Y_TAG -> y = parser.intValue() + W_TAG -> w = parser.intValue() + H_TAG -> h = parser.intValue() + QUERY_TAG -> query = parser.text() + TIME_FIELD_TAG -> timeField = parser.text() + TYPE_TAG -> type = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Source Skipping Unknown field $fieldName") + } + } + } + id ?: throw IllegalArgumentException("$ID_TAG field absent") + title ?: throw IllegalArgumentException("$TITLE_TAG field absent") + x ?: throw IllegalArgumentException("$X_TAG field absent") + y ?: throw IllegalArgumentException("$Y_TAG field absent") + w ?: throw IllegalArgumentException("$W_TAG field absent") + h ?: throw IllegalArgumentException("$H_TAG field absent") + query ?: throw IllegalArgumentException("$QUERY_TAG field absent") + timeField ?: throw IllegalArgumentException("$TIME_FIELD_TAG field absent") + type ?: throw IllegalArgumentException("$TYPE_TAG field absent") + return Visualization(id, title, x, y, w, h, query, timeField, type) + } + } + + constructor(streamInput: StreamInput) : this( + id = streamInput.readString(), + title = streamInput.readString(), + x = streamInput.readInt(), + y = streamInput.readInt(), + w = streamInput.readInt(), + h = streamInput.readInt(), + query = streamInput.readString(), + timeField = streamInput.readString(), + type = streamInput.readString() + ) + + override fun writeTo(streamOutput: StreamOutput) { + streamOutput.writeString(id) + streamOutput.writeString(title) + streamOutput.writeInt(x) + streamOutput.writeInt(y) + streamOutput.writeInt(w) + streamOutput.writeInt(h) + streamOutput.writeString(query) + streamOutput.writeString(timeField) + streamOutput.writeString(type) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(ID_TAG, id) + .field(TITLE_TAG, title) + .field(X_TAG, x) + .field(Y_TAG, y) + .field(W_TAG, w) + .field(H_TAG, h) + .field(QUERY_TAG, query) + .field(TIME_FIELD_TAG, timeField) + .field(TYPE_TAG, type) + return builder.endObject() + } + } + + /** + * OperationalPanel TimeRange data class + */ + internal data class TimeRange( + val to: String, + val from: String + ) : BaseModel { + internal companion object { + private const val TO_TAG = "to" + private const val FROM_TAG = "from" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { TimeRange(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Format object + * @param parser data referenced at parser + * @return created Format object + */ + fun parse(parser: XContentParser): TimeRange { + var to: String? = null + var from: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + TO_TAG -> to = parser.text() + FROM_TAG -> from = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Format Skipping Unknown field $fieldName") + } + } + } + to ?: throw IllegalArgumentException("$TO_TAG field absent") + from ?: throw IllegalArgumentException("$FROM_TAG field absent") + return TimeRange(to, from) + } + } + + constructor(input: StreamInput) : this( + to = input.readString(), + from = input.readString() + ) + + override fun writeTo(output: StreamOutput) { + output.writeString(to) + output.writeString(from) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(TO_TAG, to) + .field(FROM_TAG, from) + builder.endObject() + return builder + } + } + + /** + * OperationalPanel QueryFilter data class + */ + internal data class QueryFilter( + val query: String, + val language: String + ) : BaseModel { + internal companion object { + private const val QUERY_TAG = "query" + private const val LANGUAGE_TAG = "language" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { QueryFilter(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Trigger object + * @param parser data referenced at parser + * @return created Trigger object + */ + fun parse(parser: XContentParser): QueryFilter { + var inputText: String? = null + var inputType: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + QUERY_TAG -> inputText = parser.text() + LANGUAGE_TAG -> inputType = parser.text() + else -> log.info("$LOG_PREFIX: Trigger Skipping Unknown field $fieldName") + } + } + inputText ?: throw IllegalArgumentException("$QUERY_TAG field absent") + inputType ?: throw IllegalArgumentException("$LANGUAGE_TAG field absent") + return QueryFilter(inputText, inputType) + } + } + + constructor(input: StreamInput) : this( + query = input.readString(), + language = input.readString() + ) + + override fun writeTo(output: StreamOutput) { + output.writeString(query) + output.writeString(language) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(QUERY_TAG, query) + .field(LANGUAGE_TAG, language) + builder.endObject() + return builder + } + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/RestTag.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/RestTag.kt new file mode 100644 index 000000000..3dd11bced --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/RestTag.kt @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ +package org.opensearch.observability.model + +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContent.Params + +/** + * Plugin Rest common Tags. + */ +internal object RestTag { + const val QUERY_FIELD = "query" + const val OBJECT_LIST_FIELD = "observabilityObjectList" + const val DELETE_RESPONSE_LIST_TAG = "deleteResponseList" + const val OBJECT_TYPE_FIELD = "objectType" + const val OBJECT_ID_FIELD = "objectId" + const val OBJECT_ID_LIST_FIELD = "objectIdList" + const val UPDATED_TIME_FIELD = "lastUpdatedTimeMs" + const val CREATED_TIME_FIELD = "createdTimeMs" + const val TENANT_FIELD = "tenant" + const val ACCESS_LIST_FIELD = "access" + const val NAME_FIELD = "name" + const val FROM_INDEX_FIELD = "fromIndex" + const val MAX_ITEMS_FIELD = "maxItems" + const val SORT_FIELD_FIELD = "sortField" + const val SORT_ORDER_FIELD = "sortOrder" + const val FILTER_PARAM_LIST_FIELD = "filterParamList" + const val NOTEBOOK_FIELD = "notebook" + const val SAVED_QUERY_FIELD = "savedQuery" + const val SAVED_VISUALIZATION_FIELD = "savedVisualization" + const val OPERATIONAL_PANEL_FIELD = "operationalPanel" + const val TIMESTAMP_FIELD = "timestamp" + private val INCLUDE_ID = Pair(OBJECT_ID_FIELD, "true") + private val EXCLUDE_ACCESS = Pair(ACCESS_LIST_FIELD, "false") + val REST_OUTPUT_PARAMS: Params = ToXContent.MapParams(mapOf(INCLUDE_ID)) + val FILTERED_REST_OUTPUT_PARAMS: Params = ToXContent.MapParams(mapOf(INCLUDE_ID, EXCLUDE_ACCESS)) +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedQuery.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedQuery.kt new file mode 100644 index 000000000..00dc53ddd --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedQuery.kt @@ -0,0 +1,417 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.util.fieldIfNotNull +import org.opensearch.observability.util.logger + +/** + * Saved query main data class. + * *
 JSON format
+ * {@code
+ * {
+ *   "query": "source=index | where utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00')",
+ *   "selected_date_range": {
+ *     "start": "now/15m",
+ *     "end": "now",
+ *     "text": "utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00')"
+ *   },
+ *   "selected_timestamp": {
+ *       "name": "utc_time",
+ *       "type": "timestamp"
+ *   },
+ *   "selected_fields": {
+ *       "text": "| fields clientip, bytes, memory, host",
+ *       "tokens": [
+ *           {"name":"bytes","type":"long"},
+ *           {"name":"clientip","type":"ip"}
+ *       ]
+ *   },
+ *   "name": "Logs between dates",
+ *   "description": "some descriptions related to this query"
+ * }
+ * }
+ */ + +internal data class SavedQuery( + val name: String?, + val description: String?, + val query: String?, + val selectedDateRange: SelectedDateRange?, + val selectedTimestamp: Token?, + val selectedFields: SelectedFields? +) : BaseObjectData { + + internal companion object { + private val log by logger(SavedQuery::class.java) + private const val NAME_TAG = "name" + private const val DESCRIPTION_TAG = "description" + private const val QUERY_TAG = "query" + private const val SELECTED_DATE_RANGE_TAG = "selected_date_range" + private const val SELECTED_TIMESTAMP_TAG = "selected_timestamp" + private const val SELECTED_FIELDS_TAG = "selected_fields" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SavedQuery(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create ObservabilityObject object + * @param parser data referenced at parser + * @return created ObservabilityObject object + */ + fun parse(parser: XContentParser): SavedQuery { + var name: String? = null + var description: String? = null + var query: String? = null + var selectedDateRange: SelectedDateRange? = null + var selectedTimestamp: Token? = null + var selectedFields: SelectedFields? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + DESCRIPTION_TAG -> description = parser.text() + QUERY_TAG -> query = parser.text() + SELECTED_DATE_RANGE_TAG -> selectedDateRange = SelectedDateRange.parse(parser) + SELECTED_TIMESTAMP_TAG -> selectedTimestamp = Token.parse(parser) + SELECTED_FIELDS_TAG -> selectedFields = SelectedFields.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:SavedQuery Skipping Unknown field $fieldName") + } + } + } + return SavedQuery(name, description, query, selectedDateRange, selectedTimestamp, selectedFields) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + description = input.readString(), + query = input.readString(), + selectedDateRange = input.readOptionalWriteable(SelectedDateRange.reader), + selectedTimestamp = input.readOptionalWriteable(Token.reader), + selectedFields = input.readOptionalWriteable(SelectedFields.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(description) + output.writeString(query) + output.writeOptionalWriteable(selectedDateRange) + output.writeOptionalWriteable(selectedTimestamp) + output.writeOptionalWriteable(selectedFields) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .fieldIfNotNull(NAME_TAG, name) + .fieldIfNotNull(DESCRIPTION_TAG, description) + .fieldIfNotNull(QUERY_TAG, query) + .fieldIfNotNull(SELECTED_DATE_RANGE_TAG, selectedDateRange) + .fieldIfNotNull(SELECTED_TIMESTAMP_TAG, selectedTimestamp) + .fieldIfNotNull(SELECTED_FIELDS_TAG, selectedFields) + return builder.endObject() + } + + internal data class SelectedDateRange( + val start: String, + val end: String, + val text: String + ) : BaseModel { + internal companion object { + private const val START_TAG = "start" + private const val END_TAG = "end" + private const val TEXT_TAG = "text" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SelectedDateRange(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Trigger object + * @param parser data referenced at parser + * @return created Trigger object + */ + fun parse(parser: XContentParser): SelectedDateRange { + var start: String? = null + var end: String? = null + var text: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + START_TAG -> start = parser.text() + END_TAG -> end = parser.text() + TEXT_TAG -> text = parser.text() + else -> log.info("$LOG_PREFIX: Trigger Skipping Unknown field $fieldName") + } + } + start ?: throw IllegalArgumentException("$START_TAG field absent") + end ?: throw IllegalArgumentException("$END_TAG field absent") + text ?: throw IllegalArgumentException("$TEXT_TAG field absent") + return SelectedDateRange(start, end, text) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + start = input.readString(), + end = input.readString(), + text = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(start) + output.writeString(end) + output.writeString(text) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(START_TAG, start) + .field(END_TAG, end) + .field(TEXT_TAG, text) + builder.endObject() + return builder + } + } + + internal data class Token( + val name: String, + val type: String, + ) : BaseModel { + internal companion object { + private const val NAME_TAG = "name" + private const val TYPE_TAG = "type" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Token(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Trigger object + * @param parser data referenced at parser + * @return created Trigger object + */ + fun parse(parser: XContentParser): Token { + var name: String? = null + var type: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + TYPE_TAG -> type = parser.text() + else -> log.info("$LOG_PREFIX: Trigger Skipping Unknown field $fieldName") + } + } + name ?: throw IllegalArgumentException("$NAME_TAG field absent") + type ?: throw IllegalArgumentException("$TYPE_TAG field absent") + return Token(name, type) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + type = input.readString(), + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(type) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(NAME_TAG, name) + .field(TYPE_TAG, type) + builder.endObject() + return builder + } + } + + internal data class SelectedFields( + val text: String?, + val tokens: List? + ) : BaseModel { + internal companion object { + private const val TEXT_TAG = "text" + private const val TOKENS_TAG = "tokens" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SelectedFields(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(Token.parse(parser)) + } + return retList + } + + /** + * Parse the data from parser and create Trigger object + * @param parser data referenced at parser + * @return created Trigger object + */ + fun parse(parser: XContentParser): SelectedFields { + var text: String? = null + var tokens: List? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + TEXT_TAG -> text = parser.text() + TOKENS_TAG -> tokens = parseItemList(parser) + else -> log.info("$LOG_PREFIX: Trigger Skipping Unknown field $fieldName") + } + } + text ?: throw IllegalArgumentException("$TEXT_TAG field absent") + tokens ?: throw IllegalArgumentException("$TOKENS_TAG field absent") + return SelectedFields(text, tokens) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + text = input.readString(), + tokens = input.readList(Token.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(text) + output.writeCollection(tokens) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .field(TEXT_TAG, text) + if (tokens != null) { + builder.startArray(TOKENS_TAG) + tokens.forEach { it.toXContent(builder, params) } + builder.endArray() + } + builder.endObject() + return builder + } + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedVisualization.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedVisualization.kt new file mode 100644 index 000000000..3e76cdd95 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SavedVisualization.kt @@ -0,0 +1,179 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.util.fieldIfNotNull +import org.opensearch.observability.util.logger + +/** + * Saved query main data class. + * *
 JSON format
+ * {@code
+ * {
+ *   "query": "source=index | where utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00')",
+ *   "selected_date_range": {
+ *     "start": "now/15m",
+ *     "end": "now",
+ *     "text": "utc_time > timestamp('2021-07-01 00:00:00') and utc_time < timestamp('2021-07-02 00:00:00')"
+ *   },
+ *   "selected_timestamp": {
+ *       "name": "utc_time",
+ *       "type": "timestamp"
+ *   },
+ *   "selected_fields": {
+ *       "text": "| fields clientip, bytes, memory, host",
+ *       "tokens": [
+ *           {"name":"bytes","type":"long"},
+ *           {"name":"clientip","type":"ip"}
+ *       ]
+ *   },
+ *   "type": "bar",
+ *   "name": "Logs between dates",
+ *   "description": "some descriptions related to this query"
+ * }
+ * }
+ */ + +internal data class SavedVisualization( + val name: String?, + val description: String?, + val query: String?, + val type: String?, + val selectedDateRange: SavedQuery.SelectedDateRange?, + val selectedTimestamp: SavedQuery.Token?, + val selectedFields: SavedQuery.SelectedFields? +) : BaseObjectData { + + internal companion object { + private val log by logger(SavedVisualization::class.java) + private const val NAME_TAG = "name" + private const val DESCRIPTION_TAG = "description" + private const val QUERY_TAG = "query" + private const val TYPE_TAG = "type" + private const val SELECTED_DATE_RANGE_TAG = "selected_date_range" + private const val SELECTED_TIMESTAMP_TAG = "selected_timestamp" + private const val SELECTED_FIELDS_TAG = "selected_fields" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SavedVisualization(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create SavedVisualization object + * @param parser data referenced at parser + * @return created SavedVisualization object + */ + fun parse(parser: XContentParser): SavedVisualization { + var name: String? = null + var description: String? = null + var query: String? = null + var type: String? = null + var selectedDateRange: SavedQuery.SelectedDateRange? = null + var selectedTimestamp: SavedQuery.Token? = null + var selectedFields: SavedQuery.SelectedFields? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + DESCRIPTION_TAG -> description = parser.text() + QUERY_TAG -> query = parser.text() + TYPE_TAG -> type = parser.text() + SELECTED_DATE_RANGE_TAG -> selectedDateRange = SavedQuery.SelectedDateRange.parse(parser) + SELECTED_TIMESTAMP_TAG -> selectedTimestamp = SavedQuery.Token.parse(parser) + SELECTED_FIELDS_TAG -> selectedFields = SavedQuery.SelectedFields.parse(parser) + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:SavedVisualization Skipping Unknown field $fieldName") + } + } + } + return SavedVisualization( + name, + description, + query, + type, + selectedDateRange, + selectedTimestamp, + selectedFields + ) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + description = input.readString(), + query = input.readString(), + type = input.readString(), + selectedDateRange = input.readOptionalWriteable(SavedQuery.SelectedDateRange.reader), + selectedTimestamp = input.readOptionalWriteable(SavedQuery.Token.reader), + selectedFields = input.readOptionalWriteable(SavedQuery.SelectedFields.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(description) + output.writeString(query) + output.writeString(type) + output.writeOptionalWriteable(selectedDateRange) + output.writeOptionalWriteable(selectedTimestamp) + output.writeOptionalWriteable(selectedFields) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .fieldIfNotNull(NAME_TAG, name) + .fieldIfNotNull(DESCRIPTION_TAG, description) + .fieldIfNotNull(QUERY_TAG, query) + .fieldIfNotNull(TYPE_TAG, type) + .fieldIfNotNull(SELECTED_DATE_RANGE_TAG, selectedDateRange) + .fieldIfNotNull(SELECTED_TIMESTAMP_TAG, selectedTimestamp) + .fieldIfNotNull(SELECTED_FIELDS_TAG, selectedFields) + return builder.endObject() + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SearchResults.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SearchResults.kt new file mode 100644 index 000000000..2ceff501f --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/SearchResults.kt @@ -0,0 +1,220 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package org.opensearch.observability.model + +import org.apache.lucene.search.TotalHits.Relation +import org.apache.lucene.search.TotalHits.Relation.EQUAL_TO +import org.apache.lucene.search.TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO +import org.opensearch.action.search.SearchResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent.Params +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.search.SearchHit + +internal abstract class SearchResults : BaseModel { + val startIndex: Long + val totalHits: Long + val totalHitRelation: Relation + val objectListFieldName: String + val objectList: List + + interface SearchHitParser { + fun parse(searchHit: SearchHit): ItemClass + } + + companion object { + private val log by org.opensearch.commons.utils.logger(SearchResults::class.java) + private const val START_INDEX_TAG = "startIndex" + private const val TOTAL_HITS_TAG = "totalHits" + private const val TOTAL_HIT_RELATION_TAG = "totalHitRelation" + private fun convertRelation(totalHitRelation: Relation): String { + return if (totalHitRelation == EQUAL_TO) { + "eq" + } else { + "gte" + } + } + + private fun convertRelation(totalHitRelation: String): Relation { + return if (totalHitRelation == "eq") { + EQUAL_TO + } else { + GREATER_THAN_OR_EQUAL_TO + } + } + } + + constructor( + objectListFieldName: String, + objectItem: ItemClass + ) { + this.startIndex = 0 + this.totalHits = 1 + this.totalHitRelation = EQUAL_TO + this.objectListFieldName = objectListFieldName + this.objectList = listOf(objectItem) + } + + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: Relation, + objectListFieldName: String, + objectList: List + ) { + this.startIndex = startIndex + this.totalHits = totalHits + this.totalHitRelation = totalHitRelation + this.objectListFieldName = objectListFieldName + this.objectList = objectList + } + + constructor( + from: Long, + response: SearchResponse, + searchHitParser: SearchHitParser, + objectListFieldName: String + ) { + val mutableList: MutableList = mutableListOf() + response.hits.forEach { + mutableList.add(searchHitParser.parse(it)) + } + val totalHits = response.hits.totalHits + val totalHitsVal: Long + val totalHitsRelation: Relation + if (totalHits == null) { + totalHitsVal = mutableList.size.toLong() + totalHitsRelation = EQUAL_TO + } else { + totalHitsVal = totalHits.value + totalHitsRelation = totalHits.relation + } + this.startIndex = from + this.totalHits = totalHitsVal + this.totalHitRelation = totalHitsRelation + this.objectListFieldName = objectListFieldName + this.objectList = mutableList + } + + /** + * Parse the data from parser and create object + * @param parser data referenced at parser + */ + constructor(parser: XContentParser, objectListFieldName: String) { + var startIndex: Long = 0 + var totalHits: Long = 0 + var totalHitRelation: Relation = EQUAL_TO + var objectList: List? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + START_INDEX_TAG -> startIndex = parser.longValue() + TOTAL_HITS_TAG -> totalHits = parser.longValue() + TOTAL_HIT_RELATION_TAG -> totalHitRelation = convertRelation(parser.text()) + objectListFieldName -> objectList = parseItemList(parser) + else -> { + parser.skipChildren() + log.info("Skipping Unknown field $fieldName") + } + } + } + objectList ?: throw IllegalArgumentException("$objectListFieldName field absent") + if (totalHits == 0L) { + totalHits = objectList.size.toLong() + } + this.startIndex = startIndex + this.totalHits = totalHits + this.totalHitRelation = totalHitRelation + this.objectListFieldName = objectListFieldName + this.objectList = objectList + } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(parseItem(parser)) + } + return retList + } + + /** + * Parse the object item + * @param parser data referenced at parser + * @return created item + */ + abstract fun parseItem(parser: XContentParser, useId: String? = null): ItemClass + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + * @param reader StreamInput reader class for reading item. + */ + constructor(input: StreamInput, reader: Writeable.Reader) : this( + startIndex = input.readLong(), + totalHits = input.readLong(), + totalHitRelation = input.readEnum(Relation::class.java), + objectListFieldName = input.readString(), + objectList = input.readList(reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeLong(startIndex) + output.writeLong(totalHits) + output.writeEnum(totalHitRelation) + output.writeString(objectListFieldName) + output.writeList(objectList) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: Params?): XContentBuilder { + builder!!.startObject() + .field(START_INDEX_TAG, startIndex) + .field(TOTAL_HITS_TAG, totalHits) + .field(TOTAL_HIT_RELATION_TAG, convertRelation(totalHitRelation)) + .startArray(objectListFieldName) + objectList.forEach { it.toXContent(builder, params) } + return builder.endArray().endObject() + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Timestamp.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Timestamp.kt new file mode 100644 index 000000000..37c9b5756 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/Timestamp.kt @@ -0,0 +1,134 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.util.fieldIfNotNull +import org.opensearch.observability.util.logger + +/** + * Timestamp main data class. + * *
 JSON format
+ * {@code
+ * {
+ *   "name": "Logs between dates",
+ *   "index": "opensearch_dashboards_sample_data_logs",
+ *   "type": "timestamp",
+ *   "dsl_type": "date"
+ * }
+ * }
+ */ + +internal data class Timestamp( + val name: String?, + val index: String?, + val type: String?, + val dslType: String?, +) : BaseObjectData { + + internal companion object { + private val log by logger(Timestamp::class.java) + private const val NAME_TAG = "name" + private const val INDEX_TAG = "index" + private const val TYPE_TAG = "type" + private const val DSL_TYPE_TAG = "dsl_type" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Timestamp(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Parse the data from parser and create Timestamp object + * @param parser data referenced at parser + * @return created Timestamp object + */ + fun parse(parser: XContentParser): Timestamp { + var name: String? = null + var index: String? = null + var type: String? = null + var dslType: String? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + NAME_TAG -> name = parser.text() + INDEX_TAG -> index = parser.text() + TYPE_TAG -> type = parser.text() + DSL_TYPE_TAG -> dslType = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:SavedVisualization Skipping Unknown field $fieldName") + } + } + } + return Timestamp(name, index, type, dslType) + } + } + + /** + * create XContentBuilder from this object using [XContentFactory.jsonBuilder()] + * @param params XContent parameters + * @return created XContentBuilder object + */ + fun toXContent(params: ToXContent.Params = ToXContent.EMPTY_PARAMS): XContentBuilder? { + return toXContent(XContentFactory.jsonBuilder(), params) + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + index = input.readString(), + type = input.readString(), + dslType = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(index) + output.writeString(type) + output.writeString(dslType) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + builder.startObject() + .fieldIfNotNull(NAME_TAG, name) + .fieldIfNotNull(INDEX_TAG, index) + .fieldIfNotNull(TYPE_TAG, type) + .fieldIfNotNull(DSL_TYPE_TAG, dslType) + return builder.endObject() + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectRequest.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectRequest.kt new file mode 100644 index 000000000..fe6a19db3 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectRequest.kt @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.observability.model + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +import org.opensearch.common.Strings +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import java.io.IOException + +/** + * Action request for creating new configuration. + */ +internal class UpdateObservabilityObjectRequest : ActionRequest, ToXContentObject { + val objectId: String + val type: ObservabilityObjectType + val objectData: BaseObjectData? + + companion object { + private val log by logger(UpdateObservabilityObjectRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { UpdateObservabilityObjectRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + * @param id optional id to use if missed in XContent + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser, id: String? = null): UpdateObservabilityObjectRequest { + var objectId: String? = id + var type: ObservabilityObjectType? = null + var baseObjectData: BaseObjectData? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_FIELD -> objectId = parser.text() + else -> { + val objectTypeForTag = ObservabilityObjectType.fromTagOrDefault(fieldName) + if (objectTypeForTag != ObservabilityObjectType.NONE && baseObjectData == null) { + baseObjectData = + ObservabilityObjectDataProperties.createObjectData(objectTypeForTag, parser) + type = objectTypeForTag + } else { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing CreateObservabilityObjectRequest") + } + } + } + } + objectId ?: throw IllegalArgumentException("$OBJECT_ID_FIELD field absent") + type ?: throw IllegalArgumentException("Object data field absent") + baseObjectData ?: throw IllegalArgumentException("Object data field absent") + return UpdateObservabilityObjectRequest(baseObjectData, type, objectId) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .fieldIfNotNull(OBJECT_ID_FIELD, objectId) + .field(type.tag, objectData) + .endObject() + } + + /** + * constructor for creating the class + * @param objectData the ObservabilityObject + * @param objectId optional id to use for ObservabilityObject + */ + constructor(objectData: BaseObjectData, type: ObservabilityObjectType, objectId: String) { + this.objectData = objectData + this.type = type + this.objectId = objectId + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + objectId = input.readString() + type = input.readEnum(ObservabilityObjectType::class.java) + objectData = input.readOptionalWriteable( + ObservabilityObjectDataProperties.getReaderForObjectType( + input.readEnum( + ObservabilityObjectType::class.java + ) + ) + ) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeString(objectId) + output.writeEnum(type) + output.writeOptionalWriteable(objectData) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (Strings.isNullOrEmpty(objectId)) { + validationException = ValidateActions.addValidationError("objectId is null or empty", validationException) + } + return validationException + } +} diff --git a/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectResponse.kt b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectResponse.kt new file mode 100644 index 000000000..9114ad809 --- /dev/null +++ b/opensearch-observability/src/main/kotlin/org/opensearch/observability/model/UpdateObservabilityObjectResponse.kt @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package org.opensearch.observability.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParser.Token +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.observability.ObservabilityPlugin.Companion.LOG_PREFIX +import org.opensearch.observability.model.RestTag.OBJECT_ID_FIELD +import org.opensearch.observability.util.logger +import java.io.IOException + +/** + * ObservabilityObject-update response. + *
 JSON format
+ * {@code
+ * {
+ *   "objectId":"objectId"
+ * }
+ * }
+ */ +internal class UpdateObservabilityObjectResponse( + val objectId: String? +) : BaseResponse() { + + @Throws(IOException::class) + constructor(input: StreamInput) : this( + objectId = input.readString() + ) + + companion object { + private val log by logger(UpdateObservabilityObjectResponse::class.java) + + /** + * Parse the data from parser and create [UpdateObservabilityObjectResponse] object + * @param parser data referenced at parser + * @return created [UpdateObservabilityObjectResponse] object + */ + fun parse(parser: XContentParser): UpdateObservabilityObjectResponse { + var objectId: String? = null + XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser) + while (Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + OBJECT_ID_FIELD -> objectId = parser.text() + else -> { + parser.skipChildren() + log.info("$LOG_PREFIX:Skipping Unknown field $fieldName") + } + } + } + objectId ?: throw IllegalArgumentException("$OBJECT_ID_FIELD field absent") + return UpdateObservabilityObjectResponse(objectId) + } + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeString(objectId) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(OBJECT_ID_FIELD, objectId) + .endObject() + } +}