1 package projectpruning
2
3 import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/google/go-github/v47/github"
9 "github.com/shurcooL/githubv4"
10
11 "edge-infra.dev/pkg/f8n/devinfra/jack/plugin"
12 )
13
14 const (
15 PluginName = "project_pruning"
16 )
17
18 type Card struct {
19 Title string
20 ID string
21 Content struct {
22 Issue struct {
23 State string
24 ClosedAt string
25 Number int
26 } `graphql:"... on Issue"`
27 }
28 }
29
30 func init() {
31 plugin.RegisterIssueHandler(PluginName, handleIssue)
32 }
33
34 func handleIssue(hp plugin.HandlerParams, ce github.IssuesEvent) {
35 hp.Log.WithName(PluginName)
36
37 switch action := ce.GetAction(); action {
38 case "opened":
39 client := hp.ClientV4
40
41 ctx := context.Background()
42
43 id, err := fetchProjectNodeID(ctx, client, "ncrvoyix-swt-retail", 12)
44 if err != nil {
45 hp.Log.Error(err, "Failed to fetch project node ID")
46 }
47
48 items, err := fetchProjectItems(ctx, client, id)
49 if err != nil {
50 hp.Log.Error(err, "Failed to fetch project items")
51 }
52 hp.Log.Info(fmt.Sprintf("there are currently %d items in the project\n", len(items)))
53
54 if len(items) < 1100 {
55 cardLimit := 1100 - len(items)
56 hp.Log.Info(fmt.Sprintf("Project cards under threshold. %d cards until pruning", cardLimit))
57 return
58 }
59 parseCards(ctx, hp, client, id, items)
60 }
61 }
62
63 func fetchProjectNodeID(ctx context.Context, c plugin.GithubV4ClientInterface, org string, id int) (string, error) {
64 var q struct {
65 Organization struct {
66 Project struct {
67 ID string
68 } `graphql:"projectNext(number: $id)"`
69 } `graphql:"organization(login: $org)"`
70 }
71
72 variables := map[string]interface{}{
73 "org": githubv4.String(org),
74 "id": githubv4.Int(id),
75 }
76
77 err := c.Query(ctx, &q, variables)
78 return q.Organization.Project.ID, err
79 }
80
81 func fetchProjectItems(ctx context.Context, c plugin.GithubV4ClientInterface, id string) ([]Card, error) {
82 var q struct {
83 Node struct {
84 Project struct {
85 Items struct {
86 Nodes []Card
87 PageInfo struct {
88 EndCursor githubv4.String
89 HasNextPage bool
90 }
91 } `graphql:"items(first: 100, after: $cursor)"`
92 } `graphql:"... on ProjectNext"`
93 } `graphql:"node(id: $id)"`
94 }
95
96 variables := map[string]interface{}{
97 "id": githubv4.ID(id),
98 "cursor": (*githubv4.String)(nil),
99 }
100
101 var cards []Card
102 for {
103 err := c.Query(ctx, &q, variables)
104 if err != nil {
105 return nil, err
106 }
107 cards = append(cards, q.Node.Project.Items.Nodes...)
108 if !q.Node.Project.Items.PageInfo.HasNextPage {
109 break
110 }
111 variables["cursor"] = githubv4.NewString(q.Node.Project.Items.PageInfo.EndCursor)
112 }
113
114 return cards, nil
115 }
116
117 func deleteCard(ctx context.Context, c plugin.GithubV4ClientInterface, pid, cid string) error {
118 var m struct {
119 DeleteProjectV2ItemInput struct {
120 DeletedItemID githubv4.ID
121 } `graphql:"deleteProjectV2Item(input: $input)"`
122 }
123
124 input := githubv4.DeleteProjectV2ItemInput{
125 ProjectID: githubv4.ID(pid),
126 ItemID: githubv4.ID(cid),
127 }
128
129 return c.Mutate(ctx, &m, input, nil)
130 }
131
132 func parseCards(ctx context.Context, hp plugin.HandlerParams, client plugin.GithubV4ClientInterface, id string, items []Card) {
133 cutoff := time.Now().AddDate(0, 0, -14)
134 hp.Log.Info("(issues still exist, just being removed from the project")
135 var r []Card
136 for _, item := range items {
137 conditional := item.Content.Issue.State == string(githubv4.IssueStateClosed)
138 switch status := conditional; status {
139 case true:
140 closedTime, err := time.Parse(time.RFC3339, item.Content.Issue.ClosedAt)
141 if err != nil {
142 hp.Log.Error(err, "Failed to return time value")
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 if closedTime.Before(cutoff) {
166 r = append(r, item)
167 }
168 }
169 }
170 hp.Log.Info(fmt.Sprintf("identified %d items for deletion\n", len(r)))
171
172 for _, item := range r {
173 if err := deleteCard(ctx, client, id, item.ID); err != nil {
174 hp.Log.Error(err, "Failed to delete card")
175 }
176
177 hp.Log.Info(fmt.Sprintf("%s: %s\n", item.Title, item.Content.Issue.ClosedAt))
178 }
179 }
180
View as plain text