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