1package gitlab
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 "github.com/xanzy/go-gitlab"
9
10 "github.com/MichaelMure/git-bug/util/text"
11)
12
13// Event represents a unified GitLab event (note, label or state event).
14type Event interface {
15 ID() string
16 UserID() int
17 Kind() EventKind
18 CreatedAt() time.Time
19}
20
21type EventKind int
22
23const (
24 EventUnknown EventKind = iota
25 EventError
26 EventComment
27 EventTitleChanged
28 EventDescriptionChanged
29 EventClosed
30 EventReopened
31 EventLocked
32 EventUnlocked
33 EventChangedDuedate
34 EventRemovedDuedate
35 EventAssigned
36 EventUnassigned
37 EventChangedMilestone
38 EventRemovedMilestone
39 EventAddLabel
40 EventRemoveLabel
41 EventMentionedInIssue
42 EventMentionedInMergeRequest
43)
44
45var _ Event = &NoteEvent{}
46
47type NoteEvent struct{ gitlab.Note }
48
49func (n NoteEvent) ID() string { return fmt.Sprintf("%d", n.Note.ID) }
50func (n NoteEvent) UserID() int { return n.Author.ID }
51func (n NoteEvent) CreatedAt() time.Time { return *n.Note.CreatedAt }
52func (n NoteEvent) Kind() EventKind {
53
54 switch {
55 case !n.System:
56 return EventComment
57
58 case n.Body == "closed":
59 return EventClosed
60
61 case n.Body == "reopened":
62 return EventReopened
63
64 case n.Body == "changed the description":
65 return EventDescriptionChanged
66
67 case n.Body == "locked this issue":
68 return EventLocked
69
70 case n.Body == "unlocked this issue":
71 return EventUnlocked
72
73 case strings.HasPrefix(n.Body, "changed title from"):
74 return EventTitleChanged
75
76 case strings.HasPrefix(n.Body, "changed due date to"):
77 return EventChangedDuedate
78
79 case n.Body == "removed due date":
80 return EventRemovedDuedate
81
82 case strings.HasPrefix(n.Body, "assigned to @"):
83 return EventAssigned
84
85 case strings.HasPrefix(n.Body, "unassigned @"):
86 return EventUnassigned
87
88 case strings.HasPrefix(n.Body, "changed milestone to %"):
89 return EventChangedMilestone
90
91 case strings.HasPrefix(n.Body, "removed milestone"):
92 return EventRemovedMilestone
93
94 case strings.HasPrefix(n.Body, "mentioned in issue"):
95 return EventMentionedInIssue
96
97 case strings.HasPrefix(n.Body, "mentioned in merge request"):
98 return EventMentionedInMergeRequest
99
100 default:
101 return EventUnknown
102 }
103
104}
105
106func (n NoteEvent) Title() string {
107 if n.Kind() == EventTitleChanged {
108 return getNewTitle(n.Body)
109 }
110 return text.CleanupOneLine(n.Body)
111}
112
113var _ Event = &LabelEvent{}
114
115type LabelEvent struct{ gitlab.LabelEvent }
116
117func (l LabelEvent) ID() string { return fmt.Sprintf("%d", l.LabelEvent.ID) }
118func (l LabelEvent) UserID() int { return l.User.ID }
119func (l LabelEvent) CreatedAt() time.Time { return *l.LabelEvent.CreatedAt }
120func (l LabelEvent) Kind() EventKind {
121 switch l.Action {
122 case "add":
123 return EventAddLabel
124 case "remove":
125 return EventRemoveLabel
126 default:
127 return EventUnknown
128 }
129}
130
131var _ Event = &StateEvent{}
132
133type StateEvent struct{ gitlab.StateEvent }
134
135func (s StateEvent) ID() string { return fmt.Sprintf("%d", s.StateEvent.ID) }
136func (s StateEvent) UserID() int { return s.User.ID }
137func (s StateEvent) CreatedAt() time.Time { return *s.StateEvent.CreatedAt }
138func (s StateEvent) Kind() EventKind {
139 switch s.State {
140 case "closed":
141 return EventClosed
142 case "opened", "reopened":
143 return EventReopened
144 default:
145 return EventUnknown
146 }
147}
148
149var _ Event = &ErrorEvent{}
150
151type ErrorEvent struct {
152 Err error
153 Time time.Time
154}
155
156func (e ErrorEvent) ID() string { return "" }
157func (e ErrorEvent) UserID() int { return -1 }
158func (e ErrorEvent) CreatedAt() time.Time { return e.Time }
159func (e ErrorEvent) Kind() EventKind { return EventError }
160
161// SortedEvents fan-in some Event-channels into one, sorted by creation date, using CreatedAt-method.
162// This function assume that each channel is pre-ordered.
163func SortedEvents(inputs ...<-chan Event) chan Event {
164 out := make(chan Event)
165
166 go func() {
167 defer close(out)
168
169 heads := make([]Event, len(inputs))
170
171 // pre-fill the head view
172 for i, input := range inputs {
173 if event, ok := <-input; ok {
174 heads[i] = event
175 }
176 }
177
178 for {
179 var earliestEvent Event
180 var originChannel int
181
182 // pick the earliest event of the heads
183 for i, head := range heads {
184 if head != nil && (earliestEvent == nil || head.CreatedAt().Before(earliestEvent.CreatedAt())) {
185 earliestEvent = head
186 originChannel = i
187 }
188 }
189
190 if earliestEvent == nil {
191 // no event anymore, we are done
192 return
193 }
194
195 // we have an event: consume it and replace it if possible
196 heads[originChannel] = nil
197 if event, ok := <-inputs[originChannel]; ok {
198 heads[originChannel] = event
199 }
200 out <- earliestEvent
201 }
202 }()
203
204 return out
205}
206
207// getNewTitle parses body diff given by gitlab api and return it final form
208// examples:
209// - "changed title from **fourth issue** to **fourth issue{+ changed+}**"
210// - "changed title from **fourth issue{- changed-}** to **fourth issue**"
211func getNewTitle(diff string) string {
212 newTitle := strings.Split(diff, "** to **")[1]
213 newTitle = strings.Replace(newTitle, "{+", "", -1)
214 newTitle = strings.Replace(newTitle, "+}", "", -1)
215 return strings.TrimSuffix(newTitle, "**")
216}