Skip to content

Commit 9b4f3b7

Browse files
author
Ismar Iljazovic
committed
feat: add worklog list, edit, and delete commands (ankitpokhrel#908)
Cherry-picked from upstream PR ankitpokhrel#908 by isaacpalomero. Adds full CRUD support for worklogs: - jira issue worklog list ISSUE-KEY - jira issue worklog edit ISSUE-KEY WORKLOG-ID TIME - jira issue worklog delete ISSUE-KEY WORKLOG-ID
1 parent 1fee7ed commit 9b4f3b7

File tree

10 files changed

+1212
-10
lines changed

10 files changed

+1212
-10
lines changed

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,19 +562,71 @@ EOF
562562
The `worklog` command provides a list of sub-commands to manage issue worklog (timelog).
563563

564564
##### Add
565-
The `add` command lets you add a worklog to an issue. The command supports markdown for worklog comments.
565+
The `add` command lets you add a worklog to an issue. The command supports markdown for worklog comments and returns the created worklog ID.
566566

567567
```sh
568568
# Add a worklog using an interactive prompt
569569
$ jira issue worklog add
570570

571571
# Pass required parameters and use --no-input to skip prompt
572572
$ jira issue worklog add ISSUE-1 "2d 3h 30m" --no-input
573+
✓ Worklog 10001 added to issue "ISSUE-1"
573574

574575
# You can add a comment using --comment flag when adding a worklog
575576
$ jira issue worklog add ISSUE-1 "10m" --comment "This is a comment" --no-input
576577
```
577578

579+
##### List
580+
The `list` command displays all worklogs for an issue. Supports both table and plain text output formats.
581+
582+
```sh
583+
# List worklogs in table format (default)
584+
$ jira issue worklog list ISSUE-1
585+
586+
# List worklogs with detailed information
587+
$ jira issue worklog list ISSUE-1 --plain
588+
589+
# Using the alias
590+
$ jira issue worklog ls ISSUE-1
591+
```
592+
593+
##### Edit
594+
The `edit` command allows you to update an existing worklog. You can modify the time spent, comment, and start date.
595+
596+
```sh
597+
# Edit a worklog interactively (select from list)
598+
$ jira issue worklog edit ISSUE-1
599+
600+
# Edit a specific worklog with new time
601+
$ jira issue worklog edit ISSUE-1 10001 "3h 30m" --no-input
602+
603+
# Edit worklog with new comment and start date
604+
$ jira issue worklog edit ISSUE-1 10001 "2h" \
605+
--comment "Updated work description" \
606+
--started "2024-11-05 09:30:00"
607+
608+
# Using the alias
609+
$ jira issue worklog update ISSUE-1 10001 "4h"
610+
```
611+
612+
##### Delete
613+
The `delete` command removes a worklog from an issue. By default, it asks for confirmation before deleting.
614+
615+
```sh
616+
# Delete a worklog interactively (select from list)
617+
$ jira issue worklog delete ISSUE-1
618+
619+
# Delete a specific worklog with confirmation
620+
$ jira issue worklog delete ISSUE-1 10001
621+
622+
# Delete without confirmation prompt (use with caution)
623+
$ jira issue worklog delete ISSUE-1 10001 --force
624+
625+
# Using the aliases
626+
$ jira issue worklog remove ISSUE-1 10001
627+
$ jira issue worklog rm ISSUE-1 10001 -f
628+
```
629+
578630
### Epic
579631
Epics are displayed in an explorer view by default. You can output the results in a table view using the `--table` flag.
580632
When viewing epic issues, you can use all filters available for the issue command.

internal/cmd/issue/worklog/add/add.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func add(cmd *cobra.Command, args []string) {
9898
}
9999
}
100100

