event.go

  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}