feat(serverless-spark)!: add URLs to list_batches output

Unlike get_batch, in this case we are not returning a JSON type directly
from the server, so we can add the new fields in our top-level object
rather than wrapping.
This commit is contained in:
Dave Borowitz
2025-11-21 09:52:10 -08:00
parent e29c0616d6
commit 5605eabd69
3 changed files with 41 additions and 6 deletions

View File

@@ -50,14 +50,18 @@ tools:
"uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"state": "SUCCEEDED",
"creator": "alice@example.com",
"createTime": "2023-10-27T10:00:00Z"
"createTime": "2023-10-27T10:00:00Z",
"consoleUrl": "https://console.cloud.google.com/dataproc/batches/us-central1/batch-abc-123/summary?project=my-project",
"logsUrl": "https://console.cloud.google.com/logs/viewer?advancedFilter=resource.type%3D%22cloud_dataproc_batch%22%0Aresource.labels.project_id%3D%22my-project%22%0Aresource.labels.location%3D%22us-central1%22%0Aresource.labels.batch_id%3D%22batch-abc-123%22%0Atimestamp%3E%3D%222023-10-27T09%3A59%3A00Z%22%0Atimestamp%3C%3D%222023-10-27T10%3A10%3A00Z%22&project=my-project&resource=cloud_dataproc_batch%2Fbatch_id%2Fbatch-abc-123"
},
{
"name": "projects/my-project/locations/us-central1/batches/batch-def-456",
"uuid": "b2c3d4e5-f6a7-8901-2345-678901bcdefa",
"state": "FAILED",
"creator": "alice@example.com",
"createTime": "2023-10-27T11:30:00Z"
"createTime": "2023-10-27T11:30:00Z",
"consoleUrl": "https://console.cloud.google.com/dataproc/batches/us-central1/batch-def-456/summary?project=my-project",
"logsUrl": "https://console.cloud.google.com/logs/viewer?advancedFilter=resource.type%3D%22cloud_dataproc_batch%22%0Aresource.labels.project_id%3D%22my-project%22%0Aresource.labels.location%3D%22us-central1%22%0Aresource.labels.batch_id%3D%22batch-def-456%22%0Atimestamp%3E%3D%222023-10-27T11%3A29%3A00Z%22%0Atimestamp%3C%3D%222023-10-27T11%3A40%3A00Z%22&project=my-project&resource=cloud_dataproc_batch%2Fbatch_id%2Fbatch-def-456"
}
],
"nextPageToken": "abcd1234"

View File

@@ -24,6 +24,7 @@ import (
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/sources/serverlessspark"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/serverlessspark/common"
"github.com/googleapis/genai-toolbox/internal/util/parameters"
"google.golang.org/api/iterator"
)
@@ -124,6 +125,8 @@ type Batch struct {
Creator string `json:"creator"`
CreateTime string `json:"createTime"`
Operation string `json:"operation"`
ConsoleURL string `json:"consoleUrl"`
LogsURL string `json:"logsUrl"`
}
// Invoke executes the tool's operation.
@@ -159,15 +162,26 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para
return nil, fmt.Errorf("failed to list batches: %w", err)
}
batches := ToBatches(batchPbs)
batches, err := ToBatches(batchPbs)
if err != nil {
return nil, err
}
return ListBatchesResponse{Batches: batches, NextPageToken: nextPageToken}, nil
}
// ToBatches converts a slice of protobuf Batch messages to a slice of Batch structs.
func ToBatches(batchPbs []*dataprocpb.Batch) []Batch {
func ToBatches(batchPbs []*dataprocpb.Batch) ([]Batch, error) {
batches := make([]Batch, 0, len(batchPbs))
for _, batchPb := range batchPbs {
consoleUrl, err := common.BatchConsoleURLFromProto(batchPb)
if err != nil {
return nil, fmt.Errorf("error generating console url: %v", err)
}
logsUrl, err := common.BatchLogsURLFromProto(batchPb)
if err != nil {
return nil, fmt.Errorf("error generating logs url: %v", err)
}
batch := Batch{
Name: batchPb.Name,
UUID: batchPb.Uuid,
@@ -175,10 +189,12 @@ func ToBatches(batchPbs []*dataprocpb.Batch) []Batch {
Creator: batchPb.Creator,
CreateTime: batchPb.CreateTime.AsTime().Format(time.RFC3339),
Operation: batchPb.Operation,
ConsoleURL: consoleUrl,
LogsURL: logsUrl,
}
batches = append(batches, batch)
}
return batches
return batches, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) {

View File

@@ -744,6 +744,17 @@ func runListBatchesTest(t *testing.T, client *dataproc.BatchControllerClient, ct
if !reflect.DeepEqual(actual, tc.want) {
t.Fatalf("unexpected batches: got %+v, want %+v", actual, tc.want)
}
// want has URLs because it's created from Batch instances by the same utility function
// used by the tool internals. Double-check that the URLs are reasonable.
for _, batch := range tc.want {
if !strings.HasPrefix(batch.ConsoleURL, batchURLPrefix) {
t.Errorf("unexpected consoleUrl in batch: %#v", batch)
}
if !strings.HasPrefix(batch.LogsURL, logsURLPrefix) {
t.Errorf("unexpected logsUrl in batch: %#v", batch)
}
}
})
}
}
@@ -772,8 +783,12 @@ func listBatchesRpc(t *testing.T, client *dataproc.BatchControllerClient, ctx co
if !exact && (len(batchPbs) == 0 || len(batchPbs) > n) {
t.Fatalf("expected between 1 and %d batches, got %d", n, len(batchPbs))
}
batches, err := serverlesssparklistbatches.ToBatches(batchPbs)
if err != nil {
t.Fatalf("failed to convert batches to JSON: %v", err)
}
return serverlesssparklistbatches.ToBatches(batchPbs)
return batches
}
func runAuthTest(t *testing.T, toolName string, request map[string]any, wantStatus int) {