Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions build-info-api/src/main/java/org/jfrog/build/api/Build.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public class Build extends BaseBuildBean {

private Issues issues;

private List<Trace> traces;

/**
* Formats the timestamp to the ISO date time string format expected by the build info API.
*
Expand Down Expand Up @@ -466,6 +468,13 @@ public void setIssues(Issues issues) {
this.issues = issues;
}

public List<Trace> getTraces() {
return traces;
}

public void setTraces(List<Trace> traces) {
this.traces = traces;
}

@Override
public String toString() {
Expand All @@ -491,6 +500,7 @@ public String toString() {
", statuses=" + statuses +
", buildDependencies=" + buildDependencies +
", issues=" + issues +
", traces=" + traces +
'}';
}
}
105 changes: 105 additions & 0 deletions build-info-api/src/main/java/org/jfrog/build/api/Trace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.jfrog.build.api;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.util.Map;

/**
* Represents a single OpenTelemetry-style span attached to a build.
* The traces array captures CI pipeline observability data — test runs,
* commands, and their timing relationships.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Trace implements Serializable {

@JsonProperty("trace_id")
private String traceId;

@JsonProperty("span_id")
private String spanId;

@JsonProperty("parent_span_id")
private String parentSpanId;

private String name;

@JsonProperty("start_time")
private String startTime;

@JsonProperty("end_time")
private String endTime;

private Map<String, Object> attributes;

@JsonProperty("span_kind")
private String spanKind;

public Trace() {
}

public String getTraceId() {
return traceId;
}

public void setTraceId(String traceId) {
this.traceId = traceId;
}

public String getSpanId() {
return spanId;
}

public void setSpanId(String spanId) {
this.spanId = spanId;
}

public String getParentSpanId() {
return parentSpanId;
}

public void setParentSpanId(String parentSpanId) {
this.parentSpanId = parentSpanId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getStartTime() {
return startTime;
}

public void setStartTime(String startTime) {
this.startTime = startTime;
}

public String getEndTime() {
return endTime;
}

public void setEndTime(String endTime) {
this.endTime = endTime;
}

public Map<String, Object> getAttributes() {
return attributes;
}

public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}

public String getSpanKind() {
return spanKind;
}

public void setSpanKind(String spanKind) {
this.spanKind = spanKind;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jfrog.build.api.BuildRetention;
import org.jfrog.build.api.Issues;
import org.jfrog.build.api.MatrixParameter;
import org.jfrog.build.api.Trace;
import org.jfrog.build.api.Module;
import org.jfrog.build.api.Vcs;
import org.jfrog.build.api.release.PromotionStatus;
Expand Down Expand Up @@ -49,6 +50,7 @@ public class BuildInfoBuilder {
protected Properties properties;
protected BuildRetention buildRetention;
protected Issues issues;
protected List<Trace> traces;

public BuildInfoBuilder(String name) {
this.name = name;
Expand Down Expand Up @@ -95,6 +97,7 @@ public Build build() {
build.setVcs(vcs);
build.setBuildRetention(buildRetention);
build.setIssues(issues);
build.setTraces(traces);
return build;
}

Expand Down Expand Up @@ -418,6 +421,11 @@ public BuildInfoBuilder issues(Issues issues) {
return this;
}

public BuildInfoBuilder traces(List<Trace> traces) {
this.traces = traces;
return this;
}

public BuildInfoBuilder project(String project) {
this.project = project;
return this;
Expand Down
144 changes: 144 additions & 0 deletions build-info-api/src/test/java/org/jfrog/build/api/TraceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package org.jfrog.build.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;

/**
* Tests the Trace model and its serialization / deserialization via the Build class.
*/
@Test
public class TraceTest {

private static final String TRACE_ID = "43fb38773c526b515370ba5406ce4b89";

public void testTraceGettersSetters() {
Trace trace = new Trace();
assertNull(trace.getTraceId(), "trace_id should be null initially");
assertNull(trace.getSpanId());
assertNull(trace.getParentSpanId());
assertNull(trace.getName());
assertNull(trace.getStartTime());
assertNull(trace.getEndTime());
assertNull(trace.getAttributes());
assertNull(trace.getSpanKind());

Map<String, Object> attrs = new HashMap<>();
attrs.put("span.type", "test_case");
attrs.put("test.outcome", "pass");

trace.setTraceId(TRACE_ID);
trace.setSpanId("0246df031f708709");
trace.setParentSpanId("8024dcd3910a35e6");
trace.setName("boost ci-setup github");
trace.setStartTime("2026-06-22T14:37:56.885735333Z");
trace.setEndTime("2026-06-22T14:37:56.894949188Z");
trace.setAttributes(attrs);
trace.setSpanKind("internal");

assertEquals(trace.getTraceId(), TRACE_ID);
assertEquals(trace.getSpanId(), "0246df031f708709");
assertEquals(trace.getParentSpanId(), "8024dcd3910a35e6");
assertEquals(trace.getName(), "boost ci-setup github");
assertEquals(trace.getStartTime(), "2026-06-22T14:37:56.885735333Z");
assertEquals(trace.getEndTime(), "2026-06-22T14:37:56.894949188Z");
assertEquals(trace.getAttributes(), attrs);
assertEquals(trace.getSpanKind(), "internal");
}

public void testBuildTracesGetterSetter() {
Build build = new Build();
assertNull(build.getTraces(), "traces should be null by default");

Trace t1 = buildTrace("span1", "parent1", "test run: go");
Trace t2 = buildTrace("span2", "parent1", "testenv/TestAdd");
List<Trace> traces = Arrays.asList(t1, t2);

build.setTraces(traces);

assertNotNull(build.getTraces());
assertEquals(build.getTraces().size(), 2);
assertEquals(build.getTraces().get(0).getSpanId(), "span1");
assertEquals(build.getTraces().get(1).getName(), "testenv/TestAdd");
}

public void testTraceJsonRoundTrip() throws Exception {
ObjectMapper mapper = new ObjectMapper();

Map<String, Object> attrs = new HashMap<>();
attrs.put("span.type", "test_run");
attrs.put("test.case_count", 2);
attrs.put("test.pass_count", 2);
attrs.put("test.exit_code", 0);

Trace trace = new Trace();
trace.setTraceId(TRACE_ID);
trace.setSpanId("b009c2f52bd10e4a");
trace.setParentSpanId("679767eacc094f15");
trace.setName("test run: go");
trace.setStartTime("2026-06-22T14:37:56.955856491Z");
trace.setEndTime("2026-06-22T14:38:03.242776111Z");
trace.setAttributes(attrs);
trace.setSpanKind("internal");

String json = mapper.writeValueAsString(trace);

// Verify snake_case JSON property names
assert json.contains("\"trace_id\"") : "JSON should use trace_id (snake_case)";
assert json.contains("\"span_id\"") : "JSON should use span_id";
assert json.contains("\"parent_span_id\"") : "JSON should use parent_span_id";
assert json.contains("\"start_time\"") : "JSON should use start_time";
assert json.contains("\"end_time\"") : "JSON should use end_time";
assert json.contains("\"span_kind\"") : "JSON should use span_kind";

Trace deserialized = mapper.readValue(json, Trace.class);
assertEquals(deserialized.getTraceId(), TRACE_ID);
assertEquals(deserialized.getSpanId(), "b009c2f52bd10e4a");
assertEquals(deserialized.getName(), "test run: go");
assertEquals(deserialized.getAttributes().get("test.case_count"), 2);
}

public void testBuildWithTracesJsonRoundTrip() throws Exception {
ObjectMapper mapper = new ObjectMapper();

Trace t1 = buildTrace("span1", "parent0", "step 1");
Trace t2 = buildTrace("span2", "span1", "step 2");

Build build = new Build();
build.setName("my-build");
build.setNumber("42");
build.setStarted("2026-06-22T14:37:56.000+0000");
build.setTraces(Arrays.asList(t1, t2));

String json = mapper.writeValueAsString(build);
assert json.contains("\"traces\"") : "Build JSON should include traces field";

Build deserialized = mapper.readValue(json, Build.class);
assertNotNull(deserialized.getTraces());
assertEquals(deserialized.getTraces().size(), 2);
assertEquals(deserialized.getTraces().get(0).getSpanId(), "span1");
assertEquals(deserialized.getTraces().get(1).getSpanId(), "span2");
}

// ── helpers ──────────────────────────────────────────────────────────────

private Trace buildTrace(String spanId, String parentSpanId, String name) {
Trace t = new Trace();
t.setTraceId(TRACE_ID);
t.setSpanId(spanId);
t.setParentSpanId(parentSpanId);
t.setName(name);
t.setStartTime("2026-06-22T14:37:56.000Z");
t.setEndTime("2026-06-22T14:37:57.000Z");
t.setSpanKind("internal");
return t;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class BuildInfoBuilder {
protected Properties properties;
protected BuildRetention buildRetention;
protected Issues issues;
protected List<org.jfrog.build.api.Trace> traces;

public BuildInfoBuilder(String name) {
this.name = name;
Expand Down Expand Up @@ -86,6 +87,7 @@ public BuildInfo build() {
buildInfo.setVcs(vcs);
buildInfo.setBuildRetention(buildRetention);
buildInfo.setIssues(issues);
buildInfo.setTraces(traces);
return buildInfo;
}

Expand Down Expand Up @@ -421,6 +423,11 @@ public BuildInfoBuilder issues(Issues issues) {
return this;
}

public BuildInfoBuilder traces(List<org.jfrog.build.api.Trace> traces) {
this.traces = traces;
return this;
}

public BuildInfoBuilder setProject(String project) {
this.project = project;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public class BuildInfo extends BaseBuildBean {

private Issues issues;

private List<org.jfrog.build.api.Trace> traces;

/**
* Formats the timestamp to the ISO date time string format expected by the build info API.
*
Expand Down Expand Up @@ -486,6 +488,14 @@ public void setIssues(Issues issues) {
this.issues = issues;
}

public List<org.jfrog.build.api.Trace> getTraces() {
return traces;
}

public void setTraces(List<org.jfrog.build.api.Trace> traces) {
this.traces = traces;
}

public void append(BuildInfo other) {
if (buildAgent == null) {
setBuildAgent(other.buildAgent);
Expand Down Expand Up @@ -574,6 +584,7 @@ public String toString() {
", statuses=" + statuses +
", buildDependencies=" + buildDependencies +
", issues=" + issues +
", traces=" + traces +
'}';
}

Expand All @@ -597,7 +608,8 @@ public Build ToBuild() {
.properties(getProperties())
.vcs(vcs == null ? null : vcs.stream().map(Vcs::ToBuildVcs).collect(Collectors.toList()))
.buildRetention(buildRetention == null ? null : buildRetention.ToBuildRetention())
.issues(issues == null ? null : issues.ToBuildIssues());
.issues(issues == null ? null : issues.ToBuildIssues())
.traces(traces);
if (modules != null) {
builder.modules(modules.stream().map(m -> new org.jfrog.build.api.builder.ModuleBuilder()
.type(m.getType() == null ? null : ModuleType.valueOf(m.getType().toUpperCase()))
Expand Down Expand Up @@ -634,7 +646,8 @@ public static BuildInfo ToBuildInfo(org.jfrog.build.api.Build build) {
.properties(build.getProperties())
.vcs(build.getVcs() == null ? null : build.getVcs().stream().map(Vcs::ToBuildInfoVcs).collect(Collectors.toList()))
.buildRetention(build.getBuildRetention() == null ? null : BuildRetention.ToBuildInfoRetention(build.getBuildRetention()))
.issues(build.getIssues() == null ? null : Issues.ToBuildInfoIssues(build.getIssues()));
.issues(build.getIssues() == null ? null : Issues.ToBuildInfoIssues(build.getIssues()))
.traces(build.getTraces());
if (build.getModules() != null) {
builder.modules(build.getModules().stream().map(m -> new ModuleBuilder()
.type(m.getType())
Expand Down
Loading