101-
err := func() error {
101+
worklog, err := func() (*jira.Worklog, error) {
102102
s := cmdutil.Info("Adding a worklog")
103103
defer s.Stop()
104104

@@ -108,7 +108,7 @@ func add(cmd *cobra.Command, args []string) {
108108

109109
server := viper.GetString("server")
110110

111-
cmdutil.Success("Worklog added to issue %q", ac.params.issueKey)
111+
cmdutil.Success("Worklog %s added to issue %q", worklog.ID, ac.params.issueKey)
112112
fmt.Printf("%s\n", cmdutil.GenerateServerBrowseURL(server, ac.params.issueKey))
113113
}
114114

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Package delete provides the worklog delete command.
2+
package delete
3+
4+
import (
5+
"fmt"
6+
7+
"github.com/AlecAivazis/survey/v2"
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
11+
"github.com/ankitpokhrel/jira-cli/api"
12+
"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
13+
"github.com/ankitpokhrel/jira-cli/internal/query"
14+
"github.com/ankitpokhrel/jira-cli/pkg/jira"
15+
)
16+
17+
const (
18+
helpText = `Delete removes a worklog from an issue.`
19+
examples = `$ jira issue worklog delete
20+
21+
# Delete a specific worklog
22+
$ jira issue worklog delete ISSUE-1 10001
23+
24+
# Delete worklog without confirmation prompt
25+
$ jira issue worklog delete ISSUE-1 10001 --force
26+
27+
# Delete worklog interactively (select from list)
28+
$ jira issue worklog delete ISSUE-1`
29+
)
30+
31+
// NewCmdWorklogDelete is a worklog delete command.
32+
func NewCmdWorklogDelete() *cobra.Command {
33+
cmd := cobra.Command{
34+
Use: "delete ISSUE-KEY [WORKLOG-ID]",
35+
Short: "Delete a worklog from an issue",
36+
Long: helpText,
37+
Example: examples,
38+
Aliases: []string{"remove", "rm"},
39+
Annotations: map[string]string{
40+
"help:args": "ISSUE-KEY\tIssue key of the source issue, eg: ISSUE-1\n" +
41+
"WORKLOG-ID\tID of the worklog to delete (optional, will prompt to select if not provided)",
42+
},
43+
Run: deleteWorklog,
44+
}
45+
46+
cmd.Flags().SortFlags = false
47+
48+
cmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
49+
50+
return &cmd
51+
}
52+
53+
func deleteWorklog(cmd *cobra.Command, args []string) {
54+
params := parseArgsAndFlags(args, cmd.Flags())
55+
client := api.DefaultClient(params.debug)
56+
dc := deleteCmd{
57+
client: client,
58+
params: params,
59+
}
60+
61+
cmdutil.ExitIfError(dc.setIssueKey())
62+
cmdutil.ExitIfError(dc.setWorklogID())
63+
64+
if !params.force {
65+
var confirm bool
66+
prompt := &survey.Confirm{
67+
Message: fmt.Sprintf("Are you sure you want to delete worklog %s from issue %s?",
68+
dc.params.worklogID, dc.params.issueKey),
69+
Default: false,
70+
}
71+
if err := survey.AskOne(prompt, &confirm); err != nil {
72+
cmdutil.Failed("Confirmation failed: %s", err.Error())
73+
}
74+
if !confirm {
75+
cmdutil.Failed("Action cancelled")
76+
}
77+
}
78+
79+
err := func() error {
80+
s := cmdutil.Info("Deleting worklog")
81+
defer s.Stop()
82+
83+
return client.DeleteIssueWorklog(dc.params.issueKey, dc.params.worklogID)
84+
}()
85+
cmdutil.ExitIfError(err)
86+
87+
server := viper.GetString("server")
88+
89+
cmdutil.Success("Worklog deleted from issue %q", dc.params.issueKey)
90+
fmt.Printf("%s\n", cmdutil.GenerateServerBrowseURL(server, dc.params.issueKey))
91+
}
92+
93+
type deleteParams struct {
94+
issueKey string
95+
worklogID string
96+
force bool
97+
debug bool
98+
}
99+
100+
func parseArgsAndFlags(args []string, flags query.FlagParser) *deleteParams {
101+
var issueKey, worklogID string
102+
103+
const (
104+
argIndexIssueKey = 0
105+
argIndexWorklogID = 1
106+
)
107+
108+
nargs := len(args)
109+
if nargs > argIndexIssueKey {
110+
issueKey = cmdutil.GetJiraIssueKey(viper.GetString("project.key"), args[argIndexIssueKey])
111+
}
112+
if nargs > argIndexWorklogID {
113+
worklogID = args[argIndexWorklogID]
114+
}
115+
116+
debug, err := flags.GetBool("debug")
117+
cmdutil.ExitIfError(err)
118+
119+
force, err := flags.GetBool("force")
120+
cmdutil.ExitIfError(err)
121+
122+
return &deleteParams{
123+
issueKey: issueKey,
124+
worklogID: worklogID,
125+
force: force,
126+
debug: debug,
127+
}
128+
}
129+
130+
type deleteCmd struct {
131+
client *jira.Client
132+
params *deleteParams
133+
}
134+
135+
func (dc *deleteCmd) setIssueKey() error {
136+
if dc.params.issueKey != "" {
137+
return nil
138+
}
139+
140+
var ans string
141+
142+
qs := &survey.Question{
143+
Name: "issueKey",
144+
Prompt: &survey.Input{Message: "Issue key"},
145+
Validate: survey.Required,
146+
}
147+
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
148+
return err
149+
}
150+
dc.params.issueKey = cmdutil.GetJiraIssueKey(viper.GetString("project.key"), ans)
151+
152+
return nil
153+
}
154+
155+
func (dc *deleteCmd) setWorklogID() error {
156+
if dc.params.worklogID != "" {
157+
return nil
158+
}
159+
160+
// Fetch worklogs for the issue
161+
worklogs, err := dc.client.GetIssueWorklogs(dc.params.issueKey)
162+
if err != nil {
163+
return err
164+
}
165+
166+
if worklogs.Total == 0 {
167+
return fmt.Errorf("no worklogs found for issue %s", dc.params.issueKey)
168+
}
169+
170+
// Create options for selection
171+
options := make([]string, len(worklogs.Worklogs))
172+
for i, wl := range worklogs.Worklogs {
173+
options[i] = fmt.Sprintf("%s - %s by %s (%s)", wl.ID, wl.TimeSpent, wl.Author.Name, wl.Started)
174+
}
175+
176+
var selected string
177+
prompt := &survey.Select{
178+
Message: "Select worklog to delete:",
179+
Options: options,
180+
}
181+
if err := survey.AskOne(prompt, &selected); err != nil {
182+
return err
183+
}
184+
185+
// Extract worklog ID from selection (format: "ID - ...")
186+
var id string
187+
_, _ = fmt.Sscanf(selected, "%s -", &id)
188+
dc.params.worklogID = id
189+
190+
return nil
191+
}

0 commit comments

Comments
 (0)