forked from Gitlink/gitlink-cli
merge: integrate hook-runner into webhook commands
CI / test (push) Failing after 2m6s
Details
CI / test (push) Failing after 2m6s
Details
- Add webhook +failed: list failed deliveries - Add webhook +task-view: view task details - Enhance webhook +tasks: add --limit, show UUID - Remove standalone hook-runner package - Update register.go
This commit is contained in:
parent
a115c97dde
commit
e60e876fad
|
|
@ -1,201 +0,0 @@
|
|||
package hookrunner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// v1RepoPath returns the v1 API path prefix.
|
||||
func v1RepoPath(ctx *common.RuntimeContext) string {
|
||||
return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
// Shortcuts returns webhook delivery monitoring shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List webhook delivery tasks (latest first)",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "limit", Short: "l", Usage: "Number of recent tasks to show", Default: "10"},
|
||||
},
|
||||
Run: runList,
|
||||
},
|
||||
{
|
||||
Name: "view",
|
||||
Description: "View webhook delivery task details",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "task-id", Usage: "Task ID", Required: true},
|
||||
},
|
||||
Run: runView,
|
||||
},
|
||||
{
|
||||
Name: "failed",
|
||||
Description: "List failed webhook deliveries",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "limit", Short: "l", Usage: "Number of recent failed tasks to show", Default: "10"},
|
||||
},
|
||||
Run: runFailed,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runList(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s/hooktasks", v1RepoPath(ctx), id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Limit results if specified
|
||||
limitStr := ctx.Arg("limit")
|
||||
if limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if ok {
|
||||
if tasks, ok := raw["hooktasks"].([]interface{}); ok {
|
||||
if len(tasks) > limit {
|
||||
raw["hooktasks"] = tasks[:limit]
|
||||
raw["total_count"] = float64(len(tasks[:limit]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Simplify output for listing - only show essential fields
|
||||
if raw, ok := env.Data.(map[string]interface{}); ok {
|
||||
if tasks, ok := raw["hooktasks"].([]interface{}); ok {
|
||||
for i, rawTask := range tasks {
|
||||
if task, ok := rawTask.(map[string]interface{}); ok {
|
||||
// Keep only essential fields for list view
|
||||
simple := map[string]interface{}{
|
||||
"uuid": task["uuid"],
|
||||
"event_type": task["event_type"],
|
||||
"delivered_time": task["delivered_time"],
|
||||
"is_succeed": task["is_succeed"],
|
||||
"is_delivered": task["is_delivered"],
|
||||
}
|
||||
tasks[i] = simple
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runView(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskID, err := ctx.RequireArg("task-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s/hooktasks", v1RepoPath(ctx), id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the specific task by ID
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to parse hook tasks")
|
||||
}
|
||||
tasks, ok := raw["hooktasks"].([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("no hook tasks found")
|
||||
}
|
||||
|
||||
taskNum, err := strconv.Atoi(taskID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid task-id: %s", taskID)
|
||||
}
|
||||
|
||||
for _, rawTask := range tasks {
|
||||
task, ok := rawTask.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if tid, ok := task["id"].(float64); ok && int(tid) == taskNum {
|
||||
return ctx.OutputData(task)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("task %s not found", taskID)
|
||||
}
|
||||
|
||||
func runFailed(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s/hooktasks", v1RepoPath(ctx), id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter for failed deliveries
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return ctx.Output(env)
|
||||
}
|
||||
tasks, ok := raw["hooktasks"].([]interface{})
|
||||
if !ok {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"hooktasks": []interface{}{},
|
||||
"total_count": 0,
|
||||
})
|
||||
}
|
||||
|
||||
limit := 0
|
||||
if limitStr := ctx.Arg("limit"); limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
|
||||
failedTasks := make([]interface{}, 0)
|
||||
for _, rawTask := range tasks {
|
||||
task, ok := rawTask.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if succeed, ok := task["is_succeed"].(bool); ok && !succeed {
|
||||
task["status"] = "failed"
|
||||
failedTasks = append(failedTasks, task)
|
||||
if limit > 0 && len(failedTasks) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"hooktasks": failedTasks,
|
||||
"total_count": len(failedTasks),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
package hookrunner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func runHookRunnerShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findHookRunnerShortcut(t, name)
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{
|
||||
HTTP: server.Client(),
|
||||
BaseURL: server.URL,
|
||||
},
|
||||
Owner: "test-owner",
|
||||
Repo: "test-repo",
|
||||
Format: "json",
|
||||
Args: args,
|
||||
}
|
||||
if ctx.Args == nil {
|
||||
ctx.Args = map[string]string{}
|
||||
}
|
||||
return shortcut.Run(ctx)
|
||||
}
|
||||
|
||||
func findHookRunnerShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
t.Helper()
|
||||
for _, shortcut := range Shortcuts() {
|
||||
if shortcut.Name == name {
|
||||
return shortcut
|
||||
}
|
||||
}
|
||||
t.Fatalf("shortcut %q not found", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, payload interface{}) {
|
||||
t.Helper()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
t.Fatalf("failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestHookRunnerList(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 2,
|
||||
"hooktasks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1, "event_type": "push", "is_succeed": true,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2, "event_type": "push", "is_succeed": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runHookRunnerShortcut(t, server, "list", map[string]string{"id": "10"}); err != nil {
|
||||
t.Fatalf("list failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerListRejectsMissingID(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("should not call API")
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runHookRunnerShortcut(t, server, "list", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when --id is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerView(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 3,
|
||||
"hooktasks": []interface{}{
|
||||
map[string]interface{}{"id": 1, "event_type": "push", "is_succeed": true},
|
||||
map[string]interface{}{"id": 2, "event_type": "issues_only", "is_succeed": false},
|
||||
map[string]interface{}{"id": 3, "event_type": "push", "is_succeed": true},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runHookRunnerShortcut(t, server, "view", map[string]string{
|
||||
"id": "10",
|
||||
"task-id": "2",
|
||||
}); err != nil {
|
||||
t.Fatalf("view failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerViewNotFound(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"hooktasks": []interface{}{
|
||||
map[string]interface{}{"id": 1, "event_type": "push"},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runHookRunnerShortcut(t, server, "view", map[string]string{
|
||||
"id": "10",
|
||||
"task-id": "999",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when task not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerFailed(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"hooktasks": []interface{}{
|
||||
map[string]interface{}{"id": 1, "event_type": "push", "is_succeed": true},
|
||||
map[string]interface{}{"id": 2, "event_type": "issues_only", "is_succeed": false},
|
||||
map[string]interface{}{"id": 3, "event_type": "push", "is_succeed": false},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runHookRunnerShortcut(t, server, "failed", map[string]string{"id": "10"}); err != nil {
|
||||
t.Fatalf("failed command error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerFailedAllSucceed(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"hooktasks": []interface{}{
|
||||
map[string]interface{}{"id": 1, "is_succeed": true},
|
||||
map[string]interface{}{"id": 2, "is_succeed": true},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runHookRunnerShortcut(t, server, "failed", map[string]string{"id": "10"}); err != nil {
|
||||
t.Fatalf("failed with all succeed error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerFailedEmpty(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"hooktasks": []interface{}{},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
if err := runHookRunnerShortcut(t, server, "failed", map[string]string{"id": "10"}); err != nil {
|
||||
t.Fatalf("failed with empty error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRunnerViewRejectsMissingTaskID(t *testing.T) {
|
||||
server := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("should not call API")
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runHookRunnerShortcut(t, server, "view", map[string]string{"id": "10"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when --task-id is missing")
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/dataset"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/file"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/hookrunner"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/issue"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/label"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/milestone"
|
||||
|
|
@ -43,7 +42,6 @@ func RegisterAll(root *cobra.Command) {
|
|||
"search": search.Shortcuts(),
|
||||
"ci": ci.Shortcuts(),
|
||||
"webhook": webhook.Shortcuts(),
|
||||
"hook-runner": hookrunner.Shortcuts(),
|
||||
"wiki": wiki.Shortcuts(),
|
||||
"snippet": snippet.Shortcuts(),
|
||||
"collaborator": collaborator.Shortcuts(),
|
||||
|
|
@ -70,7 +68,6 @@ func RegisterAll(root *cobra.Command) {
|
|||
"search": "Search operations",
|
||||
"ci": "CI/CD operations",
|
||||
"webhook": "Webhook operations",
|
||||
"hook-runner": "Webhook delivery monitoring",
|
||||
"wiki": "Wiki operations",
|
||||
"snippet": "Code snippet management",
|
||||
"collaborator": "Collaborator management",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package webhook
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
|
|
@ -21,17 +22,11 @@ var allowedWebhookEvents = map[string]bool{
|
|||
"pull_request_only": true, "pull_request_assign": true, "pull_request_comment": true,
|
||||
}
|
||||
|
||||
// Shortcuts returns webhook management shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List repository webhooks",
|
||||
Long: `List repository webhooks.
|
||||
|
||||
Returns all webhooks configured for the current repository.`,
|
||||
Example: ` # List all webhooks
|
||||
gitlink webhook +list`,
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -46,12 +41,6 @@ Returns all webhooks configured for the current repository.`,
|
|||
{
|
||||
Name: "view",
|
||||
Description: "View webhook details",
|
||||
Long: `View webhook details.
|
||||
|
||||
Shows the full configuration of a specific webhook including
|
||||
URL, events, type, and active status.`,
|
||||
Example: ` # View a webhook
|
||||
gitlink webhook +view --id 10`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
},
|
||||
|
|
@ -73,16 +62,6 @@ URL, events, type, and active status.`,
|
|||
{
|
||||
Name: "create",
|
||||
Description: "Create a repository webhook",
|
||||
Long: `Create a repository webhook.
|
||||
|
||||
Creates a new webhook that sends HTTP requests to the specified URL
|
||||
when certain events occur. Supported types: gitea, slack, discord,
|
||||
dingtalk, telegram, msteams, feishu, matrix, jianmu, softbot.`,
|
||||
Example: ` # Create a push webhook
|
||||
gitlink webhook +create --url https://example.com/hook --events push
|
||||
|
||||
# Create a webhook for multiple events
|
||||
gitlink webhook +create --url https://example.com/hook --events push,issues_only --secret mysecret`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "url", Short: "u", Usage: "Webhook target URL", Required: true},
|
||||
{Name: "events", Short: "e", Usage: "Comma-separated events, for example: push,issues_only", Required: true},
|
||||
|
|
@ -98,16 +77,6 @@ dingtalk, telegram, msteams, feishu, matrix, jianmu, softbot.`,
|
|||
{
|
||||
Name: "update",
|
||||
Description: "Update a repository webhook while preserving unspecified fields when available",
|
||||
Long: `Update a repository webhook while preserving unspecified fields when available.
|
||||
|
||||
Fetches the current webhook configuration, merges changes from the provided
|
||||
flags, and updates the webhook. Fields not specified are preserved from the
|
||||
current configuration.`,
|
||||
Example: ` # Update a webhook URL
|
||||
gitlink webhook +update --id 10 --url https://new.example.com/hook
|
||||
|
||||
# Update webhook events
|
||||
gitlink webhook +update --id 10 --events push,pull_request_only`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "url", Short: "u", Usage: "Webhook target URL"},
|
||||
|
|
@ -124,11 +93,6 @@ current configuration.`,
|
|||
{
|
||||
Name: "delete",
|
||||
Description: "Delete a repository webhook",
|
||||
Long: `Delete a repository webhook.
|
||||
|
||||
Permanently removes the webhook from the repository.`,
|
||||
Example: ` # Delete a webhook
|
||||
gitlink webhook +delete --id 10`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
},
|
||||
|
|
@ -150,11 +114,6 @@ Permanently removes the webhook from the repository.`,
|
|||
{
|
||||
Name: "test",
|
||||
Description: "Trigger a test delivery for a webhook",
|
||||
Long: `Trigger a test delivery for a webhook.
|
||||
|
||||
Sends a test payload to the webhook URL to verify the configuration.`,
|
||||
Example: ` # Test a webhook
|
||||
gitlink webhook +test --id 10`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
},
|
||||
|
|
@ -176,14 +135,9 @@ Sends a test payload to the webhook URL to verify the configuration.`,
|
|||
{
|
||||
Name: "tasks",
|
||||
Description: "List webhook delivery tasks",
|
||||
Long: `List webhook delivery tasks.
|
||||
|
||||
Returns the history of webhook deliveries including status,
|
||||
response code, and timing information.`,
|
||||
Example: ` # List delivery tasks for a webhook
|
||||
gitlink webhook +tasks --id 10`,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "limit", Short: "l", Usage: "Number of recent tasks to show", Default: "10"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -197,9 +151,141 @@ response code, and timing information.`,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply limit and simplify output to show UUID
|
||||
if raw, ok := env.Data.(map[string]interface{}); ok {
|
||||
if tasks, ok := raw["hooktasks"].([]interface{}); ok {
|
||||
limitStr := ctx.Arg("limit")
|
||||
if limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 && len(tasks) > limit {
|
||||
tasks = tasks[:limit]
|
||||
raw["hooktasks"] = tasks
|
||||
raw["total_count"] = float64(len(tasks))
|
||||
}
|
||||
}
|
||||
for i, rawTask := range tasks {
|
||||
if task, ok := rawTask.(map[string]interface{}); ok {
|
||||
tasks[i] = map[string]interface{}{
|
||||
"uuid": task["uuid"],
|
||||
"event_type": task["event_type"],
|
||||
"delivered_time": task["delivered_time"],
|
||||
"is_succeed": task["is_succeed"],
|
||||
"is_delivered": task["is_delivered"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "failed",
|
||||
Description: "List failed webhook deliveries",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "limit", Short: "l", Usage: "Number of recent failed tasks", Default: "10"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/hooktasks", webhookItemPath(ctx, id)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return ctx.Output(env)
|
||||
}
|
||||
tasks, ok := raw["hooktasks"].([]interface{})
|
||||
if !ok {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"hooktasks": []interface{}{},
|
||||
"total_count": 0,
|
||||
})
|
||||
}
|
||||
limit := 0
|
||||
if limitStr := ctx.Arg("limit"); limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
failedTasks := make([]interface{}, 0)
|
||||
for _, rawTask := range tasks {
|
||||
task, ok := rawTask.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if succeed, ok := task["is_succeed"].(bool); ok && !succeed {
|
||||
failedTasks = append(failedTasks, map[string]interface{}{
|
||||
"uuid": task["uuid"],
|
||||
"event_type": task["event_type"],
|
||||
"delivered_time": task["delivered_time"],
|
||||
"status": "failed",
|
||||
})
|
||||
if limit > 0 && len(failedTasks) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"hooktasks": failedTasks,
|
||||
"total_count": len(failedTasks),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "task-view",
|
||||
Description: "View webhook delivery task details",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Webhook ID", Required: true},
|
||||
{Name: "task-id", Usage: "Task ID (numeric)", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskID, err := ctx.RequireArg("task-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/hooktasks", webhookItemPath(ctx, id)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to parse hook tasks")
|
||||
}
|
||||
tasks, ok := raw["hooktasks"].([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("no hook tasks found")
|
||||
}
|
||||
taskNum, err := strconv.Atoi(taskID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid task-id: %s", taskID)
|
||||
}
|
||||
for _, rawTask := range tasks {
|
||||
task, ok := rawTask.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if tid, ok := task["id"].(float64); ok && int(tid) == taskNum {
|
||||
return ctx.OutputData(task)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("task %s not found", taskID)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +312,6 @@ func runUpdate(ctx *common.RuntimeContext) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
current, err := fetchWebhook(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch webhook: %w", err)
|
||||
|
|
|
|||
Loading…
Reference in New Issue