mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 05:49:12 +02:00
* fix: prevent duplicate active issue creation * fix(issues): address duplicate guard review * fix(autopilot): skip duplicate issue admissions * fix(issueguard): tighten duplicate lookup edge cases * test(issues): cover duplicate guard autopilot skips * feat(autopilots): group skipped runs in history
87 lines
2.3 KiB
Go
87 lines
2.3 KiB
Go
package issueguard
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/multica-ai/multica/server/internal/util"
|
|
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
|
)
|
|
|
|
func NormalizeTitle(title string) string {
|
|
return strings.ToLower(strings.Join(strings.Fields(title), " "))
|
|
}
|
|
|
|
func DuplicateMessage(identifier, title, status string) string {
|
|
return "Active duplicate issue exists: " + identifier + " " + title + " (status: " + status + "). Set allow_duplicate=true or use --allow-duplicate to create another."
|
|
}
|
|
|
|
type ActiveDuplicateError struct {
|
|
ID string
|
|
Identifier string
|
|
Title string
|
|
Status string
|
|
}
|
|
|
|
func (e *ActiveDuplicateError) Error() string {
|
|
return DuplicateMessage(e.Identifier, e.Title, e.Status)
|
|
}
|
|
|
|
func NewActiveDuplicateError(issue db.Issue, issuePrefix string) *ActiveDuplicateError {
|
|
return &ActiveDuplicateError{
|
|
ID: util.UUIDToString(issue.ID),
|
|
Identifier: fmt.Sprintf("%s-%d", issuePrefix, issue.Number),
|
|
Title: issue.Title,
|
|
Status: issue.Status,
|
|
}
|
|
}
|
|
|
|
func LockAndFindActiveDuplicate(
|
|
ctx context.Context,
|
|
q *db.Queries,
|
|
workspaceID pgtype.UUID,
|
|
projectID pgtype.UUID,
|
|
parentIssueID pgtype.UUID,
|
|
title string,
|
|
allowDuplicate bool,
|
|
) (db.Issue, bool, error) {
|
|
normalizedTitle := NormalizeTitle(title)
|
|
if normalizedTitle == "" {
|
|
return db.Issue{}, false, nil
|
|
}
|
|
if err := q.LockIssueDuplicateKey(ctx, lockKey(workspaceID, projectID, parentIssueID, normalizedTitle)); err != nil {
|
|
return db.Issue{}, false, err
|
|
}
|
|
if allowDuplicate {
|
|
return db.Issue{}, false, nil
|
|
}
|
|
|
|
duplicate, err := q.FindActiveDuplicateIssue(ctx, db.FindActiveDuplicateIssueParams{
|
|
WorkspaceID: workspaceID,
|
|
ProjectID: projectID,
|
|
ParentIssueID: parentIssueID,
|
|
NormalizedTitle: normalizedTitle,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return db.Issue{}, false, nil
|
|
}
|
|
return db.Issue{}, false, err
|
|
}
|
|
return duplicate, true, nil
|
|
}
|
|
|
|
func lockKey(workspaceID, projectID, parentIssueID pgtype.UUID, normalizedTitle string) string {
|
|
return strings.Join([]string{
|
|
"issue-active-duplicate",
|
|
util.UUIDToString(workspaceID),
|
|
util.UUIDToString(projectID),
|
|
util.UUIDToString(parentIssueID),
|
|
normalizedTitle,
|
|
}, "|")
|
|
}
|