package projectpruning import ( "context" "fmt" "time" "github.com/google/go-github/v47/github" "github.com/shurcooL/githubv4" "edge-infra.dev/pkg/f8n/devinfra/jack/plugin" ) const ( PluginName = "project_pruning" ) type Card struct { Title string ID string Content struct { Issue struct { State string ClosedAt string Number int } `graphql:"... on Issue"` } } func init() { plugin.RegisterIssueHandler(PluginName, handleIssue) } func handleIssue(hp plugin.HandlerParams, ce github.IssuesEvent) { hp.Log.WithName(PluginName) switch action := ce.GetAction(); action { case "opened": client := hp.ClientV4 ctx := context.Background() id, err := fetchProjectNodeID(ctx, client, "ncrvoyix-swt-retail", 12) if err != nil { hp.Log.Error(err, "Failed to fetch project node ID") } items, err := fetchProjectItems(ctx, client, id) if err != nil { hp.Log.Error(err, "Failed to fetch project items") } hp.Log.Info(fmt.Sprintf("there are currently %d items in the project\n", len(items))) if len(items) < 1100 { cardLimit := 1100 - len(items) hp.Log.Info(fmt.Sprintf("Project cards under threshold. %d cards until pruning", cardLimit)) return } parseCards(ctx, hp, client, id, items) } } func fetchProjectNodeID(ctx context.Context, c plugin.GithubV4ClientInterface, org string, id int) (string, error) { var q struct { Organization struct { Project struct { ID string } `graphql:"projectNext(number: $id)"` } `graphql:"organization(login: $org)"` } variables := map[string]interface{}{ "org": githubv4.String(org), "id": githubv4.Int(id), /* #nosec G115 */ } err := c.Query(ctx, &q, variables) return q.Organization.Project.ID, err } func fetchProjectItems(ctx context.Context, c plugin.GithubV4ClientInterface, id string) ([]Card, error) { var q struct { Node struct { Project struct { Items struct { Nodes []Card PageInfo struct { EndCursor githubv4.String HasNextPage bool } } `graphql:"items(first: 100, after: $cursor)"` } `graphql:"... on ProjectNext"` } `graphql:"node(id: $id)"` } variables := map[string]interface{}{ "id": githubv4.ID(id), "cursor": (*githubv4.String)(nil), } var cards []Card for { err := c.Query(ctx, &q, variables) if err != nil { return nil, err } cards = append(cards, q.Node.Project.Items.Nodes...) if !q.Node.Project.Items.PageInfo.HasNextPage { break } variables["cursor"] = githubv4.NewString(q.Node.Project.Items.PageInfo.EndCursor) } return cards, nil } func deleteCard(ctx context.Context, c plugin.GithubV4ClientInterface, pid, cid string) error { var m struct { DeleteProjectV2ItemInput struct { DeletedItemID githubv4.ID } `graphql:"deleteProjectV2Item(input: $input)"` } input := githubv4.DeleteProjectV2ItemInput{ ProjectID: githubv4.ID(pid), ItemID: githubv4.ID(cid), } return c.Mutate(ctx, &m, input, nil) } func parseCards(ctx context.Context, hp plugin.HandlerParams, client plugin.GithubV4ClientInterface, id string, items []Card) { cutoff := time.Now().AddDate(0, 0, -14) hp.Log.Info("(issues still exist, just being removed from the project") var r []Card for _, item := range items { conditional := item.Content.Issue.State == string(githubv4.IssueStateClosed) switch status := conditional; status { case true: closedTime, err := time.Parse(time.RFC3339, item.Content.Issue.ClosedAt) if err != nil { hp.Log.Error(err, "Failed to return time value") } // issueNum := item.Content.Issue.Number // issue, _, err := hp.Client.Issues().Get(hp.Ctx, hp.Org, hp.Repo, issueNum) // if err != nil { // hp.Log.Error(err, "Failed to fetch issue") // } // check := true // labels := issue.Labels // for _, label := range labels { // name := label.GetName() // if name == string(constants.Epic) || name == string(constants.Capability) || name == string(constants.Feature) { // check = false // } // } // if err != nil { // hp.Log.Error(err, "") // } //if closedTime.Before(cutoff) && check { if closedTime.Before(cutoff) { r = append(r, item) } } } hp.Log.Info(fmt.Sprintf("identified %d items for deletion\n", len(r))) for _, item := range r { if err := deleteCard(ctx, client, id, item.ID); err != nil { hp.Log.Error(err, "Failed to delete card") } hp.Log.Info(fmt.Sprintf("%s: %s\n", item.Title, item.Content.Issue.ClosedAt)) } }