package list import ( "context" "fmt" "strconv" "strings" "github.com/google/go-github/v47/github" "edge-infra.dev/pkg/f8n/devinfra/jack/constants" guestservices "edge-infra.dev/pkg/f8n/devinfra/jack/guest_services" "edge-infra.dev/pkg/f8n/devinfra/jack/plugin" "edge-infra.dev/pkg/lib/logging" ) // Adding issue(s) from an epic func addIssue(hp plugin.HandlerParams, event AddRemoveEvent, commands []string, isParent bool) error { log := hp.Log ctx := hp.Ctx client := hp.Client log.Info("Adding a list of issues") epicNumber := event.EpicNumber repoOwner := event.RepoOwner repoName := event.RepoName log.Info(fmt.Sprintf("%+v", commands)) hasParentLabel := false for _, v := range event.Labels { if guestservices.IsParentLabel(v.GetName()) { log.Info(v.GetName()) hasParentLabel = true log.Info("Issue already has a parent label") } } isEpic := guestservices.HasEpicLabel(event.Labels) // if the issue has the epic label and its attempting to add a parent stop it if isEpic && isParent { log.Info("Epics cant have parents silly goose") msg := fmt.Sprintf("%s `%s`\n%s `%s` `%s` `%s`\n___\n%s", "Cannot add parents to issues labeled with", string(constants.Epic), "Use a different parent label such as:", string(constants.Capability), string(constants.Feature), string(constants.Story), "[Check the docs for more info](https://docs.edge-infra.dev/dev/project/#hierarchy)") issueComment := github.IssueComment{Body: &msg} if _, _, err := client.Issues().CreateComment(ctx, repoOwner, repoName, epicNumber, &issueComment); err != nil { log.Error(err, "Failed to comment on issue") return err } return nil } // iterate over the list of issues errorList, newParentBody := iterateOverListOfIssues(ctx, log, client, event, commands, isParent) if !hasParentLabel && !isParent { log.Info("adding epic label to new parent") labels := []string{string(constants.Epic)} var fl []*github.Label fl = append(fl, &github.Label{Name: github.String(string(constants.Epic))}) newParent := guestservices.ParentChild{} newParent.New(newParentBody, fl, constants.Preamble, constants.Postamble, event.RepoID) githubIssueBody := &github.IssueRequest{Body: github.String(newParent.ToString()), Labels: &labels} _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, epicNumber, githubIssueBody) if err != nil { return err } return nil // _, _, err := client.Issues.AddLabelsToIssue(ctx, repoOwner, repoName, epicNumber, labels) // if err != nil { // log.Error(err, "Failed to add kind/epic label") // } } githubIssueBody := &github.IssueRequest{Body: github.String(newParentBody)} _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, epicNumber, githubIssueBody) if err != nil { log.Error(err, "Failed to update epic desc") return err } if len(errorList) > 0 { errorString := strings.Join(errorList, "\n") log.Info("Commenting on epic that some issues were not added") log.Info(fmt.Sprintf("%s\n%s", "Some issues could not be added:", errorString)) msg := fmt.Sprintf("%s\n%s", "Some issues could not be added:", errorString) issueComment := github.IssueComment{Body: &msg} if _, _, err := client.Issues().CreateComment(ctx, repoOwner, repoName, epicNumber, &issueComment); err != nil { log.Error(err, "Failed to comment on issue") return err } } return nil } func iterateOverListOfIssues(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event AddRemoveEvent, commands []string, isParent bool) ([]string, string) { epicTitle := event.EpicTitle epicNumber := event.EpicNumber labels := event.Labels repoOwner := event.RepoOwner repoName := event.RepoName repoID := event.RepoID pc := guestservices.ParentChild{} pc.New(event.EpicBody, labels, constants.Preamble, constants.Postamble, repoID) log.Info(pc.ToString()) errorList := []string{} for _, v := range commands { log.Info(v) issueNumberRegex, err := strconv.Atoi(v) if err != nil { log.Info("#" + v + " - Failed to convert issue number to an int") errorList = append(errorList, "#"+v+" - failed to convert issue number to an int") continue } // Check if the user is attempting to add the issue to itself if epicNumber == issueNumberRegex { log.Info("#" + v + " - cannot add an issue to itself") errorList = append(errorList, "#"+v+" - cannot add an issue to itself") continue } // Get the issue foundIssue, _, err := client.Issues().Get(ctx, repoOwner, repoName, issueNumberRegex) if err != nil { log.Error(err, "Failed to get issues") errorList = append(errorList, "failed to get issue #"+v) continue } // check if the issue is a PR if foundIssue.IsPullRequest() { log.Info("#" + v + " references a pull request") errorList = append(errorList, "#"+v+" - pull requests cannot be linked to issues") continue } foundIssueNumber := foundIssue.GetNumber() foundIssueTitle := foundIssue.GetTitle() foundIssueBody := foundIssue.GetBody() foundLabels := foundIssue.Labels secondPC := &guestservices.ParentChild{} secondPC.New(foundIssueBody, foundLabels, constants.Preamble, constants.Postamble, repoID) log.Info(fmt.Sprintf("Adding epic %v to issue %v", epicNumber, foundIssueNumber)) // check if the issue being added is already on the list foundParentIndex := pc.FindParent(foundIssueNumber, repoID) foundChildIndex := pc.FindChild(foundIssueNumber, repoID) // if a parent was found with the same issue number and the command is /child, swap if foundParentIndex != -1 && !isParent { log.Info(fmt.Sprintf("This issue was already added to the parent list #%d", epicNumber)) log.Info(fmt.Sprintf("attempting to swap child to parent %d", foundIssueNumber)) success := pc.SwapParentToChild(foundIssueNumber, repoID) if success == -1 { log.Info(fmt.Sprintf("failed to swap #%d from parent to child on upstream list", foundIssueNumber)) } success = secondPC.SwapChildToParent(epicNumber, repoID) if success == -1 { log.Info(fmt.Sprintf("failed to swap #%d from child to parent on downstream list", epicNumber)) } } // if a child was found, its a parent command, and the downstream issue is a parent then swap p, _ := guestservices.CheckForParentLabel("", foundIssue.Labels) if foundChildIndex != -1 && isParent && p { log.Info(fmt.Sprintf("This issue was already added to the child list on #%d", epicNumber)) log.Info(fmt.Sprintf("attempting to swap child to parent %d", foundIssueNumber)) success := pc.SwapChildToParent(foundIssueNumber, repoID) if success == -1 { log.Info(fmt.Sprintf("failed to swap #%d from child to parent on upstream list", foundIssueNumber)) } log.Info(fmt.Sprintf("attempting to swap parent to child %d %d", foundIssueNumber, epicNumber)) success = secondPC.SwapParentToChild(epicNumber, repoID) if success == -1 { log.Info(fmt.Sprintf("failed to swap #%d from parent to child on downstream list", epicNumber)) } } // if there are no matches add like normal if foundChildIndex == -1 && foundParentIndex == -1 { // Create the new epic to add epicString := guestservices.ListItem{Number: epicNumber, Title: epicTitle, RepoID: repoID} // Create the issue and add the epic to its list if isParent { secondPC.AddChildItem(epicString) } else { secondPC.AddParentItem(epicString) } issueString := guestservices.ListItem{Number: foundIssueNumber, Title: foundIssueTitle, RepoID: repoID} if isParent { pc.AddParentItem(issueString) } else { pc.AddChildItem(issueString) } } newIssueBody := secondPC.ToString() // Update the issue with the new body githubIssueBody := &github.IssueRequest{Body: github.String(newIssueBody)} _, _, err = client.Issues().Edit(ctx, repoOwner, repoName, foundIssueNumber, githubIssueBody) if err != nil { log.Error(err, "Failed to update issue body") errorList = append(errorList, "#"+v+" - failed to update issue body") continue } log.Info(fmt.Sprintf("Added issue %v to epic %v", foundIssueNumber, epicNumber)) } return errorList, pc.ToString() } // Removing issue(s) from an epic func removeIssue(hp plugin.HandlerParams, event AddRemoveEvent, commands []string, isParent bool) error { log := hp.Log ctx := hp.Ctx client := hp.Client log.Info("Removing a list of issues") repoOwner := event.RepoOwner repoName := event.RepoName repoID := event.RepoID senderNumber := event.EpicNumber senderBody := event.EpicBody senderLabels := event.Labels sender := guestservices.ParentChild{} sender.New(senderBody, senderLabels, constants.Preamble, constants.Postamble, repoID) errorList := []string{} for _, v := range commands { issueNumberRegex, err := strconv.Atoi(v) if err != nil { log.Info(v + " Failed to convert issue number to an int") errorList = append(errorList, "#"+v+" - failed to convert issue number to an int") continue } // Get the issue receivingIssue, _, err := client.Issues().Get(ctx, repoOwner, repoName, issueNumberRegex) if err != nil { log.Error(err, "Failed to get issues") errorList = append(errorList, "#"+v+" - failed to retrieve issue") continue } if receivingIssue.IsPullRequest() { log.Info("#" + v + " references a pull request") errorList = append(errorList, "#"+v+" - pull requests cannot be linked to issues") continue } log.Info("Removing an epic from an issues list") receiverNumber := receivingIssue.GetNumber() receiverBody := receivingIssue.GetBody() receiverLabels := receivingIssue.Labels log.Info(fmt.Sprintf("Removing epic %v from issue %v", senderNumber, receiverNumber)) receiver := guestservices.ParentChild{} receiver.New(receiverBody, receiverLabels, constants.Preamble, constants.Postamble, repoID) if isParent { receiver.RemoveChild(senderNumber, repoID) } else { receiver.RemoveParent(senderNumber, repoID) } newReceiverBody := receiver.ToString() // Set its new body githubIssueBody := &github.IssueRequest{Body: github.String(newReceiverBody)} _, _, err = client.Issues().Edit(ctx, repoOwner, repoName, receiverNumber, githubIssueBody) if err != nil { log.Error(err, "Failed to update issue body") errorList = append(errorList, "#"+v+" - failed to update issue body") continue } if isParent { sender.RemoveParent(receiverNumber, repoID) } else { sender.RemoveChild(receiverNumber, repoID) } log.Info(fmt.Sprintf("Removed issue %v from epic %v", receiverNumber, senderNumber)) } newSenderBody := sender.ToString() githubIssueBody := &github.IssueRequest{Body: github.String(newSenderBody)} _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, senderNumber, githubIssueBody) if err != nil { log.Error(err, "Failed to update epic desc") return err } // If any errors were caught when adding issues add them to a comment on the epic if len(errorList) > 0 { errorString := strings.Join(errorList, "\n") log.Info("Commenting on epic that some issues were not removed") log.Info(fmt.Sprintf("%s\n%s", "Some issues could not be removed:", errorString)) msg := fmt.Sprintf("%s\n%s", "Some issues could not be removed:", errorString) issueComment := github.IssueComment{Body: &msg} if _, _, err := client.Issues().CreateComment(ctx, repoOwner, repoName, senderNumber, &issueComment); err != nil { log.Error(err, "Failed to comment on issue") return err } } return nil }