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