Simon Ser: 1 todo ticket create: new command 3 files changed, 104 insertions(+), 0 deletions(-)
Tickets having a wrong timestamp is a bug on our side after all [1]. I think this needs a workaround in gqlclient until omitempty will work on structs [2]. [1]: https://todo.sr.ht/~sircmpwn/todo.sr.ht/258 [2]: https://github.com/golang/go/issues/11939
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~emersion/hut-dev/patches/29580/mbox | git am -3Learn more about email & git
Pretty similar to `hut todo ticket comment`, except we need to provide both a subject and a body. Add commented instructions when using an editor, similar to git-commit's behavior.
Code mostly LGTM, but right now tickets that are created this way, will have a bug that will hinder other GraphQL operations (see IRC), so I think we should wait a bit before applying this.
--- srht/todosrht/gql.go | 11 ++++ srht/todosrht/operations.graphql | 6 +++ todo.go | 87 ++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+)
Manpage entry is missing.
diff --git a/srht/todosrht/gql.go b/srht/todosrht/gql.go index e6cb2bfbdaa7..d1708fabe870 100644 --- a/srht/todosrht/gql.go +++ b/srht/todosrht/gql.go @@ -549,3 +549,14 @@ func UpdateTicketStatus(client *gqlclient.Client, ctx context.Context, trackerId err = client.Execute(ctx, op, &respData) return respData.UpdateTicketStatus, err } + +func SubmitTicket(client *gqlclient.Client, ctx context.Context, trackerId int32, input SubmitTicketInput) (submitTicket *Ticket, err error) { + op := gqlclient.NewOperation("mutation submitTicket ($trackerId: Int!, $input: SubmitTicketInput!) {\n\tsubmitTicket(trackerId: $trackerId, input: $input) {\n\t\tid\n\t}\n}\n") + op.Var("trackerId", trackerId) + op.Var("input", input) + var respData struct { + SubmitTicket *Ticket + } + err = client.Execute(ctx, op, &respData) + return respData.SubmitTicket, err +} diff --git a/srht/todosrht/operations.graphql b/srht/todosrht/operations.graphql index 883276f0ec8d..2d31a42d0331 100644 --- a/srht/todosrht/operations.graphql +++ b/srht/todosrht/operations.graphql @@ -98,3 +98,9 @@ mutation updateTicketStatus( } } } + +mutation submitTicket($trackerId: Int!, $input: SubmitTicketInput!) { + submitTicket(trackerId: $trackerId, input: $input) { + id + } +} diff --git a/todo.go b/todo.go index 5860c3f739df..f4a060cf24b6 100644 --- a/todo.go +++ b/todo.go @@ -1,12 +1,14 @@ package main import ( + "bufio" "context" "fmt" "io" "log" "os" "strings" + "unicode" "git.sr.ht/~emersion/hut/srht/todosrht" "git.sr.ht/~emersion/hut/termfmt" @@ -110,6 +112,7 @@ func newTodoTicketCommand() *cobra.Command { cmd.AddCommand(newTodoTicketListCommand()) cmd.AddCommand(newTodoTicketCommentCommand()) cmd.AddCommand(newTodoTicketStatusCommand()) + cmd.AddCommand(newTodoTicketCreateCommand()) return cmd } @@ -296,6 +299,90 @@ func newTodoTicketStatusCommand() *cobra.Command { return cmd } +const todoTicketPrefill = ` +<!-- +Please enter the subject of the new ticket. The subject line can be +followed by a blank line and a Markdown description. An empty subject +aborts the ticket. +-->`
Should add an "above" here. My first intuition was to write below the comment (mostly because thats how my first prototype worked).
+ +func newTodoTicketCreateCommand() *cobra.Command { + var stdin bool + run := func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + name, owner, instance := getTrackerName(ctx, cmd) + c := createClientWithInstance("todo", cmd, instance) + trackerID := getTrackerID(c, ctx, name, owner) + + var input todosrht.SubmitTicketInput + if stdin { + br := bufio.NewReader(os.Stdin) + fmt.Printf("Subject: ") + + var err error + input.Subject, err = br.ReadString('\n') + if err != nil { + log.Fatalf("failed to read subject: %v", err) + } + input.Subject = strings.TrimSpace(input.Subject) + if input.Subject == "" { + fmt.Println("Aborting due to empty subject.") + os.Exit(1) + } + + fmt.Printf("Description %s:\n", termfmt.Dim.String("(Markdown supported)")) + bodyBytes, err := io.ReadAll(br) + if err != nil { + log.Fatalf("failed to read description: %v", err) + } + if body := strings.TrimSpace(string(bodyBytes)); body != "" { + input.Body = &body + } + } else { + text, err := getInputWithEditor("hut_ticket*.md", todoTicketPrefill) + if err != nil { + log.Fatalf("failed to read ticket subject and description: %v", err) + } + + // Drop our prefilled comment, but without stripping leading + // whitespace + text = strings.TrimRightFunc(text, unicode.IsSpace) + text = strings.TrimSuffix(text, todoTicketPrefill) + text = strings.TrimRightFunc(text, unicode.IsSpace)
What do you think about moving the comment drop to the getInputWithEditor function - maybe behind a bool parameter? I had something similar in mind for creating mailing lists and trackers so we probably want to re-use the functionality. Besides a sanity check could be nice. Test if the stripped string still contains the prefilled text and fail in this case (for example if a user wrongly writes the ticket below the text instead of above).
+ + parts := strings.SplitN(text, "\n", 2) + input.Subject = strings.TrimSpace(parts[0]) + if len(parts) > 1 { + body := strings.TrimSpace(parts[1]) + input.Body = &body + } + } + + if input.Subject == "" { + fmt.Println("Aborting due to empty subject.") + os.Exit(1) + } + + ticket, err := todosrht.SubmitTicket(c.Client, ctx, trackerID, input) + if err != nil { + log.Fatal(err) + } else if ticket == nil { + log.Fatal("failed to create ticket") + } + + fmt.Printf("Created new ticket %v\n", termfmt.DarkYellow.Sprintf("#%v", ticket.Id)) + } + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new ticket", + Args: cobra.ExactArgs(0), + Run: run, + } + cmd.Flags().BoolVar(&stdin, "stdin", false, "read ticket from stdin") + return cmd +} + func getTrackerID(c *Client, ctx context.Context, name, owner string) int32 { var ( tracker *todosrht.Tracker base-commit: f7519c5d49a8287a0e47f9459cb859304d1e8b0c -- 2.35.1