1 package epics
2
3 import (
4 "context"
5 "fmt"
6 "strconv"
7 "strings"
8
9 "github.com/google/go-github/v47/github"
10
11 "edge-infra.dev/pkg/f8n/devinfra/jack/constants"
12 guestservices "edge-infra.dev/pkg/f8n/devinfra/jack/guest_services"
13 "edge-infra.dev/pkg/f8n/devinfra/jack/plugin"
14 "edge-infra.dev/pkg/lib/logging"
15 )
16
17 func labelAdded(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event github.IssuesEvent) error {
18 repo := event.GetRepo()
19 repoName := repo.GetName()
20 repoID := repo.GetID()
21 repoOwner := repo.GetOwner().GetLogin()
22 issueNumber := event.GetIssue().GetNumber()
23 issueLabelName := event.GetLabel().GetName()
24
25
26 if !guestservices.IsParentLabel(issueLabelName) {
27 return nil
28 }
29 log.Info("Running epics plugin")
30
31 if event.GetSender().GetType() == constants.Bot {
32 log.Info("a bot added the label ignoring")
33 return nil
34 }
35
36 hasParentLabel, labels := guestservices.CheckForParentLabel(issueLabelName, event.Issue.Labels)
37 log.Info(fmt.Sprintf("%+v", labels))
38
39
40 if hasParentLabel {
41 return swapParentLabels(ctx, client, event, labels)
42 }
43 log.Info(fmt.Sprintf("Epic %v initializing", issueNumber))
44
45 body := event.GetIssue().GetBody()
46 parentLabels := event.GetIssue().Labels
47
48
49 parent := guestservices.ParentChild{}
50 parent.New(body, parentLabels, constants.Preamble, constants.Postamble, repoID)
51
52
53 parentBody := parent.ToString()
54
55 log.Info(fmt.Sprintf("Epic %v initialized with the jackbot epic template", issueNumber))
56
57 githubIssueBody := &github.IssueRequest{Body: github.String(parentBody)}
58 _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, issueNumber, githubIssueBody)
59 if err != nil {
60 return err
61 }
62
63 return nil
64 }
65
66 func swapParentLabels(ctx context.Context, client plugin.GithubClientInterface, event github.IssuesEvent, labels []string) error {
67 body := event.GetIssue().GetBody()
68 repo := event.GetRepo()
69 repoName := repo.GetName()
70 repoID := repo.GetID()
71 repoOwner := repo.GetOwner().GetLogin()
72 issueNumber := event.GetIssue().GetNumber()
73
74
75 var fl []*github.Label
76 for _, label := range labels {
77 fl = append(fl, &github.Label{Name: github.String(label)})
78 }
79
80 parent := guestservices.ParentChild{}
81 parent.New(body, fl, constants.Preamble, constants.Postamble, repoID)
82 parentBody := parent.ToString()
83
84 githubIssueBody := &github.IssueRequest{Body: github.String(parentBody), Labels: &labels}
85 _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, issueNumber, githubIssueBody)
86 if err != nil {
87 return err
88 }
89 return nil
90 }
91
92 func labelRemoved(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event github.IssuesEvent) error {
93 issueLabelName := event.GetLabel().GetName()
94
95
96 issueNumber := event.GetIssue().GetNumber()
97 repo := event.GetRepo()
98 repoName := repo.GetName()
99 repoOwner := repo.GetOwner().GetLogin()
100
101
102 if !guestservices.IsParentLabel(issueLabelName) {
103 return nil
104 }
105
106
107
108 isParent, _ := guestservices.CheckForParentLabel(issueLabelName, event.Issue.Labels)
109 if isParent {
110 return nil
111 }
112
113
114 err := handleEpicLabelRemoval(ctx, log, client, event)
115 if err != nil {
116 log.Error(err, "Failed to update issue desc")
117 return err
118 }
119 labelBody := createNewLabelBody(event.GetIssue().Labels)
120
121 githubIssueBody := &github.IssueRequest{Labels: &labelBody}
122 _, _, err = client.Issues().Edit(ctx, repoOwner, repoName, issueNumber, githubIssueBody)
123 if err != nil {
124 log.Error(err, "Failed to update issue desc")
125 return err
126 }
127 return nil
128 }
129
130 func handleEpicLabelRemoval(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event github.IssuesEvent) error {
131 log.Info("Removing the jackbot epic template from issue")
132
133
134 issueBody := event.GetIssue().GetBody()
135 issueLabels := event.GetIssue().Labels
136 issueNumber := event.GetIssue().GetNumber()
137 issueTitle := event.GetIssue().GetTitle()
138 repo := event.GetRepo()
139 repoID := repo.GetID()
140 repoName := repo.GetName()
141 repoOwner := repo.GetOwner().GetLogin()
142 issueCommenter := event.GetSender().GetLogin()
143
144 sender := guestservices.ParentChild{}
145 sender.New(issueBody, issueLabels, constants.Preamble, constants.Postamble, repoID)
146 newEpicBody, clearedChildren := sender.ClearChildren()
147
148 log.Info(fmt.Sprintf("Parent %v had its children removed", issueNumber))
149
150 clearedIssueComments := []string{}
151 for _, v := range clearedChildren {
152 foundIssue, _, err := client.Issues().Get(ctx, repoOwner, repoName, v.Number)
153 if err != nil {
154 log.Error(err, "Failed to get the selected issue")
155 return err
156 }
157
158 receiver := guestservices.ParentChild{}
159 receiver.New(foundIssue.GetBody(), foundIssue.Labels, constants.Preamble, constants.Postamble, repoID)
160
161 receiver.Remove(issueNumber, repoID)
162 newIssueBody := receiver.ToString()
163 log.Info(fmt.Sprintf("Removed Epic %v from issue %v", issueNumber, v.Number))
164
165 err = updateIssue(ctx, repoOwner, repoName, v.Number, newIssueBody, client)
166 if err != nil {
167 log.Error(err, "Failed to update issue with new body")
168 return err
169 }
170
171 msg := fmt.Sprintf("@%s has removed the parent label from ```#%d - %s``` and it has been removed from this issue.", issueCommenter, issueNumber, issueTitle)
172 prComment := github.IssueComment{
173 Body: &msg,
174 }
175 if _, _, err := client.Issues().CreateComment(ctx, repoOwner, repoName, v.Number, &prComment); err != nil {
176 log.Error(err, "Failed to comment on issue")
177 return err
178 }
179
180 clearedIssueComments = append(clearedIssueComments, "#"+strconv.Itoa(v.Number))
181 }
182 clearedCommentSection := strings.Join(clearedIssueComments, ", ")
183
184
185 err := updateIssue(ctx, repoOwner, repoName, issueNumber, newEpicBody, client)
186 if err != nil {
187 log.Error(err, "Failed to update child with new body")
188 return err
189 }
190
191 msg := fmt.Sprintf("@%s has removed the parent label and the following issues have been removed: %s", issueCommenter, clearedCommentSection)
192 prComment := github.IssueComment{
193 Body: &msg,
194 }
195 if _, _, err := client.Issues().CreateComment(ctx, repoOwner, repoName, issueNumber, &prComment); err != nil {
196 log.Error(err, "Failed to comment on issue")
197 return err
198 }
199
200 return nil
201 }
202
203 func updateIssue(ctx context.Context, repoOwner string, repoName string, issueNumber int, newBody string, client plugin.GithubClientInterface) error {
204 githubIssueBody := &github.IssueRequest{Body: github.String(newBody)}
205 _, _, err := client.Issues().Edit(ctx, repoOwner, repoName, issueNumber, githubIssueBody)
206 return err
207 }
208
209 func verifyEditedEpic(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event github.IssuesEvent) error {
210 log.Info("Checking edits made to an issue")
211
212 senderType := event.GetSender().GetType()
213 if senderType == constants.Bot {
214 return nil
215 }
216 err := ParseJackBlock(ctx, log, client, event)
217 if err != nil {
218 log.Error(err, "Failed to parse jack's block.")
219 return err
220 }
221 return nil
222 }
223
224 func createNewLabelBody(labels []*github.Label) []string {
225 label := []string{}
226
227
228 for _, v := range labels {
229 name := v.GetName()
230 label = append(label, name)
231 }
232
233 return label
234 }
235
236 func ParseJackBlock(ctx context.Context, log logging.EdgeLogger, client plugin.GithubClientInterface, event github.IssuesEvent) error {
237 changes := event.GetChanges()
238 changedBody := changes.GetBody().GetFrom()
239
240 if changedBody == "" {
241 log.Info("No changed body. Ignoring.")
242 }
243
244
245 oldBody := strings.ReplaceAll(changes.GetBody().GetFrom(), "\r\n", "\n")
246 newBody := strings.ReplaceAll(event.GetIssue().GetBody(), "\r\n", "\n")
247
248 issueNumber := event.GetIssue().GetNumber()
249 repo := event.GetRepo()
250 repoName := repo.GetName()
251 repoOwner := repo.GetOwner().GetLogin()
252
253
254 if oldBody == "" && newBody == "" {
255 log.Info("Issue Body is empty.")
256 cleanJackBlock := constants.Preamble + constants.Postamble
257 err := updateIssue(ctx, repoOwner, repoName, issueNumber, cleanJackBlock, client)
258 if err != nil {
259 log.Error(err, "Failed to update issue")
260 return err
261 }
262 emptyIssueCommentBody := "An error has occurred. The issue body cannot be left empty. The previous issue body cannot be recovered. Please add a parent or child to continue using jack."
263 emptyIssueComment := github.IssueComment{
264 Body: &emptyIssueCommentBody,
265 }
266 if _, _, err := client.Issues().CreateComment(ctx, repoOwner, *repo.Name, issueNumber, &emptyIssueComment); err != nil {
267 log.Error(err, "Failed to comment on issue")
268 return err
269 }
270 return nil
271 }
272 var newBodyPostamble []string
273 var userPostamble string
274 newBodySplit := strings.SplitN(newBody, "<!-- JACKBOT -->", 2)
275
276
277
278 index := 1
279 if len(newBodySplit) == 1 {
280 index = 0
281 }
282
283
284 userPostamble = "\n"
285 slice := newBodySplit[index]
286
287 if strings.Contains(slice, "<!-- ENDJACKBOT -->") {
288 newBodyPostamble = strings.SplitN(slice, "<!-- ENDJACKBOT -->", 2)
289 userPostamble = newBodyPostamble[1]
290 }
291
292
293 userPreamble := newBodySplit[0]
294
295 var correctPreamble string
296 var correctPreambleSplit []string
297 var correctBody string
298
299
300 correctBodySplit := strings.SplitN(oldBody, "<!-- JACKBOT -->", 2)
301 if len(correctBodySplit) != 2 {
302 return nil
303 }
304 correctPreamble = "<!-- JACKBOT -->" + correctBodySplit[1]
305
306 correctPreambleSplit = strings.SplitN(correctPreamble, "<!-- ENDJACKBOT -->", 2)
307
308 correctBody = correctPreambleSplit[0] + "<!-- ENDJACKBOT -->"
309
310
311 if !strings.Contains(newBody, correctBody) {
312
313 newBody = userPreamble + correctBody + userPostamble
314
315
316 if index == 0 {
317 newBody = correctBody
318 }
319 err := updateIssue(ctx, repoOwner, repoName, issueNumber, newBody, client)
320 if err != nil {
321 log.Error(err, "Failed to update issue")
322 return err
323 }
324 revertCommentBody := "Unauthorized manual edit to Jack's block attempted. Changes inside the block have been removed. Please place all edits above JACKBOT."
325 revertIssueComment := github.IssueComment{
326 Body: &revertCommentBody,
327 }
328 if _, _, err := client.Issues().CreateComment(ctx, repoOwner, *repo.Name, issueNumber, &revertIssueComment); err != nil {
329 log.Error(err, "Failed to comment on issue")
330 return err
331 }
332 }
333 return nil
334 }
335
View as plain text