Skip to content

Commit c75e594

Browse files
MichaelGHSegclaude
andcommitted
Add e2e-cli module for analytics-java
- Kotlin CLI using Gson for JSON parsing - Separate from existing analytics-cli to avoid disruption - Added to parent pom.xml modules Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1eb13a3 commit c75e594

3 files changed

Lines changed: 251 additions & 0 deletions

File tree

e2e-cli/pom.xml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<artifactId>analytics-parent</artifactId>
8+
<groupId>com.segment.analytics.java</groupId>
9+
<version>3.5.5-SNAPSHOT</version>
10+
</parent>
11+
12+
<groupId>com.segment.analytics.java</groupId>
13+
<artifactId>e2e-cli</artifactId>
14+
<version>3.5.5-SNAPSHOT</version>
15+
<name>Analytics Java E2E CLI</name>
16+
17+
<description>E2E testing CLI for Segment Analytics for Java.</description>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.jetbrains.kotlin</groupId>
22+
<artifactId>kotlin-stdlib</artifactId>
23+
<version>${kotlin.version}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>com.segment.analytics.java</groupId>
27+
<artifactId>analytics</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.jetbrains.kotlinx</groupId>
32+
<artifactId>kotlinx-serialization-json</artifactId>
33+
<version>1.4.1</version>
34+
</dependency>
35+
</dependencies>
36+
37+
<build>
38+
<sourceDirectory>src/main/kotlin</sourceDirectory>
39+
40+
<plugins>
41+
<plugin>
42+
<artifactId>kotlin-maven-plugin</artifactId>
43+
<groupId>org.jetbrains.kotlin</groupId>
44+
<version>${kotlin.version}</version>
45+
<configuration>
46+
<compilerPlugins>
47+
<plugin>kotlinx-serialization</plugin>
48+
</compilerPlugins>
49+
</configuration>
50+
<dependencies>
51+
<dependency>
52+
<groupId>org.jetbrains.kotlin</groupId>
53+
<artifactId>kotlin-maven-serialization</artifactId>
54+
<version>${kotlin.version}</version>
55+
</dependency>
56+
</dependencies>
57+
<executions>
58+
<execution>
59+
<id>compile</id>
60+
<phase>compile</phase>
61+
<goals>
62+
<goal>compile</goal>
63+
</goals>
64+
</execution>
65+
</executions>
66+
</plugin>
67+
<plugin>
68+
<groupId>org.apache.maven.plugins</groupId>
69+
<artifactId>maven-assembly-plugin</artifactId>
70+
<configuration>
71+
<descriptorRefs>
72+
<descriptorRef>jar-with-dependencies</descriptorRef>
73+
</descriptorRefs>
74+
<archive>
75+
<manifest>
76+
<mainClass>cli.MainKt</mainClass>
77+
</manifest>
78+
</archive>
79+
</configuration>
80+
<executions>
81+
<execution>
82+
<phase>package</phase>
83+
<goals>
84+
<goal>single</goal>
85+
</goals>
86+
</execution>
87+
</executions>
88+
</plugin>
89+
</plugins>
90+
</build>
91+
</project>
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package cli
2+
3+
import com.google.gson.Gson
4+
import com.google.gson.reflect.TypeToken
5+
import com.segment.analytics.Analytics
6+
import com.segment.analytics.Callback
7+
import com.segment.analytics.messages.*
8+
import java.util.concurrent.CountDownLatch
9+
import java.util.concurrent.TimeUnit
10+
import java.util.concurrent.atomic.AtomicBoolean
11+
12+
data class CLIOutput(
13+
val success: Boolean,
14+
val error: String? = null,
15+
val sentBatches: Int = 0
16+
)
17+
18+
data class CLIConfig(
19+
val flushAt: Int? = null,
20+
val flushInterval: Long? = null,
21+
val maxRetries: Int? = null,
22+
val timeout: Int? = null
23+
)
24+
25+
data class EventSequence(
26+
val delayMs: Long = 0,
27+
val events: List<Map<String, Any>>
28+
)
29+
30+
data class CLIInput(
31+
val writeKey: String,
32+
val apiHost: String,
33+
val sequences: List<EventSequence>,
34+
val config: CLIConfig? = null
35+
)
36+
37+
private val gson = Gson()
38+
39+
fun main(args: Array<String>) {
40+
var output = CLIOutput(success = false, error = "Unknown error")
41+
42+
try {
43+
// Parse --input argument
44+
val inputIndex = args.indexOf("--input")
45+
if (inputIndex == -1 || inputIndex + 1 >= args.size) {
46+
throw IllegalArgumentException("Missing required --input argument")
47+
}
48+
49+
val inputJson = args[inputIndex + 1]
50+
val input = gson.fromJson(inputJson, CLIInput::class.java)
51+
52+
val flushAt = input.config?.flushAt ?: 20
53+
val flushIntervalMs = input.config?.flushInterval ?: 10000L
54+
55+
val flushLatch = CountDownLatch(1)
56+
val hasError = AtomicBoolean(false)
57+
var errorMessage: String? = null
58+
59+
val analytics = Analytics.builder(input.writeKey)
60+
.endpoint(input.apiHost)
61+
.flushQueueSize(flushAt)
62+
.flushInterval(maxOf(flushIntervalMs, 1000L), TimeUnit.MILLISECONDS)
63+
.callback(object : Callback {
64+
override fun success(message: Message?) {
65+
// Event sent successfully
66+
}
67+
68+
override fun failure(message: Message?, throwable: Throwable?) {
69+
hasError.set(true)
70+
errorMessage = throwable?.message
71+
}
72+
})
73+
.build()
74+
75+
// Process event sequences
76+
for (seq in input.sequences) {
77+
if (seq.delayMs > 0) {
78+
Thread.sleep(seq.delayMs)
79+
}
80+
81+
for (event in seq.events) {
82+
sendEvent(analytics, event)
83+
}
84+
}
85+
86+
// Flush and shutdown
87+
analytics.flush()
88+
analytics.shutdown()
89+
90+
output = if (hasError.get()) {
91+
CLIOutput(success = false, error = errorMessage, sentBatches = 0)
92+
} else {
93+
CLIOutput(success = true, sentBatches = 1)
94+
}
95+
96+
} catch (e: Exception) {
97+
output = CLIOutput(success = false, error = e.message ?: e.toString())
98+
}
99+
100+
println(gson.toJson(output))
101+
}
102+
103+
fun sendEvent(analytics: Analytics, event: Map<String, Any>) {
104+
val type = event["type"] as? String
105+
?: throw IllegalArgumentException("Event missing 'type' field")
106+
107+
val userId = event["userId"] as? String ?: ""
108+
val anonymousId = event["anonymousId"] as? String
109+
val messageId = event["messageId"] as? String
110+
@Suppress("UNCHECKED_CAST")
111+
val traits = event["traits"] as? Map<String, Any> ?: emptyMap()
112+
@Suppress("UNCHECKED_CAST")
113+
val properties = event["properties"] as? Map<String, Any> ?: emptyMap()
114+
val eventName = event["event"] as? String
115+
val name = event["name"] as? String
116+
val groupId = event["groupId"] as? String
117+
val previousId = event["previousId"] as? String
118+
119+
val messageBuilder: MessageBuilder<*, *> = when (type) {
120+
"identify" -> {
121+
IdentifyMessage.builder().apply {
122+
traits(traits)
123+
}
124+
}
125+
"track" -> {
126+
TrackMessage.builder(eventName ?: "Unknown Event").apply {
127+
properties(properties)
128+
}
129+
}
130+
"page" -> {
131+
PageMessage.builder(name ?: "Unknown Page").apply {
132+
properties(properties)
133+
}
134+
}
135+
"screen" -> {
136+
ScreenMessage.builder(name ?: "Unknown Screen").apply {
137+
properties(properties)
138+
}
139+
}
140+
"alias" -> {
141+
AliasMessage.builder(previousId ?: "")
142+
}
143+
"group" -> {
144+
GroupMessage.builder(groupId ?: "").apply {
145+
traits(traits)
146+
}
147+
}
148+
else -> throw IllegalArgumentException("Unknown event type: $type")
149+
}
150+
151+
if (userId.isNotEmpty()) {
152+
messageBuilder.userId(userId)
153+
}
154+
if (anonymousId != null) {
155+
messageBuilder.anonymousId(anonymousId)
156+
}
157+
158+
analytics.enqueue(messageBuilder)
159+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<module>analytics-sample</module>
2727
<module>analytics-cli</module>
2828
<module>analytics-spring-boot-starter</module>
29+
<module>e2e-cli</module>
2930
</modules>
3031

3132
<properties>

0 commit comments

Comments
 (0)