From 387aad72e1d80ec71085d9b21fe17b51adfab3f1 Mon Sep 17 00:00:00 2001 From: Surya Sashank Nistala Date: Fri, 24 Apr 2026 14:09:04 -0700 Subject: [PATCH] fix: Change ScheduleJobPayload.jobStartTime from Instant to String jobStartTime needs to support both scheduler placeholders (e.g. ) on the producer side and actual ISO-8601 timestamps on the consumer side. Using String allows the POJO to be used directly by both paths. Signed-off-by: Surya Sashank Nistala --- .../alerting/model/ScheduleJobPayload.kt | 13 ++++--- .../alerting/model/ScheduleJobPayloadTests.kt | 37 ++++++++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayload.kt b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayload.kt index 96000ef5..0fea8d3a 100644 --- a/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayload.kt +++ b/src/main/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayload.kt @@ -13,24 +13,27 @@ import org.opensearch.core.xcontent.ToXContentObject import org.opensearch.core.xcontent.XContentBuilder import org.opensearch.core.xcontent.XContentParser import org.opensearch.core.xcontent.XContentParserUtils -import java.time.Instant /** * Represents the payload for an externally scheduled monitor job. * * Written to the schedule target (e.g. SQS) by the monitor CRUD path, * and parsed back by the job poller at execution time. + * + * [jobStartTime] is a String to support both scheduler placeholders + * (e.g. "") on the producer side and + * actual ISO-8601 timestamps on the consumer side. */ data class ScheduleJobPayload( val monitorId: String, - val jobStartTime: Instant, + val jobStartTime: String, val monitorConfig: String ) : ToXContentObject { override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { builder.startObject() builder.field(MONITOR_ID_FIELD, monitorId) - builder.field(JOB_START_TIME_FIELD, jobStartTime.toString()) + builder.field(JOB_START_TIME_FIELD, jobStartTime) builder.field(MONITOR_CONFIG_FIELD, monitorConfig) builder.endObject() return builder @@ -56,7 +59,7 @@ data class ScheduleJobPayload( fun parse(xcp: XContentParser): ScheduleJobPayload { var monitorId: String? = null - var jobStartTime: Instant? = null + var jobStartTime: String? = null var monitorConfig: String? = null XContentParserUtils.ensureExpectedToken( @@ -69,7 +72,7 @@ data class ScheduleJobPayload( xcp.nextToken() when (fieldName) { MONITOR_ID_FIELD -> monitorId = xcp.text() - JOB_START_TIME_FIELD -> jobStartTime = Instant.parse(xcp.text()) + JOB_START_TIME_FIELD -> jobStartTime = xcp.text() MONITOR_CONFIG_FIELD -> monitorConfig = xcp.text() else -> xcp.skipChildren() } diff --git a/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayloadTests.kt b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayloadTests.kt index d626d8a9..1c73a8f4 100644 --- a/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayloadTests.kt +++ b/src/test/kotlin/org/opensearch/commons/alerting/model/ScheduleJobPayloadTests.kt @@ -11,7 +11,6 @@ import org.opensearch.commons.alerting.parser import org.opensearch.commons.alerting.randomQueryLevelMonitor import org.opensearch.commons.alerting.xContentRegistry import org.opensearch.core.xcontent.ToXContent -import java.time.Instant class ScheduleJobPayloadTests { @@ -26,7 +25,7 @@ class ScheduleJobPayloadTests { val monitor = randomQueryLevelMonitor().copy(id = "mon-123") val payload = ScheduleJobPayload( monitorId = monitor.id, - jobStartTime = Instant.parse("2026-04-23T10:00:00Z"), + jobStartTime = "2026-04-23T10:00:00Z", monitorConfig = serializeMonitor(monitor) ) @@ -36,16 +35,33 @@ class ScheduleJobPayloadTests { val parsed = ScheduleJobPayload.parse(parser(json)) assertEquals("mon-123", parsed.monitorId) - assertEquals(Instant.parse("2026-04-23T10:00:00Z"), parsed.jobStartTime) + assertEquals("2026-04-23T10:00:00Z", parsed.jobStartTime) assertEquals(payload.monitorConfig, parsed.monitorConfig) } + @Test + fun `round-trip with EB placeholder as jobStartTime`() { + val monitor = randomQueryLevelMonitor().copy(id = "mon-eb") + val payload = ScheduleJobPayload( + monitorId = monitor.id, + jobStartTime = "", + monitorConfig = serializeMonitor(monitor) + ) + + val builder = org.opensearch.common.xcontent.XContentFactory.jsonBuilder() + payload.toXContent(builder, ToXContent.EMPTY_PARAMS) + val json = builder.toString() + + val parsed = ScheduleJobPayload.parse(parser(json)) + assertEquals("", parsed.jobStartTime) + } + @Test fun `toMonitor preserves id and fields`() { val monitor = randomQueryLevelMonitor().copy(id = "mon-456") val payload = ScheduleJobPayload( monitorId = monitor.id, - jobStartTime = Instant.now(), + jobStartTime = "2026-04-23T10:00:00Z", monitorConfig = serializeMonitor(monitor) ) @@ -63,7 +79,7 @@ class ScheduleJobPayloadTests { ) val payload = ScheduleJobPayload( monitorId = monitor.id, - jobStartTime = Instant.now(), + jobStartTime = "2026-04-23T10:00:00Z", monitorConfig = serializeMonitor(monitor) ) @@ -84,4 +100,15 @@ class ScheduleJobPayloadTests { ScheduleJobPayload.parse(parser(json)) } } + + @Test + fun `parse throws on missing jobStartTime`() { + val monitor = randomQueryLevelMonitor() + val json = "{\"monitorId\":\"mon-123\"," + + "\"monitorConfig\":\"${serializeMonitor(monitor).replace("\"", "\\\"")}\"}" + + org.junit.jupiter.api.assertThrows { + ScheduleJobPayload.parse(parser(json)) + } + } }