Refactor PR comment handling by moving it to the CLI in check.go, removing the previous implementation from entrypoint.sh. Enhance report summary to include the number of files scanned and ensure markdown report generation is streamlined.

This commit is contained in:
Luke Hagar
2025-09-12 18:32:42 +00:00
parent f0c3f3f912
commit d54ac41670
3 changed files with 121 additions and 72 deletions

View File

@@ -1,9 +1,12 @@
package cmd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sort"
@@ -222,25 +225,46 @@ func init() {
_ = f.Close()
}
// Optionally write Markdown report for PR comment consumption
if mdOut != "" {
base := repoBlobBase
if strings.TrimSpace(base) == "" {
base = os.Getenv("SLINKY_REPO_BLOB_BASE_URL")
}
summary := report.Summary{
RootPath: displayRoot,
StartedAt: startedAt,
FinishedAt: time.Now(),
Processed: total,
OK: okCount,
Fail: failCount,
JSONPath: jsonOut,
RepoBlobBaseURL: base,
}
if _, err := report.WriteMarkdown(mdOut, failedResults, summary); err != nil {
// Build report summary
base := repoBlobBase
if strings.TrimSpace(base) == "" {
base = os.Getenv("SLINKY_REPO_BLOB_BASE_URL")
}
summary := report.Summary{
RootPath: displayRoot,
StartedAt: startedAt,
FinishedAt: time.Now(),
Processed: total,
OK: okCount,
Fail: failCount,
FilesScanned: countFiles(urlToFiles),
JSONPath: jsonOut,
RepoBlobBaseURL: base,
}
// Ensure we have a markdown file if needed for PR comment
mdPath := mdOut
ghRepo, ghPR, ghToken, ghOK := detectGitHubPR()
if strings.TrimSpace(mdPath) != "" {
if _, err := report.WriteMarkdown(mdPath, failedResults, summary); err != nil {
return err
}
} else if ghOK {
p, err := report.WriteMarkdown("", failedResults, summary)
if err != nil {
return err
}
mdPath = p
}
// If running on a PR, post or update the comment
if ghOK && strings.TrimSpace(mdPath) != "" {
b, rerr := os.ReadFile(mdPath)
if rerr == nil {
body := string(b)
body = fmt.Sprintf("%s\n%s", "<!-- slinky-report -->", body)
_ = upsertPRComment(ghRepo, ghPR, ghToken, body)
}
}
fmt.Printf("Checked %d URLs: %d OK, %d failed\n", total, okCount, failCount)
@@ -284,3 +308,75 @@ func toSlash(p string) string {
func hasGlobMeta(s string) bool {
return strings.ContainsAny(s, "*?[")
}
func countFiles(urlToFiles map[string][]string) int {
seen := make(map[string]struct{})
for _, files := range urlToFiles {
for _, f := range files {
seen[f] = struct{}{}
}
}
return len(seen)
}
func detectGitHubPR() (repo string, prNumber int, token string, ok bool) {
repo = os.Getenv("GITHUB_REPOSITORY")
token = os.Getenv("GITHUB_TOKEN")
eventPath := os.Getenv("GITHUB_EVENT_PATH")
if repo == "" || eventPath == "" || token == "" {
return "", 0, "", false
}
data, err := os.ReadFile(eventPath)
if err != nil {
return "", 0, "", false
}
var ev struct {
PullRequest struct {
Number int `json:"number"`
} `json:"pull_request"`
}
_ = json.Unmarshal(data, &ev)
if ev.PullRequest.Number == 0 {
return "", 0, "", false
}
return repo, ev.PullRequest.Number, token, true
}
func upsertPRComment(repo string, prNumber int, token string, body string) error {
apiBase := "https://api.github.com"
listURL := fmt.Sprintf("%s/repos/%s/issues/%d/comments?per_page=100", apiBase, repo, prNumber)
req, _ := http.NewRequest(http.MethodGet, listURL, nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/vnd.github+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var comments []struct {
ID int `json:"id"`
Body string `json:"body"`
}
b, _ := io.ReadAll(resp.Body)
_ = json.Unmarshal(b, &comments)
var existingID int
for _, c := range comments {
if strings.Contains(c.Body, "<!-- slinky-report -->") {
existingID = c.ID
break
}
}
payload, _ := json.Marshal(map[string]string{"body": body})
if existingID > 0 {
u := fmt.Sprintf("%s/repos/%s/issues/comments/%d", apiBase, repo, existingID)
req, _ = http.NewRequest(http.MethodPatch, u, bytes.NewReader(payload))
} else {
u := fmt.Sprintf("%s/repos/%s/issues/%d/comments", apiBase, repo, prNumber)
req, _ = http.NewRequest(http.MethodPost, u, bytes.NewReader(payload))
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Content-Type", "application/json")
_, _ = http.DefaultClient.Do(req)
return nil
}

View File

@@ -84,59 +84,7 @@ if [ "${STEP_SUMMARY_ARG}" = "true" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ] && [
cat "${MD_OUT_ARG}" >> "$GITHUB_STEP_SUMMARY"
fi
# Post PR comment if this is a PR and requested (even if the run failed)
if [ "${COMMENT_PR_ARG}" = "true" ]; then
if [ -z "${MD_OUT_ARG}" ] || [ ! -f "${MD_OUT_ARG}" ]; then
echo "[slinky] No markdown report found at '${MD_OUT_ARG}', skipping PR comment."
else
PR_NUMBER=""
if [ -n "${GITHUB_EVENT_PATH:-}" ] && command -v jq >/dev/null 2>&1; then
PR_NUMBER="$(jq -r '.pull_request.number // empty' "$GITHUB_EVENT_PATH" || true)"
fi
if [ -z "${PR_NUMBER}" ]; then
echo "[slinky] Not a pull_request event or PR number not found; skipping PR comment."
elif [ -z "${GITHUB_TOKEN:-}" ]; then
echo "[slinky] GITHUB_TOKEN not available; ensure the workflow grants permissions and passes the token as env. Skipping PR comment."
elif [ -z "${GITHUB_REPOSITORY:-}" ]; then
echo "[slinky] GITHUB_REPOSITORY not set; skipping PR comment."
else
BODY_CONTENT="$(cat "${MD_OUT_ARG}")"
COMMENT_BODY="$(printf '%s\n%s\n' '<!-- slinky-report -->' "${BODY_CONTENT}")"
# Try to find an existing slinky comment to update
COMMENTS_JSON=$(curl -sS -H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100") || COMMENTS_JSON="[]"
EXISTING_ID=$(printf '%s' "${COMMENTS_JSON}" | jq -r '[.[] | select((.body // "") | contains("<!-- slinky-report -->"))][0].id // empty')
if [ -n "${EXISTING_ID}" ]; then
# Update existing comment
curl -sS -H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-X PATCH "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/comments/${EXISTING_ID}" \
-d "$(printf '{"body": %s}' "$(jq -Rs . <<EOF
${COMMENT_BODY}
EOF
)" )" >/dev/null || true
echo "[slinky] Updated existing PR comment #${EXISTING_ID}."
else
# Create new comment
curl -sS -H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-X POST "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
-d "$(printf '{"body": %s}' "$(jq -Rs . <<EOF
${COMMENT_BODY}
EOF
)" )" >/dev/null || true
echo "[slinky] Posted PR comment to #${PR_NUMBER}."
fi
fi
fi
fi
# PR comment handling is now done in the CLI itself when running on a PR
exit ${SLINKY_EXIT_CODE:-0}

View File

@@ -24,6 +24,7 @@ type Summary struct {
AvgRPS float64
PeakRPS float64
LowRPS float64
FilesScanned int
JSONPath string
RepoBlobBaseURL string // e.g. https://github.com/owner/repo/blob/<sha>
}
@@ -51,17 +52,21 @@ func WriteMarkdown(path string, results []web.Result, s Summary) (string, error)
// Title
buf.WriteString("## Slinky Test Report\n\n")
// Last run (not in the bullet list)
// Last run (not in the bullet list). Render in US Central Time.
dur := s.FinishedAt.Sub(s.StartedAt)
if dur < 0 {
dur = 0
}
buf.WriteString(fmt.Sprintf("Last Run: %s (Duration: %s)\n\n", s.StartedAt.Format("2006-01-02 15:04:05 MST"), dur.Truncate(time.Millisecond)))
loc, _ := time.LoadLocation("America/Chicago")
buf.WriteString(fmt.Sprintf("Last Run: %s (Duration: %s)\n\n", s.StartedAt.In(loc).Format("2006-01-02 15:04:05 MST"), dur.Truncate(time.Millisecond)))
// Summary list: Pass, Fail, Total
buf.WriteString(fmt.Sprintf("- **Pass**: %d\n", s.OK))
buf.WriteString(fmt.Sprintf("- **Fail**: %d\n", s.Fail))
buf.WriteString(fmt.Sprintf("- **Total**: %d\n", s.Processed))
if s.FilesScanned > 0 {
buf.WriteString(fmt.Sprintf("- **Files Scanned**: %d\n", s.FilesScanned))
}
// Optional root
if strings.TrimSpace(s.RootPath) != "." && strings.TrimSpace(s.RootPath) != "" && s.RootPath != string(filepath.Separator) {