diff --git a/cmd/pm/tasks/backlogRefinement.go b/cmd/pm/tasks/backlogRefinement.go index be536b5..f1ed46b 100644 --- a/cmd/pm/tasks/backlogRefinement.go +++ b/cmd/pm/tasks/backlogRefinement.go @@ -2,6 +2,7 @@ package tasks import ( "context" + "strings" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/utils/check" @@ -12,12 +13,12 @@ const backlogRefinementDescription = `You are tasked with backlog refinement. The product backlog has become cluttered with old and unclear issues. You need to groom the backlog: -1. Review all issues in the backlog -2. Identify stale or obsolete issues (older items that are no longer relevant) -3. Update issue descriptions for clarity where needed -4. Close issues that are duplicates or no longer applicable -5. Reprioritize issues based on current business value -6. Ensure remaining issues are well-defined and actionable +1. Go to the backlog. +2. Find two issues that got the same name or describe the same problem. +3. Open one of these issues. +4. Select "Close issue" +5. Choose "Duplicate issue" as closing reason. +6. Save/close issue. Focus on making the backlog a reliable source of upcoming work.` @@ -47,60 +48,65 @@ func (t *BacklogRefinementTask) Questions(interfaceType InterfaceType) Questions return BaseQuestions(interfaceType).With( huh.NewGroup( huh.NewSelect[int](). - Title("How many issues did you close or update during refinement?"). + Key("how-many-duplicate-issues"). + Title("How many duplicate issues did you close during refinement?"). Options( - huh.NewOption("1-2", 1), - huh.NewOption("3-4", 2), - huh.NewOption("5+", 3), + huh.NewOption("1", 1), + huh.NewOption("2", 2), + huh.NewOption("3+", 3), ), ), ) } +func (t *BacklogRefinementTask) QuestionnaireKeys(_ InterfaceType) []string { + return []string{"task_completed", "task_difficulty", "how-many-duplicate-issues"} +} + func (t *BacklogRefinementTask) Setup(ctx context.Context) error { if err := ClearIssues(t.app); err != nil { return err } refinementIssues := []*models.Issue{ - NewIssueBuilder(). - WithTitle("Old feature request: Fax integration"). - WithDescription("Allow sending reports via fax. DEPRECATED - nobody uses fax anymore"). - WithPriority(3). - WithStatus(models.StatusOpen). - WithIssueType(models.TypeTask). - Build(), NewIssueBuilder(). WithTitle("User profile page"). - WithDescription("Create page for users to view profile. DUPLICATE of user-management epic"). + WithDescription("Create page for users to view and edit their profile"). WithPriority(2). WithStatus(models.StatusOpen). WithIssueType(models.TypeTask). Build(), NewIssueBuilder(). - WithTitle("Mobile app redesign"). - WithDescription("Redesign mobile interface with modern UI patterns. Still relevant, needs clarity"). + WithTitle("User profile page"). + WithDescription("Allow users to view their profile information"). + WithPriority(2). + WithStatus(models.StatusOpen). + WithIssueType(models.TypeTask). + Build(), + NewIssueBuilder(). + WithTitle("Fix login timeout"). + WithDescription("Login sometimes times out after 30 seconds"). WithPriority(1). WithStatus(models.StatusOpen). - WithIssueType(models.TypeTask). + WithIssueType(models.TypeBug). Build(), NewIssueBuilder(). - WithTitle("Legacy data export tool"). - WithDescription("Tool for exporting data in old format. OBSOLETE - format no longer supported"). - WithPriority(3). + WithTitle("Fix login timeout"). + WithDescription("Users report login requests timing out"). + WithPriority(1). WithStatus(models.StatusOpen). - WithIssueType(models.TypeTask). + WithIssueType(models.TypeBug). Build(), NewIssueBuilder(). - WithTitle("API v1 documentation"). - WithDescription("Document old API version. DEPRECATED - migrating to v2"). - WithPriority(3). + WithTitle("Mobile app redesign"). + WithDescription("Redesign mobile interface with modern UI patterns"). + WithPriority(2). WithStatus(models.StatusOpen). WithIssueType(models.TypeTask). Build(), NewIssueBuilder(). WithTitle("Customer feedback system"). - WithDescription("Build system for collecting user feedback. HIGH VALUE - prioritize"). + WithDescription("Build system for collecting user feedback"). WithPriority(2). WithStatus(models.StatusOpen). WithIssueType(models.TypeTask). @@ -122,5 +128,22 @@ func (t *BacklogRefinementTask) Setup(ctx context.Context) error { func (t *BacklogRefinementTask) Validate(ctx context.Context) ValidationFeedback { expect := check.NewExpector() + issues, err := FetchIssues(ctx, t.app, t.setupIssue) + if err != nil { + return expect.ValidationFeedback + } + + var closedDuplicate *models.Issue + for _, issue := range issues { + if issue.Status == models.StatusClosed && + strings.Contains(strings.ToLower(issue.CloseReason), "duplicate") { + closedDuplicate = issue + break + } + } + + expect.Assert(closedDuplicate != nil, + "Expected one duplicate issue to be closed with 'Duplicate issue' as closing reason") + return expect.Complete() } diff --git a/go.mod b/go.mod index ff1e7b2..a9ecc2f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( // Web dependencies require ( github.com/NYTimes/gziphandler v1.1.1 - github.com/a-h/templ v0.3.977 + github.com/a-h/templ v0.3.1001 github.com/donseba/go-htmx v1.12.1 github.com/go-chi/chi/v5 v5.2.5 github.com/go-playground/form/v4 v4.3.0 @@ -69,7 +69,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 // indirect + github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kevinburke/ssh_config v1.5.0 // indirect github.com/klauspost/compress v1.18.0 // indirect diff --git a/go.sum b/go.sum index 9e37a12..d159960 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= -github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= -github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= +github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -128,8 +128,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 h1:sb7SfxZfN+U9OHC61tcS98Ge0zY9uEkW5CP6KB4YVHg= -github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320/go.mod h1:LFP8N8y5ORkifb+LZuOVNZYlJuV3WqdXCjxX5pGUaNI= +github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6 h1:Yakj3J1ZxYCpFtBHUVkSBNA80LK/8bNrl/LA8fSvvFw= +github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6/go.mod h1:2A31H3xgQHTgNp8OlX7aliXaY3QFwtI7XHuaP8G1ZSw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+PRLSPpA= diff --git a/internal/commands/issues/close.go b/internal/commands/issues/close.go index 3c89b10..ce4bfcb 100644 --- a/internal/commands/issues/close.go +++ b/internal/commands/issues/close.go @@ -7,8 +7,6 @@ import ( "github.com/spf13/cobra" ) -// CloseCmd represents the close command, -// which allows users to close an existing issue by its ID. var CloseCmd = &cobra.Command{ Use: "close [id]", Short: "Close an existing issue", @@ -21,8 +19,6 @@ var CloseCmd = &cobra.Command{ ValidArgsFunction: completeIssues, } -// runCloseCmd executes the close command logic, -// which closes an issue by its ID after confirming with the user. func runCloseCmd(cmd *cobra.Command, args []string) error { closeID := args[0] @@ -42,14 +38,30 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("issue with ID %s not found", closeID) } - // Ask for closing reason - if err = huh.NewInput().Value(&issue.CloseReason). - Title("Reason for closing the issue?").WithTheme(huh.ThemeBase()).Run(); err != nil { + closeReason := "" + if err = huh.NewSelect[string]().Value(&closeReason). + Title("Reason for closing the issue?"). + Options( + huh.NewOption("Done", "Done"), + huh.NewOption("Duplicate issue", "Duplicate issue"), + huh.NewOption("Won't fix", "Won't fix"), + huh.NewOption("Obsolete", "Obsolete"), + huh.NewOption("Other", "Other"), + ).WithTheme(huh.ThemeBase()).Run(); err != nil { return fmt.Errorf("error getting close reason: %w", err) } - // Close the issue. - err = app.Issues.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "") + if closeReason == "Other" { + if err = huh.NewInput().Value(&closeReason). + Title("Enter closing reason:").WithTheme(huh.ThemeBase()).Run(); err != nil { + return fmt.Errorf("error getting close reason: %w", err) + } + if closeReason == "" { + return fmt.Errorf("closing reason cannot be empty when selecting 'Other'") + } + } + + err = app.Issues.CloseIssue(cmd.Context(), closeID, closeReason, "", "") if err != nil { return fmt.Errorf("error closing issue: %w", err) } diff --git a/pkg/web/components/assignee_form_templ.go b/pkg/web/components/assignee_form_templ.go index 06f3ea1..ae68a4f 100644 --- a/pkg/web/components/assignee_form_templ.go +++ b/pkg/web/components/assignee_form_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/input_templ.go b/pkg/web/components/base/input_templ.go index 9d787ce..1b68a0c 100644 --- a/pkg/web/components/base/input_templ.go +++ b/pkg/web/components/base/input_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/range_templ.go b/pkg/web/components/base/range_templ.go index 4ae6395..1a57ebe 100644 --- a/pkg/web/components/base/range_templ.go +++ b/pkg/web/components/base/range_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/select_templ.go b/pkg/web/components/base/select_templ.go index aab85ee..a7b5040 100644 --- a/pkg/web/components/base/select_templ.go +++ b/pkg/web/components/base/select_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/table_templ.go b/pkg/web/components/base/table_templ.go index b7544a0..7259528 100644 --- a/pkg/web/components/base/table_templ.go +++ b/pkg/web/components/base/table_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/textarea_templ.go b/pkg/web/components/base/textarea_templ.go index 32f9357..0d1bf07 100644 --- a/pkg/web/components/base/textarea_templ.go +++ b/pkg/web/components/base/textarea_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/close_issue_form.templ b/pkg/web/components/close_issue_form.templ new file mode 100644 index 0000000..7c6a1c2 --- /dev/null +++ b/pkg/web/components/close_issue_form.templ @@ -0,0 +1,34 @@ +package components + +import "github.com/LazyBachelor/LazyPM/pkg/web/components/base" + +type CloseIssueFormProps struct { + PostAction string +} + +templ CloseIssueForm(props CloseIssueFormProps) { +
+ +} diff --git a/pkg/web/components/close_issue_form_templ.go b/pkg/web/components/close_issue_form_templ.go new file mode 100644 index 0000000..1e2b421 --- /dev/null +++ b/pkg/web/components/close_issue_form_templ.go @@ -0,0 +1,79 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/LazyBachelor/LazyPM/pkg/web/components/base" + +type CloseIssueFormProps struct { + PostAction string +} + +func CloseIssueForm(props CloseIssueFormProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/components/comment_templ.go b/pkg/web/components/comment_templ.go index e97390e..2542d51 100644 --- a/pkg/web/components/comment_templ.go +++ b/pkg/web/components/comment_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/header_templ.go b/pkg/web/components/header_templ.go index 4a6a7fa..a4c3217 100644 --- a/pkg/web/components/header_templ.go +++ b/pkg/web/components/header_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/icons_templ.go b/pkg/web/components/icons_templ.go index c8c421c..0df923b 100644 --- a/pkg/web/components/icons_templ.go +++ b/pkg/web/components/icons_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/issue_detail_templ.go b/pkg/web/components/issue_detail_templ.go index b9443b2..c3d4351 100644 --- a/pkg/web/components/issue_detail_templ.go +++ b/pkg/web/components/issue_detail_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/issue_templ.go b/pkg/web/components/issue_templ.go index 922d698..d055a8e 100644 --- a/pkg/web/components/issue_templ.go +++ b/pkg/web/components/issue_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -21,7 +21,7 @@ type IssueFormProps struct { Title string Description string Status string - CloseReason string // Shown when status is "closed" + CloseReason string // Set when status is "closed" Priority int IssueType string Class string @@ -68,14 +68,14 @@ func IssueForm(props IssueFormProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" x-data=\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" x-data=\"{ status: $el.dataset.status }\" data-status=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(`{ status: '` + props.Status + `' }`) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Status) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 25, Col: 101} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 25, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -248,7 +248,7 @@ func IssueForm(props IssueFormProps) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.DeleteAction) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 106, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 107, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -348,7 +348,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 146, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 147, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -361,7 +361,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 150, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 151, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -374,7 +374,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 155, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 156, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -387,7 +387,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 159, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 160, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -400,7 +400,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.Status)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 161, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 162, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -413,7 +413,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.IssueType)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 162, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 163, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -426,7 +426,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", issue.Priority)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 163, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 164, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -439,7 +439,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 168, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 169, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { diff --git a/pkg/web/components/layout_templ.go b/pkg/web/components/layout_templ.go index 28d4c95..a426fce 100644 --- a/pkg/web/components/layout_templ.go +++ b/pkg/web/components/layout_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/modal_templ.go b/pkg/web/components/modal_templ.go index f67c331..b6fbc34 100644 --- a/pkg/web/components/modal_templ.go +++ b/pkg/web/components/modal_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/search_templ.go b/pkg/web/components/search_templ.go index f27707f..6fc7f33 100644 --- a/pkg/web/components/search_templ.go +++ b/pkg/web/components/search_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/sidebar_templ.go b/pkg/web/components/sidebar_templ.go index a66fd90..f26a365 100644 --- a/pkg/web/components/sidebar_templ.go +++ b/pkg/web/components/sidebar_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/status_templ.go b/pkg/web/components/status_templ.go index 451a387..41694c7 100644 --- a/pkg/web/components/status_templ.go +++ b/pkg/web/components/status_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go index 95c91f1..3306c7a 100644 --- a/pkg/web/handler/issues.go +++ b/pkg/web/handler/issues.go @@ -2,6 +2,7 @@ package handler import ( "context" + "html" "net/http" "strings" @@ -250,6 +251,49 @@ func DeleteIssue(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } +func CloseIssue(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + issueVal := r.Context().Value(issueKey) + issue, ok := issueVal.(*models.Issue) + if !ok || issue == nil { + http.Error(w, "Issue not found in context", http.StatusInternalServerError) + return + } + + closeReason := r.FormValue("close_reason") + + if closeReason == "" { + if HTMX(r).IsHxRequest() { + w.WriteHeader(http.StatusBadRequest) + HTMX(r).WriteString("