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