Skip to content

Commit a050658

Browse files
Saranya-jenaHarness
authored andcommitted
chore: [ML-1420]: Added log keys as a response in the get_execution tool (#206)
* 7e694d chore: [ML-1420]: resolved comments * 491912 chore: [ML-1420]: resolved conflicts * 96f1d6 chore: [ML-1320]: resolved comments * 0aae7e chore: [ML-1420]: Add log keys as a response in the get_execution tool
1 parent 2effbb1 commit a050658

File tree

4 files changed

+136
-16
lines changed

4 files changed

+136
-16
lines changed

client/dto/pipeline.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,17 @@ type PipelineExecutionOptions struct {
152152
// PipelineExecutionResponse represents the full response structure for pipeline execution details
153153
type PipelineExecutionResponse struct {
154154
PipelineExecutionSummary PipelineExecution `json:"pipelineExecutionSummary,omitempty"`
155+
ExecutionGraph ExecutionGraph `json:"executionGraph,omitempty"`
156+
ChildGraph ChildGraph `json:"childGraph,omitempty"`
157+
}
158+
159+
type ChildGraph struct {
160+
PipelineExecutionSummary PipelineExecution `json:"pipelineExecutionSummary,omitempty"`
161+
ExecutionGraph ExecutionGraph `json:"executionGraph,omitempty"`
162+
}
163+
164+
type FinalLogKeys struct {
165+
StepLogBaseKeys []string `json:"stepLogBaseKeys,omitempty"`
155166
}
156167

157168
// PipelineExecution represents a pipeline execution
@@ -180,6 +191,7 @@ type PipelineExecution struct {
180191

181192
// ExecutionFailureInfo represents the failure information of a pipeline execution
182193
type ExecutionFailureInfo struct {
194+
Message string `json:"message,omitempty"`
183195
FailureTypeList []string `json:"failureTypeList,omitempty"`
184196
ResponseMessages []ExecutionResponseMessage `json:"responseMessages,omitempty"`
185197
}
@@ -195,6 +207,30 @@ type ExecutionException struct {
195207
Message string `json:"message,omitempty"`
196208
}
197209

210+
type ExecutionGraph struct {
211+
RootNodeId string `json:"rootNodeId,omitempty"`
212+
NodeMap map[string]ExecutionNode `json:"nodeMap,omitempty"`
213+
}
214+
215+
type ExecutionNode struct {
216+
Uuid string `json:"uuid,omitempty"`
217+
SetupId string `json:"setupId,omitempty"`
218+
Name string `json:"name,omitempty"`
219+
Identifier string `json:"identifier,omitempty"`
220+
BaseFqn string `json:"baseFqn,omitempty"`
221+
StepType string `json:"stepType,omitempty"`
222+
Status string `json:"status,omitempty"`
223+
UnitProgresses []UnitProgress `json:"unitProgresses,omitempty"`
224+
LogBaseKey string `json:"logBaseKey,omitempty"`
225+
}
226+
227+
type UnitProgress struct {
228+
UnitName string `json:"unitName,omitempty"`
229+
Status string `json:"status,omitempty"`
230+
StartTime string `json:"startTime,omitempty"`
231+
EndTime string `json:"endTime,omitempty"`
232+
}
233+
198234
type User struct {
199235
Email string `json:"email,omitempty"`
200236
UserName string `json:"userName,omitempty"`

client/logs.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,27 @@ func (l *LogService) GetDownloadLogsURL(ctx context.Context, scope dto.Scope, pl
3232
slog.Info("Building log key for log download from execution details")
3333
// First, get the pipeline execution details to determine the prefix format
3434
pipelineService := &PipelineService{Client: l.PipelineClient} // TODO: needs to be changed for internal case, we should move this above
35-
execution, err := pipelineService.GetExecution(ctx, scope, planExecutionID)
35+
execution, err := pipelineService.GetExecutionWithLogKeys(ctx, scope, planExecutionID, "", "")
3636
if err != nil {
3737
return "", fmt.Errorf("failed to get execution details: %w", err)
3838
}
3939

4040
// Build the log key based on the execution details
41-
if execution.Data.ShouldUseSimplifiedBaseKey {
41+
if execution.Data.Execution.ShouldUseSimplifiedBaseKey {
4242
// Simplified key format
4343
finalLogKey = fmt.Sprintf("%s/pipeline/%s/%d/-%s",
4444
scope.AccountID,
45-
execution.Data.PipelineIdentifier,
46-
execution.Data.RunSequence,
45+
execution.Data.Execution.PipelineIdentifier,
46+
execution.Data.Execution.RunSequence,
4747
planExecutionID)
4848
} else {
4949
// Standard key format
5050
finalLogKey = fmt.Sprintf("accountId:%s/orgId:%s/projectId:%s/pipelineId:%s/runSequence:%d/level0:pipeline",
5151
scope.AccountID,
52-
execution.Data.OrgIdentifier,
53-
execution.Data.ProjectIdentifier,
54-
execution.Data.PipelineIdentifier,
55-
execution.Data.RunSequence)
52+
execution.Data.Execution.OrgIdentifier,
53+
execution.Data.Execution.ProjectIdentifier,
54+
execution.Data.Execution.PipelineIdentifier,
55+
execution.Data.Execution.RunSequence)
5656
}
5757
}
5858

client/pipelines.go

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package client
33
import (
44
"context"
55
"fmt"
6+
"log/slog"
67
"strconv"
8+
"strings"
79

810
"github.com/harness/harness-mcp/client/dto"
911
)
@@ -147,18 +149,37 @@ func (p *PipelineService) ListExecutions(
147149
return response, nil
148150
}
149151

150-
// GetExecution retrieves details of a specific pipeline execution
151-
func (p *PipelineService) GetExecution(
152+
// PipelineExecutionResult contains the pipeline execution details and log keys
153+
type PipelineExecutionResult struct {
154+
Execution dto.PipelineExecution `json:"execution,omitempty"`
155+
LogKeys dto.FinalLogKeys `json:"logKeys,omitempty"`
156+
}
157+
158+
// GetExecutionWithLogKeys retrieves details of a specific pipeline execution along with log keys
159+
func (p *PipelineService) GetExecutionWithLogKeys(
152160
ctx context.Context,
153161
scope dto.Scope,
154162
planExecutionID string,
155-
) (*dto.Entity[dto.PipelineExecution], error) {
163+
stageNodeID string,
164+
childStageNodeID string,
165+
) (*dto.Entity[PipelineExecutionResult], error) {
156166
path := fmt.Sprintf(pipelineExecutionGetPath, planExecutionID)
157167

158168
// Prepare query parameters
159169
params := make(map[string]string)
160170
addScope(scope, params)
161171

172+
// Add stageNodeId if provided
173+
if stageNodeID != "" {
174+
params["stageNodeId"] = stageNodeID
175+
}
176+
177+
// Add childStageNodeId if provided
178+
if childStageNodeID != "" {
179+
params["childStageNodeId"] = childStageNodeID
180+
}
181+
182+
slog.Info("Fetching execution details with log keys", "planExecutionID", planExecutionID, "stageNodeID", stageNodeID, "childStageNodeID", childStageNodeID)
162183
// Initialize the response object with the new structure that matches the API response
163184
response := &dto.Entity[dto.PipelineExecutionResponse]{}
164185

@@ -167,16 +188,64 @@ func (p *PipelineService) GetExecution(
167188
if err != nil {
168189
return nil, fmt.Errorf("failed to get execution details: %w", err)
169190
}
191+
// Extract log keys from the execution graph
192+
logKeys := extractLogKeys(response.Data)
170193

171-
// Extract the execution details from the nested structure
172-
result := &dto.Entity[dto.PipelineExecution]{
194+
// Create the result with both execution details and log keys
195+
result := &dto.Entity[PipelineExecutionResult]{
173196
Status: response.Status,
174-
Data: response.Data.PipelineExecutionSummary,
197+
Data: PipelineExecutionResult{
198+
Execution: response.Data.PipelineExecutionSummary,
199+
LogKeys: logKeys,
200+
},
175201
}
176-
202+
slog.Info("Returning execution result with child graph", "childGraphExists", response.Data.ChildGraph.ExecutionGraph.NodeMap != nil, "childPipelineId", response.Data.ChildGraph.PipelineExecutionSummary.PipelineIdentifier)
177203
return result, nil
178204
}
179205

206+
// extractLogKeys extracts log keys from the execution graph
207+
func extractLogKeys(executionResponse dto.PipelineExecutionResponse) dto.FinalLogKeys {
208+
logKeys := dto.FinalLogKeys{
209+
StepLogBaseKeys: []string{},
210+
}
211+
212+
// Process the main execution graph
213+
if executionResponse.ExecutionGraph.NodeMap != nil {
214+
processExecutionGraph(executionResponse.ExecutionGraph, &logKeys)
215+
}
216+
217+
// Process child graphs if they exist
218+
if executionResponse.ChildGraph.ExecutionGraph.NodeMap != nil {
219+
processExecutionGraph(executionResponse.ChildGraph.ExecutionGraph, &logKeys)
220+
}
221+
222+
return logKeys
223+
}
224+
225+
// processExecutionGraph processes an execution graph to extract log keys
226+
func processExecutionGraph(graph dto.ExecutionGraph, logKeys *dto.FinalLogKeys) {
227+
// Extract all log base keys for steps
228+
stepLogBaseKeys := extractLogBaseKeysForSteps(graph.NodeMap)
229+
230+
// Store the step log base keys in the result
231+
logKeys.StepLogBaseKeys = append(logKeys.StepLogBaseKeys, stepLogBaseKeys...)
232+
}
233+
234+
// extractLogBaseKeysForSteps extracts an array of logbasekeys where node.BaseFqn contains "steps"
235+
func extractLogBaseKeysForSteps(nodes map[string]dto.ExecutionNode) []string {
236+
var logBaseKeys []string
237+
238+
// Iterate through all nodes and find those containing "steps" in BaseFqn
239+
for _, node := range nodes {
240+
// Check if the BaseFqn contains "steps"
241+
if strings.Contains(node.BaseFqn, ".steps.") && node.LogBaseKey != "" {
242+
logBaseKeys = append(logBaseKeys, node.LogBaseKey)
243+
}
244+
}
245+
246+
return logBaseKeys
247+
}
248+
180249
func (p *PipelineService) FetchExecutionURL(
181250
ctx context.Context,
182251
scope dto.Scope,
@@ -187,6 +256,7 @@ func (p *PipelineService) FetchExecutionURL(
187256
// Prepare query parameters
188257
params := make(map[string]string)
189258
addScope(scope, params)
259+
190260
params["pipelineIdentifier"] = pipelineID
191261
params["planExecutionId"] = planExecutionID
192262

@@ -256,6 +326,7 @@ func (p *PipelineService) GetInputSet(
256326
// Prepare query parameters
257327
params := make(map[string]string)
258328
addScope(scope, params)
329+
259330
params["pipelineIdentifier"] = pipelineIdentifier
260331

261332
// Initialize the response object

pkg/harness/tools/pipelines.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ func GetExecutionTool(config *config.Config, client *client.PipelineService) (to
140140
mcp.Required(),
141141
mcp.Description("The ID of the plan execution"),
142142
),
143+
mcp.WithString("stage_node_id",
144+
mcp.Description("Optional ID of the stage node to filter the execution details"),
145+
),
146+
mcp.WithString("child_stage_node_id",
147+
mcp.Description("Optional ID of the child stage node to filter the execution details"),
148+
),
143149
common.WithScope(config, true),
144150
),
145151
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -148,12 +154,19 @@ func GetExecutionTool(config *config.Config, client *client.PipelineService) (to
148154
return mcp.NewToolResultError(err.Error()), nil
149155
}
150156

157+
// Get optional stage node ID
158+
stageNodeID, _ := OptionalParam[string](request, "stage_node_id")
159+
160+
// Get optional child stage node ID
161+
childStageNodeID, _ := OptionalParam[string](request, "child_stage_node_id")
162+
151163
scope, err := common.FetchScope(config, request, true)
152164
if err != nil {
153165
return mcp.NewToolResultError(err.Error()), nil
154166
}
155167

156-
data, err := client.GetExecution(ctx, scope, planExecutionID)
168+
// Pass both stageNodeID and childStageNodeID to the client
169+
data, err := client.GetExecutionWithLogKeys(ctx, scope, planExecutionID, stageNodeID, childStageNodeID)
157170
if err != nil {
158171
return nil, fmt.Errorf("failed to get execution details: %w", err)
159172
}

0 commit comments

Comments
 (0)