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
3 changes: 3 additions & 0 deletions modules/code/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ func ModuleInit(reg registry.ModuleRegistrar) {
reg.RegisterBodyFn(createPRCommentBodyFnID, createPRCommentBodyFn)
reg.RegisterBodyFn(createPRBodyFnID, createPRBodyFn)
reg.RegisterQueryParamsFn(listMinePRQueryParamsFnID, listMinePRQueryParamsFn)
reg.RegisterQueryParamsFn(reviewPendingPRQueryParamsFnID, reviewPendingPRQueryParamsFn)
reg.RegisterFetchFn(listMinePRFetchFnID, listMinePRFetchFn)
reg.RegisterFetchFn(codeownersPRFetchFnID, codeownersPRFetchFn)
reg.RegisterFlagResolveFn(resolvePrincipalIDFnID, resolvePrincipalID)
reg.RegisterBodyFn(reviewPRBodyFnID, reviewPRBodyFn)
reg.RegisterWorkflow(getPRWorkflowID, GetPRWorkflow)
reg.RegisterTextFormatter(reviewGroupTextFormatterID, reviewGroupTextFormatter)
reg.RegisterTextFormatter(insightTextFormatterID, insightTextFormatter)
Expand Down
73 changes: 73 additions & 0 deletions modules/code/codeowners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright © 2026 Harness Inc.
// SPDX-License-Identifier: Apache-2.0

package code

import (
"github.com/harness/cli/pkg/cmdctx"
"github.com/harness/cli/pkg/endpoint"
"github.com/harness/cli/pkg/spec"
)

const codeownersPRFetchFnID = "codeowners_pr_fetch"

// codeownersPRFetchFn delegates to HTTPFetchFn (which wraps the single
// TypesCodeOwnerEvaluation response as a one-item list via items_expr: "[it]"),
// then flattens evaluation_entries/owner_evaluations/user_group_owner_evaluations
// into one flat row per (pattern, owner) for the pr_codeowner noun's fields to consume.
func codeownersPRFetchFn(ctx *cmdctx.Ctx, ep *spec.EndpointSpec, wantStart, wantCount int, cursor any) (*cmdctx.PageResult, error) {
result, err := endpoint.HTTPFetchFn(ctx, ep, wantStart, wantCount, cursor)
if err != nil {
return nil, err
}
var rows []any
for _, raw := range result.Items {
m, ok := raw.(map[string]any)
if !ok {
continue
}
entries, _ := m["evaluation_entries"].([]any)
for _, e := range entries {
entry, ok := e.(map[string]any)
if !ok {
continue
}
pattern, _ := entry["pattern"].(string)

owners, _ := entry["owner_evaluations"].([]any)
for _, o := range owners {
rows = append(rows, ownerRow(pattern, "user", "", o))
}

groups, _ := entry["user_group_owner_evaluations"].([]any)
for _, g := range groups {
gm, ok := g.(map[string]any)
if !ok {
continue
}
groupName, _ := gm["name"].(string)
evals, _ := gm["evaluations"].([]any)
for _, o := range evals {
rows = append(rows, ownerRow(pattern, "group", groupName, o))
}
}
}
}
result.Items = rows
result.Last = true
return result, nil
}

// ownerRow builds one flat pr_codeowner row from a TypesOwnerEvaluation-shaped map.
func ownerRow(pattern, ownerType, groupName string, raw any) map[string]any {
om, _ := raw.(map[string]any)
owner, _ := om["owner"].(map[string]any)
return map[string]any{
"pattern": pattern,
"owner_type": ownerType,
"display_name": owner["display_name"],
"email": owner["email"],
"group_name": groupName,
"review_decision": om["review_decision"],
}
}
19 changes: 17 additions & 2 deletions modules/code/mine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (
)

const (
listMinePRQueryParamsFnID = "list_mine_pr_query_params"
listMinePRFetchFnID = "list_mine_pr_fetch"
listMinePRQueryParamsFnID = "list_mine_pr_query_params"
listMinePRFetchFnID = "list_mine_pr_fetch"
reviewPendingPRQueryParamsFnID = "review_pending_pr_query_params"
)

// listMinePRQueryParamsFn resolves the current user's Code numeric principal ID
Expand All @@ -27,6 +28,20 @@ func listMinePRQueryParamsFn(ctx *cmdctx.Ctx) (map[string]string, error) {
return map[string]string{"author_id": fmt.Sprintf("%d", id)}, nil
}

// reviewPendingPRQueryParamsFn resolves the current user's Code numeric principal ID
// and returns it as the reviewer_id query param, filtered to pending review decisions,
// for the cross-repo PR list endpoint.
func reviewPendingPRQueryParamsFn(ctx *cmdctx.Ctx) (map[string]string, error) {
id, err := CurrentUserPrincipalID(ctx)
if err != nil {
return nil, err
}
return map[string]string{
"reviewer_id": fmt.Sprintf("%d", id),
"review_decision": "pending",
}, nil
}

// listMinePRFetchFn delegates to HTTPFetchFn (which picks up the author_id via
// query_params_fn), then flattens each response item from
// {"pull_request": {...}, "repository": {...}} into a single map with
Expand Down
62 changes: 62 additions & 0 deletions modules/code/review.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright © 2026 Harness Inc.
// SPDX-License-Identifier: Apache-2.0

package code

import (
"fmt"

"github.com/harness/cli/pkg/client"
"github.com/harness/cli/pkg/cmdctx"
)

const reviewPRBodyFnID = "review_pr_body"

// cliToAPIReviewDecision maps the CLI's --decision values to the Code API's
// EnumPullReqReviewDecision values.
var cliToAPIReviewDecision = map[string]string{
"approve": "approved",
"changereq": "changereq",
}

// reviewPRBodyFn builds the review-submission request body for execute pr:review.
// The API requires commit_sha as a safety check, so we fetch the PR first (same
// pattern as mergePRBodyFn).
func reviewPRBodyFn(ctx *cmdctx.Ctx) (any, error) {
if len(ctx.IdParts) < 2 {
return nil, fmt.Errorf("expected <repo_id>/<pr_number>")
}
repoID := ctx.IdParts[0]
prNumber := ctx.IdParts[1]

decision := cmdctx.GetString(ctx.FlagValues, "decision")
apiDecision, ok := cliToAPIReviewDecision[decision]
if !ok {
return nil, fmt.Errorf("--decision must be %q or %q, got %q", "approve", "changereq", decision)
}

c := client.New(ctx)
params := map[string]string{
"accountIdentifier": ctx.Auth.AccountID,
"orgIdentifier": ctx.Auth.OrgID,
"projectIdentifier": ctx.Auth.ProjectID,
}
raw, _, err := c.Get(fmt.Sprintf("/code/api/v1/repos/%s/pullreq/%s", repoID, prNumber), params)
if err != nil {
return nil, fmt.Errorf("fetching PR to get source SHA: %w", err)
}

m, ok := raw.(map[string]any)
if !ok {
return nil, fmt.Errorf("unexpected PR response type")
}
sourceSHA, _ := m["source_sha"].(string)
if sourceSHA == "" {
return nil, fmt.Errorf("PR response missing source_sha")
}

return map[string]any{
"commit_sha": sourceSHA,
"decision": apiDecision,
}, nil
}
Loading