Files
kandev/apps/backend/internal/orchestrator/source_jira.go
T

132 lines
4.1 KiB
Go

package orchestrator
import (
"context"
"errors"
"fmt"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/jira"
)
// JiraWatcherSource adapts the Jira integration onto the WatcherSource
// pipeline. Symmetric with LinearWatcherSource; the only differences are
// the metadata keys and the interpolation function.
type JiraWatcherSource struct {
service JiraService
logger *logger.Logger
}
// NewJiraWatcherSource constructs a source bound to the orchestrator's
// Jira service handle.
func NewJiraWatcherSource(svc JiraService, log *logger.Logger) *JiraWatcherSource {
return &JiraWatcherSource{service: svc, logger: log}
}
func (s *JiraWatcherSource) Name() string { return "jira" }
func (s *JiraWatcherSource) Reserve(ctx context.Context, evt any) (bool, error) {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil || e.Issue == nil {
return false, errors.New("jira source: event payload missing or wrong type")
}
if s.service == nil {
return true, nil
}
return s.service.ReserveIssueWatchTask(ctx, e.IssueWatchID, e.Issue.Key, e.Issue.URL)
}
func (s *JiraWatcherSource) Release(ctx context.Context, evt any) {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil || e.Issue == nil || s.service == nil {
return
}
if err := s.service.ReleaseIssueWatchTask(ctx, e.IssueWatchID, e.Issue.Key); err != nil && s.logger != nil {
s.logger.Warn("jira source: release failed",
zap.String("issue_key", e.Issue.Key), zap.Error(err))
}
}
func (s *JiraWatcherSource) BuildTaskRequest(evt any) (*IssueTaskRequest, error) {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil || e.Issue == nil {
return nil, errors.New("jira source: event payload missing or wrong type")
}
return &IssueTaskRequest{
WorkspaceID: e.WorkspaceID,
WorkflowID: e.WorkflowID,
WorkflowStepID: e.WorkflowStepID,
Title: fmt.Sprintf("[%s] %s", e.Issue.Key, e.Issue.Summary),
Description: interpolateJiraPrompt(e.Prompt, e.Issue),
// Preserve today's literal metadata keys (do NOT normalise to
// models.MetaKey* in this refactor — separate cleanup).
Metadata: map[string]interface{}{
"jira_issue_watch_id": e.IssueWatchID,
"jira_issue_key": e.Issue.Key,
"jira_issue_url": e.Issue.URL,
"jira_status": e.Issue.StatusName,
"jira_assignee": e.Issue.AssigneeName,
"agent_profile_id": e.AgentProfileID,
"executor_profile_id": e.ExecutorProfileID,
},
}, nil
}
func (s *JiraWatcherSource) AttachTaskID(ctx context.Context, evt any, taskID string) error {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil || e.Issue == nil || s.service == nil {
return nil
}
return s.service.AssignIssueWatchTaskID(ctx, e.IssueWatchID, e.Issue.Key, taskID)
}
func (s *JiraWatcherSource) WatchID(evt any) string {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil {
return ""
}
return e.IssueWatchID
}
func (s *JiraWatcherSource) MaxInflightTasks(evt any) *int {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil {
return nil
}
return e.MaxInflightTasks
}
func (s *JiraWatcherSource) AutoStartParams(evt any) AutoStartParams {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil {
return AutoStartParams{}
}
return AutoStartParams{
AgentProfileID: e.AgentProfileID,
ExecutorProfileID: e.ExecutorProfileID,
WorkflowStepID: e.WorkflowStepID,
}
}
// AgentProfileID returns the watcher's bound profile id, or "" when the
// event payload is malformed. Pre-flight uses "" as the skip-check signal.
func (s *JiraWatcherSource) AgentProfileID(evt any) string {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil {
return ""
}
return e.AgentProfileID
}
// SelfHeal disables the jira_issue_watches row that produced this event.
// Symmetric with LinearWatcherSource.SelfHeal; nil-safe.
func (s *JiraWatcherSource) SelfHeal(ctx context.Context, evt any, cause string) error {
e, ok := evt.(*jira.NewJiraIssueEvent)
if !ok || e == nil || s.service == nil {
return nil
}
return s.service.DisableIssueWatchWithError(ctx, e.IssueWatchID, cause)
}