661 lines
24 KiB
Go
661 lines
24 KiB
Go
package backendapp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"github.com/kandev/kandev/internal/events"
|
|
"github.com/kandev/kandev/internal/events/bus"
|
|
"github.com/kandev/kandev/internal/github"
|
|
"github.com/kandev/kandev/internal/gitlab"
|
|
mcphandlers "github.com/kandev/kandev/internal/mcp/handlers"
|
|
"github.com/kandev/kandev/internal/task/models"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type taskChangeRequestGitHubService interface {
|
|
githubTaskChangeRequestService
|
|
UpdateTaskCIOptions(context.Context, string, github.TaskCIOptionsPatch) (*github.TaskCIOptionsResponse, error)
|
|
}
|
|
|
|
type taskChangeRequestGitLabService interface {
|
|
gitlabTaskChangeRequestService
|
|
UpdateTaskMRAutomationOptions(context.Context, string, gitlab.TaskMRAutomationPatch) (*gitlab.TaskMRAutomationResponse, error)
|
|
}
|
|
|
|
type taskChangeRequestAutomationCoordinator struct {
|
|
tasks taskChangeRequestTaskLookup
|
|
github taskChangeRequestGitHubService
|
|
gitlab taskChangeRequestGitLabService
|
|
eventBus mcphandlers.EventBus
|
|
logger *logger.Logger
|
|
}
|
|
|
|
func newTaskChangeRequestAutomationCoordinator(
|
|
tasks taskChangeRequestTaskLookup,
|
|
githubProvider taskChangeRequestGitHubService,
|
|
gitlabProvider taskChangeRequestGitLabService,
|
|
eventBus mcphandlers.EventBus,
|
|
log *logger.Logger,
|
|
) *taskChangeRequestAutomationCoordinator {
|
|
if log == nil {
|
|
log = logger.Default()
|
|
}
|
|
return &taskChangeRequestAutomationCoordinator{
|
|
tasks: tasks, github: githubProvider, gitlab: gitlabProvider, eventBus: eventBus, logger: log,
|
|
}
|
|
}
|
|
|
|
type taskChangeRequestAutomationPreflight struct {
|
|
provider string
|
|
changes []mcphandlers.TaskChangeRequest
|
|
settings *mcphandlers.TaskChangeRequestProviderSettings
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) UpdateTaskChangeRequestAutomation(
|
|
ctx context.Context,
|
|
taskID string,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
) (mcphandlers.TaskChangeRequestAutomationResult, error) {
|
|
result := mcphandlers.TaskChangeRequestAutomationResult{
|
|
TaskID: strings.TrimSpace(taskID),
|
|
Status: mcphandlers.TaskChangeRequestAutomationStatusFailed,
|
|
Providers: []mcphandlers.TaskChangeRequestAutomationProviderResult{},
|
|
StateKnown: false,
|
|
}
|
|
if c == nil || c.tasks == nil {
|
|
return result, errors.New("task change request automation is unavailable")
|
|
}
|
|
if result.TaskID == "" {
|
|
return result, errors.New("task_id is required")
|
|
}
|
|
task, err := c.tasks.GetTask(ctx, result.TaskID)
|
|
if err != nil || task == nil {
|
|
return result, errors.New("task not found")
|
|
}
|
|
|
|
providers := automationTargetProviders(request.Target)
|
|
for _, provider := range providers {
|
|
result.Providers = append(result.Providers, mcphandlers.TaskChangeRequestAutomationProviderResult{
|
|
Provider: provider,
|
|
Status: mcphandlers.TaskChangeRequestAutomationProviderSkipped,
|
|
Affected: []mcphandlers.TaskChangeRequestAutomationIdentity{},
|
|
})
|
|
}
|
|
|
|
snapshot, err := c.readTaskChangeRequestSnapshot(ctx, result.TaskID)
|
|
if err != nil {
|
|
return result, errors.New("failed to read task change request state")
|
|
}
|
|
|
|
preflights, firstPreflightErr := c.preflightAutomationProviders(ctx, request, providers, snapshot, task, &result)
|
|
if firstPreflightErr != nil {
|
|
return result, firstPreflightErr
|
|
}
|
|
|
|
for applied, provider := range providers {
|
|
providerResult := automationProviderResult(&result, provider)
|
|
preflight := preflights[provider]
|
|
if updateErr := c.applyAutomationProvider(ctx, result.TaskID, request, provider, providerResult, preflight); updateErr != nil {
|
|
result.Status = automationStatusAfterFailure(applied)
|
|
result.StateKnown = false
|
|
return result, updateErr
|
|
}
|
|
}
|
|
|
|
result.Status = mcphandlers.TaskChangeRequestAutomationStatusApplied
|
|
result.StateKnown = automationProviderStatesKnown(result.Providers)
|
|
return result, nil
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) readTaskChangeRequestSnapshot(
|
|
ctx context.Context, taskID string,
|
|
) (mcphandlers.TaskChangeRequestReadResponse, error) {
|
|
reader := taskChangeRequestReader{
|
|
tasks: c.tasks,
|
|
providers: []taskChangeRequestProviderAdapter{
|
|
newGitHubTaskChangeRequestAdapter(c.github),
|
|
newGitLabTaskChangeRequestAdapter(c.tasks, c.gitlab),
|
|
},
|
|
}
|
|
return reader.GetTaskChangeRequests(ctx, taskID)
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) preflightAutomationProviders(
|
|
ctx context.Context,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
providers []string,
|
|
snapshot mcphandlers.TaskChangeRequestReadResponse,
|
|
task *models.Task,
|
|
result *mcphandlers.TaskChangeRequestAutomationResult,
|
|
) (map[string]taskChangeRequestAutomationPreflight, error) {
|
|
preflights := make(map[string]taskChangeRequestAutomationPreflight, len(providers))
|
|
var firstErr error
|
|
for _, provider := range providers {
|
|
preflight, err := c.preflightProvider(ctx, request, provider, snapshot, task)
|
|
if err == nil {
|
|
preflights[provider] = preflight
|
|
continue
|
|
}
|
|
c.recordAutomationPreflightFailure(result, provider, err)
|
|
if firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
return preflights, firstErr
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) recordAutomationPreflightFailure(
|
|
result *mcphandlers.TaskChangeRequestAutomationResult, provider string, err error,
|
|
) {
|
|
providerResult := automationProviderResult(result, provider)
|
|
providerResult.Status = mcphandlers.TaskChangeRequestAutomationProviderFailed
|
|
providerResult.ErrorCode = "preflight_failed"
|
|
providerResult.ErrorMessage = err.Error()
|
|
c.logger.Warn("task change request automation preflight failed",
|
|
zap.String("task_id", result.TaskID), zap.String("provider", provider), zap.Error(err))
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) applyAutomationProvider(
|
|
ctx context.Context,
|
|
taskID string,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
provider string,
|
|
providerResult *mcphandlers.TaskChangeRequestAutomationProviderResult,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
) error {
|
|
switch provider {
|
|
case mcphandlers.TaskChangeRequestProviderGitHub:
|
|
response, err := c.updateGitHub(ctx, taskID, request)
|
|
if err != nil {
|
|
c.markAutomationProviderFailure(taskID, providerResult, err)
|
|
return err
|
|
}
|
|
*providerResult = c.resultFromGitHubValue(request, preflight, response)
|
|
c.publishGitHubOptionsUpdated(ctx, response)
|
|
return nil
|
|
case mcphandlers.TaskChangeRequestProviderGitLab:
|
|
response, err := c.updateGitLab(ctx, taskID, request, preflight)
|
|
if err != nil {
|
|
c.markAutomationProviderFailure(taskID, providerResult, err)
|
|
return err
|
|
}
|
|
*providerResult = c.resultFromGitLabValue(request, preflight, response)
|
|
c.publishGitLabOptionsUpdated(ctx, response)
|
|
return nil
|
|
default:
|
|
providerResult.Status = mcphandlers.TaskChangeRequestAutomationProviderFailed
|
|
providerResult.ErrorCode = "unsupported_provider"
|
|
providerResult.ErrorMessage = "provider is not supported"
|
|
return errors.New("provider is not supported")
|
|
}
|
|
}
|
|
|
|
func automationProviderStatesKnown(
|
|
providers []mcphandlers.TaskChangeRequestAutomationProviderResult,
|
|
) bool {
|
|
for _, provider := range providers {
|
|
if !provider.StateKnown {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func automationTargetProviders(target mcphandlers.TaskChangeRequestAutomationTarget) []string {
|
|
if target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
return []string{target.Provider}
|
|
}
|
|
providers := append([]string(nil), target.Providers...)
|
|
sort.Strings(providers)
|
|
return providers
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) preflightProvider(
|
|
ctx context.Context,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
provider string,
|
|
snapshot mcphandlers.TaskChangeRequestReadResponse,
|
|
task *models.Task,
|
|
) (taskChangeRequestAutomationPreflight, error) {
|
|
attached, err := c.taskHasProvider(ctx, task, provider)
|
|
if err != nil {
|
|
return taskChangeRequestAutomationPreflight{}, err
|
|
}
|
|
if !attached {
|
|
return taskChangeRequestAutomationPreflight{}, fmt.Errorf("%s provider is not attached to task", provider)
|
|
}
|
|
settings := taskChangeRequestProviderSettings(snapshot, provider)
|
|
if settings == nil || settings.Status != mcphandlers.TaskChangeRequestProviderStatusAvailable || !settings.Available {
|
|
return taskChangeRequestAutomationPreflight{}, fmt.Errorf("%s provider is unavailable", provider)
|
|
}
|
|
preflight := taskChangeRequestAutomationPreflight{provider: provider, settings: cloneTaskChangeRequestProviderSettings(settings)}
|
|
for _, change := range snapshot.ChangeRequests {
|
|
if change.Provider == provider {
|
|
preflight.changes = append(preflight.changes, change)
|
|
}
|
|
}
|
|
if request.Target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
return preflightAssociationAutomation(request, provider, preflight)
|
|
}
|
|
return preflightTaskAutomation(request, provider, preflight)
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) taskHasProvider(
|
|
ctx context.Context, task *models.Task, provider string,
|
|
) (bool, error) {
|
|
if task == nil || c.tasks == nil {
|
|
return false, errors.New("task provider membership could not be verified")
|
|
}
|
|
for _, taskRepository := range task.Repositories {
|
|
if taskRepository == nil || strings.TrimSpace(taskRepository.RepositoryID) == "" {
|
|
continue
|
|
}
|
|
repository, err := c.tasks.GetRepository(ctx, taskRepository.RepositoryID)
|
|
if err != nil {
|
|
c.logger.Warn("task change request automation repository lookup failed",
|
|
zap.String("task_id", task.ID),
|
|
zap.String("repository_id", taskRepository.RepositoryID),
|
|
zap.Error(err))
|
|
return false, errors.New("task provider membership could not be verified")
|
|
}
|
|
if repository == nil || repository.WorkspaceID != task.WorkspaceID {
|
|
continue
|
|
}
|
|
if strings.EqualFold(strings.TrimSpace(repository.Provider), strings.TrimSpace(provider)) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func preflightAssociationAutomation(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
provider string,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
) (taskChangeRequestAutomationPreflight, error) {
|
|
if request.Target.Provider != provider {
|
|
return taskChangeRequestAutomationPreflight{}, errors.New("association provider does not match target provider")
|
|
}
|
|
matching := make([]mcphandlers.TaskChangeRequest, 0, 1)
|
|
for _, change := range preflight.changes {
|
|
if pointerString(change.RepositoryID) == request.Target.RepositoryID && change.Number == request.Target.Number {
|
|
matching = append(matching, change)
|
|
}
|
|
}
|
|
if len(matching) == 0 {
|
|
return taskChangeRequestAutomationPreflight{}, errors.New("change request is not linked to this task")
|
|
}
|
|
if len(matching) != 1 || matching[0].IdentityStatus != mcphandlers.TaskChangeRequestIdentityResolved {
|
|
return taskChangeRequestAutomationPreflight{}, errors.New("change request identity is unresolved or ambiguous")
|
|
}
|
|
if err := validateAutomationCapabilities(matching[0], request.Patch); err != nil {
|
|
return taskChangeRequestAutomationPreflight{}, err
|
|
}
|
|
preflight.changes = matching
|
|
return preflight, nil
|
|
}
|
|
|
|
func preflightTaskAutomation(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
provider string,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
) (taskChangeRequestAutomationPreflight, error) {
|
|
if hasAutomationSwitchPatch(request.Patch) {
|
|
if len(preflight.changes) == 0 {
|
|
return taskChangeRequestAutomationPreflight{}, fmt.Errorf("%s has no linked change requests", provider)
|
|
}
|
|
for _, change := range preflight.changes {
|
|
if change.IdentityStatus != mcphandlers.TaskChangeRequestIdentityResolved {
|
|
return taskChangeRequestAutomationPreflight{}, fmt.Errorf("%s contains an unresolved or ambiguous change request identity", provider)
|
|
}
|
|
if err := validateAutomationCapabilities(change, request.Patch); err != nil {
|
|
return taskChangeRequestAutomationPreflight{}, err
|
|
}
|
|
}
|
|
return preflight, nil
|
|
}
|
|
if err := validateAutomationProviderCapabilities(preflight.settings, request.Patch); err != nil {
|
|
return taskChangeRequestAutomationPreflight{}, err
|
|
}
|
|
return preflight, nil
|
|
}
|
|
|
|
func taskChangeRequestProviderSettings(
|
|
snapshot mcphandlers.TaskChangeRequestReadResponse, provider string,
|
|
) *mcphandlers.TaskChangeRequestProviderSettings {
|
|
for i := range snapshot.ProviderSettings {
|
|
if snapshot.ProviderSettings[i].Provider == provider {
|
|
return &snapshot.ProviderSettings[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cloneTaskChangeRequestProviderSettings(
|
|
settings *mcphandlers.TaskChangeRequestProviderSettings,
|
|
) *mcphandlers.TaskChangeRequestProviderSettings {
|
|
if settings == nil {
|
|
return nil
|
|
}
|
|
copy := *settings
|
|
return ©
|
|
}
|
|
|
|
func validateAutomationProviderCapabilities(
|
|
settings *mcphandlers.TaskChangeRequestProviderSettings,
|
|
patch mcphandlers.TaskChangeRequestAutomationPatch,
|
|
) error {
|
|
if settings == nil {
|
|
return errors.New("provider capabilities are unavailable")
|
|
}
|
|
if patch.AutoFixPromptOverride != nil && !providerSupports(settings.Provider, "custom_auto_fix_prompt") {
|
|
return errors.New("provider does not support custom auto-fix prompts")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateAutomationCapabilities(
|
|
change mcphandlers.TaskChangeRequest,
|
|
patch mcphandlers.TaskChangeRequestAutomationPatch,
|
|
) error {
|
|
for _, capability := range automationPatchCapabilities(patch) {
|
|
if !change.Capabilities[capability] {
|
|
reason := change.CapabilityReasons[capability]
|
|
if reason == "" {
|
|
reason = "unsupported"
|
|
}
|
|
return fmt.Errorf("change request cannot use %s: %s", capability, reason)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func providerSupports(provider, capability string) bool {
|
|
return taskChangeRequestProviderCapabilities(provider)[capability]
|
|
}
|
|
|
|
func automationPatchCapabilities(patch mcphandlers.TaskChangeRequestAutomationPatch) []string {
|
|
capabilities := make([]string, 0, 2)
|
|
if patch.AutoFixEnabled != nil {
|
|
capabilities = append(capabilities, "auto_fix")
|
|
}
|
|
if patch.AutoMergeEnabled != nil {
|
|
capabilities = append(capabilities, "auto_merge")
|
|
}
|
|
if patch.PromptOnReviewRequested != nil || patch.PromptOnMerged != nil || patch.PromptOnClosed != nil {
|
|
capabilities = append(capabilities, "lifecycle_notifications")
|
|
}
|
|
if patch.AutoFixPromptOverride != nil {
|
|
capabilities = append(capabilities, "custom_auto_fix_prompt")
|
|
}
|
|
return capabilities
|
|
}
|
|
|
|
func hasAutomationSwitchPatch(patch mcphandlers.TaskChangeRequestAutomationPatch) bool {
|
|
return patch.AutoFixEnabled != nil || patch.AutoMergeEnabled != nil ||
|
|
patch.PromptOnReviewRequested != nil || patch.PromptOnMerged != nil || patch.PromptOnClosed != nil
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) updateGitHub(
|
|
ctx context.Context,
|
|
taskID string,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
) (*github.TaskCIOptionsResponse, error) {
|
|
if c.github == nil {
|
|
return nil, errors.New("github provider is unavailable")
|
|
}
|
|
patch := github.TaskCIOptionsPatch{
|
|
AutoFixEnabled: request.Patch.AutoFixEnabled,
|
|
AutoMergeEnabled: request.Patch.AutoMergeEnabled,
|
|
AutoFixPromptOverride: request.Patch.AutoFixPromptOverride,
|
|
PromptOnReviewRequested: request.Patch.PromptOnReviewRequested,
|
|
PromptOnMerged: request.Patch.PromptOnMerged,
|
|
PromptOnClosed: request.Patch.PromptOnClosed,
|
|
}
|
|
if request.Target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
repositoryID := request.Target.RepositoryID
|
|
number := request.Target.Number
|
|
patch.RepositoryID = &repositoryID
|
|
patch.PRNumber = &number
|
|
}
|
|
return c.github.UpdateTaskCIOptions(ctx, taskID, patch)
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) updateGitLab(
|
|
ctx context.Context,
|
|
taskID string,
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
) (*gitlab.TaskMRAutomationResponse, error) {
|
|
if c.gitlab == nil {
|
|
return nil, errors.New("gitlab provider is unavailable")
|
|
}
|
|
patch := gitlab.TaskMRAutomationPatch{
|
|
AutoFixEnabled: request.Patch.AutoFixEnabled,
|
|
AutoMergeEnabled: request.Patch.AutoMergeEnabled,
|
|
AutoFixPromptOverride: request.Patch.AutoFixPromptOverride,
|
|
PromptOnReviewRequested: request.Patch.PromptOnReviewRequested,
|
|
PromptOnMerged: request.Patch.PromptOnMerged,
|
|
PromptOnClosed: request.Patch.PromptOnClosed,
|
|
}
|
|
if request.Target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
if len(preflight.changes) != 1 {
|
|
return nil, errors.New("change request identity is not unique")
|
|
}
|
|
change := preflight.changes[0]
|
|
repositoryID := change.ProviderRepositoryID
|
|
projectPath := change.ProviderProjectPath
|
|
number := change.Number
|
|
patch.RepositoryID = &repositoryID
|
|
patch.ProjectPath = &projectPath
|
|
patch.MRIID = &number
|
|
}
|
|
return c.gitlab.UpdateTaskMRAutomationOptions(ctx, taskID, patch)
|
|
}
|
|
|
|
func automationProviderResult(
|
|
result *mcphandlers.TaskChangeRequestAutomationResult, provider string,
|
|
) *mcphandlers.TaskChangeRequestAutomationProviderResult {
|
|
for i := range result.Providers {
|
|
if result.Providers[i].Provider == provider {
|
|
return &result.Providers[i]
|
|
}
|
|
}
|
|
result.Providers = append(result.Providers, mcphandlers.TaskChangeRequestAutomationProviderResult{
|
|
Provider: provider, Status: mcphandlers.TaskChangeRequestAutomationProviderSkipped,
|
|
Affected: []mcphandlers.TaskChangeRequestAutomationIdentity{},
|
|
})
|
|
return &result.Providers[len(result.Providers)-1]
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) markAutomationProviderFailure(
|
|
taskID string,
|
|
providerResult *mcphandlers.TaskChangeRequestAutomationProviderResult,
|
|
err error,
|
|
) {
|
|
providerResult.Status = mcphandlers.TaskChangeRequestAutomationProviderFailed
|
|
providerResult.StateKnown = false
|
|
providerResult.ErrorCode = "provider_update_failed"
|
|
providerResult.ErrorMessage = "provider update failed; resulting state is unknown"
|
|
c.logger.Error("task change request automation provider update failed",
|
|
zap.String("task_id", taskID), zap.String("provider", providerResult.Provider), zap.Error(err))
|
|
}
|
|
|
|
func automationStatusAfterFailure(applied int) string {
|
|
if applied > 0 {
|
|
return mcphandlers.TaskChangeRequestAutomationStatusPartial
|
|
}
|
|
return mcphandlers.TaskChangeRequestAutomationStatusFailed
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) resultFromGitHubValue(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
response *github.TaskCIOptionsResponse,
|
|
) mcphandlers.TaskChangeRequestAutomationProviderResult {
|
|
providerResult := mcphandlers.TaskChangeRequestAutomationProviderResult{
|
|
Provider: mcphandlers.TaskChangeRequestProviderGitHub,
|
|
Status: mcphandlers.TaskChangeRequestAutomationProviderApplied,
|
|
Affected: []mcphandlers.TaskChangeRequestAutomationIdentity{},
|
|
StateKnown: response != nil,
|
|
}
|
|
if response == nil {
|
|
providerResult.ErrorCode = "readback_unknown"
|
|
providerResult.ErrorMessage = "provider update completed without a readable result"
|
|
return providerResult
|
|
}
|
|
providerResult.ResultingSettings = githubTaskChangeRequestSettings(response)
|
|
copyProviderSettingsState(providerResult.ResultingSettings, preflight.settings)
|
|
providerResult.ResultingChangeRequests = cloneTaskChangeRequests(preflight.changes)
|
|
applyGitHubTaskChangeRequestAutomation(providerResult.ResultingChangeRequests, response)
|
|
if hasAutomationSwitchPatch(request.Patch) {
|
|
providerResult.Affected = githubAffectedIdentities(request, preflight, response)
|
|
}
|
|
return providerResult
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) resultFromGitLabValue(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
response *gitlab.TaskMRAutomationResponse,
|
|
) mcphandlers.TaskChangeRequestAutomationProviderResult {
|
|
providerResult := mcphandlers.TaskChangeRequestAutomationProviderResult{
|
|
Provider: mcphandlers.TaskChangeRequestProviderGitLab,
|
|
Status: mcphandlers.TaskChangeRequestAutomationProviderApplied,
|
|
Affected: []mcphandlers.TaskChangeRequestAutomationIdentity{},
|
|
StateKnown: response != nil,
|
|
}
|
|
if response == nil {
|
|
providerResult.ErrorCode = "readback_unknown"
|
|
providerResult.ErrorMessage = "provider update completed without a readable result"
|
|
return providerResult
|
|
}
|
|
providerResult.ResultingSettings = gitlabTaskChangeRequestSettings(response)
|
|
copyProviderSettingsState(providerResult.ResultingSettings, preflight.settings)
|
|
providerResult.ResultingChangeRequests = cloneTaskChangeRequests(preflight.changes)
|
|
applyGitLabTaskChangeRequestAutomation(providerResult.ResultingChangeRequests, response)
|
|
if hasAutomationSwitchPatch(request.Patch) {
|
|
providerResult.Affected = gitlabAffectedIdentities(request, preflight, response)
|
|
}
|
|
return providerResult
|
|
}
|
|
|
|
func copyProviderSettingsState(
|
|
destination *mcphandlers.TaskChangeRequestProviderSettings,
|
|
source *mcphandlers.TaskChangeRequestProviderSettings,
|
|
) {
|
|
if destination == nil || source == nil {
|
|
return
|
|
}
|
|
destination.Status = source.Status
|
|
destination.Configured = source.Configured
|
|
destination.Available = source.Available
|
|
destination.ReasonCode = source.ReasonCode
|
|
}
|
|
|
|
func cloneTaskChangeRequests(changes []mcphandlers.TaskChangeRequest) []mcphandlers.TaskChangeRequest {
|
|
cloned := make([]mcphandlers.TaskChangeRequest, len(changes))
|
|
copy(cloned, changes)
|
|
return cloned
|
|
}
|
|
|
|
func githubAffectedIdentities(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
response *github.TaskCIOptionsResponse,
|
|
) []mcphandlers.TaskChangeRequestAutomationIdentity {
|
|
if request.Target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
return []mcphandlers.TaskChangeRequestAutomationIdentity{{
|
|
Provider: request.Target.Provider, RepositoryID: request.Target.RepositoryID, Number: request.Target.Number,
|
|
}}
|
|
}
|
|
identities := make([]mcphandlers.TaskChangeRequestAutomationIdentity, 0, len(response.PROptions))
|
|
for _, option := range response.PROptions {
|
|
if option == nil {
|
|
continue
|
|
}
|
|
identities = append(identities, mcphandlers.TaskChangeRequestAutomationIdentity{
|
|
Provider: mcphandlers.TaskChangeRequestProviderGitHub,
|
|
RepositoryID: option.RepositoryID,
|
|
Number: option.PRNumber,
|
|
})
|
|
}
|
|
if len(identities) == 0 {
|
|
for _, change := range preflight.changes {
|
|
if change.RepositoryID != nil {
|
|
identities = append(identities, mcphandlers.TaskChangeRequestAutomationIdentity{
|
|
Provider: change.Provider, RepositoryID: *change.RepositoryID, Number: change.Number,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return identities
|
|
}
|
|
|
|
func gitlabAffectedIdentities(
|
|
request mcphandlers.TaskChangeRequestAutomationRequest,
|
|
preflight taskChangeRequestAutomationPreflight,
|
|
response *gitlab.TaskMRAutomationResponse,
|
|
) []mcphandlers.TaskChangeRequestAutomationIdentity {
|
|
if request.Target.Scope == mcphandlers.TaskChangeRequestAutomationScopeAssociation {
|
|
return []mcphandlers.TaskChangeRequestAutomationIdentity{{
|
|
Provider: request.Target.Provider, RepositoryID: request.Target.RepositoryID, Number: request.Target.Number,
|
|
}}
|
|
}
|
|
byRawIdentity := make(map[string]mcphandlers.TaskChangeRequest, len(preflight.changes))
|
|
for _, change := range preflight.changes {
|
|
key := gitLabAutomationIdentityKey(change.ProviderRepositoryID, change.ProviderProjectPath, change.Number)
|
|
byRawIdentity[key] = change
|
|
}
|
|
identities := make([]mcphandlers.TaskChangeRequestAutomationIdentity, 0, len(response.MROptions))
|
|
for _, option := range response.MROptions {
|
|
if option == nil {
|
|
continue
|
|
}
|
|
change, ok := byRawIdentity[gitLabAutomationIdentityKey(option.RepositoryID, option.ProjectPath, option.MRIID)]
|
|
repositoryID := option.RepositoryID
|
|
if ok && change.RepositoryID != nil {
|
|
repositoryID = *change.RepositoryID
|
|
}
|
|
identities = append(identities, mcphandlers.TaskChangeRequestAutomationIdentity{
|
|
Provider: mcphandlers.TaskChangeRequestProviderGitLab,
|
|
RepositoryID: repositoryID,
|
|
Number: option.MRIID,
|
|
})
|
|
}
|
|
return identities
|
|
}
|
|
|
|
func gitLabAutomationIdentityKey(repositoryID, projectPath string, number int) string {
|
|
return fmt.Sprintf("%s|%s|%d", strings.TrimSpace(repositoryID), strings.Trim(strings.TrimSpace(projectPath), "/"), number)
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) publishGitHubOptionsUpdated(
|
|
ctx context.Context, response *github.TaskCIOptionsResponse,
|
|
) {
|
|
if c.eventBus == nil || response == nil {
|
|
return
|
|
}
|
|
event := bus.NewEvent(events.GitHubTaskCIOptionsUpdated, "mcp", response)
|
|
if err := c.eventBus.Publish(ctx, events.GitHubTaskCIOptionsUpdated, event); err != nil {
|
|
c.logger.Warn("failed to publish MCP PR automation options update", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func (c *taskChangeRequestAutomationCoordinator) publishGitLabOptionsUpdated(
|
|
ctx context.Context, response *gitlab.TaskMRAutomationResponse,
|
|
) {
|
|
if c.eventBus == nil || response == nil {
|
|
return
|
|
}
|
|
event := bus.NewEvent(events.GitLabTaskMROptionsUpdated, "mcp", response)
|
|
if err := c.eventBus.Publish(ctx, events.GitLabTaskMROptionsUpdated, event); err != nil {
|
|
c.logger.Warn("failed to publish MCP MR automation options update", zap.Error(err))
|
|
}
|
|
}
|