mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
199 lines
6.2 KiB
Go
199 lines
6.2 KiB
Go
// Package ghapi provides GitHub issue management functionality including
|
|
// label operations, milestone management, and project interactions.
|
|
package ghapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// ParseJSONtoIssues converts JSON data to a slice of Issue structs.
|
|
func ParseJSONtoIssues(jsonData []byte) ([]Issue, error) {
|
|
var issues []Issue
|
|
err := json.Unmarshal(jsonData, &issues)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return issues, nil
|
|
}
|
|
|
|
// GetIssues fetches issues from GitHub using optional search criteria.
|
|
func GetIssues(search string) ([]Issue, error) {
|
|
var issues []Issue
|
|
|
|
command := "gh issue list --json number,title,author,createdAt,updatedAt,state,labels,body"
|
|
if search != "" {
|
|
command = fmt.Sprintf("%s -S '%s'", command, search)
|
|
}
|
|
|
|
results, err := RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
issues, err = ParseJSONtoIssues(results)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return issues, nil
|
|
}
|
|
|
|
// GetIssuesByMilestoneLimited returns up to 'limit' issues for the given milestone title.
|
|
// It fetches limit+1 to detect whether there are more results than the limit; the returned
|
|
// slice is trimmed to 'limit', and the boolean indicates if more were available.
|
|
func GetIssuesByMilestoneLimited(title string, limit int) ([]Issue, bool, error) {
|
|
if limit <= 0 {
|
|
limit = 300
|
|
}
|
|
// Request one extra to detect overflow
|
|
reqLimit := limit + 1
|
|
cmd := fmt.Sprintf("gh issue list --milestone %q --json number,title,author,createdAt,updatedAt,state,labels,body --limit %d", title, reqLimit)
|
|
out, err := RunCommandAndReturnOutput(cmd)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
issues, err := ParseJSONtoIssues(out)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
exceeded := false
|
|
if len(issues) > limit {
|
|
exceeded = true
|
|
issues = issues[:limit]
|
|
}
|
|
return issues, exceeded, nil
|
|
}
|
|
|
|
// AddLabelToIssue adds a label to an issue.
|
|
func AddLabelToIssue(issueNumber int, label string) error {
|
|
command := fmt.Sprintf("gh issue edit %d --add-label %s", issueNumber, label)
|
|
_, err := RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveLabelFromIssue removes a label from an issue.
|
|
func RemoveLabelFromIssue(issueNumber int, label string) error {
|
|
command := fmt.Sprintf("gh issue edit %d --remove-label %s", issueNumber, label)
|
|
_, err := RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetMilestoneToIssue sets a milestone for an issue.
|
|
func SetMilestoneToIssue(issueNumber int, milestone string) error {
|
|
command := fmt.Sprintf("gh issue edit %d --milestone %s", issueNumber, milestone)
|
|
_, err := RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddIssueToProject adds an issue to a project.
|
|
func AddIssueToProject(issueNumber int, projectID int) error {
|
|
command := fmt.Sprintf("gh project item-add %d --owner fleetdm --url https://github.com/fleetdm/fleet/issues/%d", projectID, issueNumber)
|
|
_, err := RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveIssueFromProject removes an issue from a project.
|
|
func RemoveIssueFromProject(issueNumber int, projectID int) error {
|
|
// Get the project item ID for this issue using the same method as other functions
|
|
itemID, err := GetProjectItemID(issueNumber, projectID)
|
|
if err != nil {
|
|
// If the issue is not found in the project, that's not an error
|
|
if err.Error() == fmt.Sprintf("issue #%d not found in project %d", issueNumber, projectID) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to get project item ID: %v", err)
|
|
}
|
|
|
|
// Remove the item using the project ID and item ID
|
|
command := fmt.Sprintf("gh project item-delete %d --owner fleetdm --id %s", projectID, itemID)
|
|
_, err = RunCommandAndReturnOutput(command)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to remove issue %d from project %d: %v", issueNumber, projectID, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SyncEstimateField synchronizes the estimate field value from one project to another.
|
|
func SyncEstimateField(issueNumber int, sourceProjectID, targetProjectID int) error {
|
|
// Get the source project item to find the current estimate
|
|
sourceItemID, err := GetProjectItemID(issueNumber, sourceProjectID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get source project item ID: %v", err)
|
|
}
|
|
|
|
// Get the target project item
|
|
targetItemID, err := GetProjectItemID(issueNumber, targetProjectID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get target project item ID: %v", err)
|
|
}
|
|
|
|
// Get the estimate value from the source project using GraphQL
|
|
sourceEstimate, err := getProjectItemFieldValue(sourceItemID, sourceProjectID, "Estimate")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get source estimate: %v", err)
|
|
}
|
|
|
|
if sourceEstimate == "" || sourceEstimate == "0" {
|
|
return nil // No estimate to sync
|
|
}
|
|
|
|
// Set the estimate in the target project
|
|
err = SetProjectItemFieldValue(targetItemID, targetProjectID, "Estimate", sourceEstimate)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set target estimate: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetCurrentSprint sets the current sprint for an issue in a project.
|
|
func SetCurrentSprint(issueNumber int, projectID int) error {
|
|
// Get the project item ID
|
|
itemID, err := GetProjectItemID(issueNumber, projectID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get project item ID: %v", err)
|
|
}
|
|
|
|
// Look up the sprint field ID
|
|
sprintField, err := LookupProjectFieldName(projectID, "sprint")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to lookup sprint field: %v", err)
|
|
}
|
|
|
|
// Use the general field setting function to set the sprint field to @current
|
|
err = SetProjectItemFieldValue(itemID, projectID, sprintField.Name, "@current")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set current sprint: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetIssueStatus sets the status of an issue in a project using the Status field.
|
|
func SetIssueStatus(issueNumber int, projectID int, status string) error {
|
|
// Get the project item ID
|
|
itemID, err := GetProjectItemID(issueNumber, projectID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get project item ID: %v", err)
|
|
}
|
|
|
|
// Use the general field setting function to set the Status field
|
|
err = SetProjectItemFieldValue(itemID, projectID, "Status", status)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set status: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|