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