event.go

  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/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}