package gitlab
import (
"fmt"
"strings"
"time"
"github.com/xanzy/go-gitlab"
"github.com/MichaelMure/git-bug/util/text"
)
// Event represents a unified GitLab event (note, label or state event).
type Event interface {
ID() string
UserID() int
Kind() EventKind
CreatedAt() time.Time
}
type EventKind int
const (
EventUnknown EventKind = iota
EventError
EventComment
EventTitleChanged
EventDescriptionChanged
EventClosed
EventReopened
EventLocked
EventUnlocked
EventChangedDuedate
EventRemovedDuedate
EventAssigned
EventUnassigned
EventChangedMilestone
EventRemovedMilestone
EventAddLabel
EventRemoveLabel
EventMentionedInIssue
EventMentionedInMergeRequest
EventMentionedInCommit
)
var _ Event = &NoteEvent{}
type NoteEvent struct{ gitlab.Note }
func (n NoteEvent) ID() string { return fmt.Sprintf("%d", n.Note.ID) }
func (n NoteEvent) UserID() int { return n.Author.ID }
func (n NoteEvent) CreatedAt() time.Time { return *n.Note.CreatedAt }
func (n NoteEvent) Kind() EventKind {
switch {
case !n.System:
return EventComment
case n.Body == "closed":
return EventClosed
case n.Body == "reopened":
return EventReopened
case n.Body == "changed the description":
return EventDescriptionChanged
case n.Body == "locked this issue":
return EventLocked
case n.Body == "unlocked this issue":
return EventUnlocked
case strings.HasPrefix(n.Body, "changed title from"):
return EventTitleChanged
case strings.HasPrefix(n.Body, "changed due date to"):
return EventChangedDuedate
case n.Body == "removed due date":
return EventRemovedDuedate
case strings.HasPrefix(n.Body, "assigned to @"):
return EventAssigned
case strings.HasPrefix(n.Body, "unassigned @"):
return EventUnassigned
case strings.HasPrefix(n.Body, "changed milestone to %"):
return EventChangedMilestone
case strings.HasPrefix(n.Body, "removed milestone"):
return EventRemovedMilestone
case strings.HasPrefix(n.Body, "mentioned in issue"):
return EventMentionedInIssue
case strings.HasPrefix(n.Body, "mentioned in merge request"):
return EventMentionedInMergeRequest
case strings.HasPrefix(n.Body, "mentioned in commit"):
return EventMentionedInCommit
default:
return EventUnknown
}
}
func (n NoteEvent) Title() string {
if n.Kind() == EventTitleChanged {
return getNewTitle(n.Body)
}
return text.CleanupOneLine(n.Body)
}
var _ Event = &LabelEvent{}
type LabelEvent struct{ gitlab.LabelEvent }
func (l LabelEvent) ID() string { return fmt.Sprintf("%d", l.LabelEvent.ID) }
func (l LabelEvent) UserID() int { return l.User.ID }
func (l LabelEvent) CreatedAt() time.Time { return *l.LabelEvent.CreatedAt }
func (l LabelEvent) Kind() EventKind {
switch l.Action {
case "add":
return EventAddLabel
case "remove":
return EventRemoveLabel
default:
return EventUnknown
}
}
var _ Event = &StateEvent{}
type StateEvent struct{ gitlab.StateEvent }
func (s StateEvent) ID() string { return fmt.Sprintf("%d", s.StateEvent.ID) }
func (s StateEvent) UserID() int { return s.User.ID }
func (s StateEvent) CreatedAt() time.Time { return *s.StateEvent.CreatedAt }
func (s StateEvent) Kind() EventKind {
switch s.State {
case "closed":
return EventClosed
case "opened", "reopened":
return EventReopened
default:
return EventUnknown
}
}
var _ Event = &ErrorEvent{}
type ErrorEvent struct {
Err error
Time time.Time
}
func (e ErrorEvent) ID() string { return "" }
func (e ErrorEvent) UserID() int { return -1 }
func (e ErrorEvent) CreatedAt() time.Time { return e.Time }
func (e ErrorEvent) Kind() EventKind { return EventError }
// SortedEvents fan-in some Event-channels into one, sorted by creation date, using CreatedAt-method.
// This function assume that each channel is pre-ordered.
func SortedEvents(inputs ...<-chan Event) chan Event {
out := make(chan Event)
go func() {
defer close(out)
heads := make([]Event, len(inputs))
// pre-fill the head view
for i, input := range inputs {
if event, ok := <-input; ok {
heads[i] = event
}
}
for {
var earliestEvent Event
var originChannel int
// pick the earliest event of the heads
for i, head := range heads {
if head != nil && (earliestEvent == nil || head.CreatedAt().Before(earliestEvent.CreatedAt())) {
earliestEvent = head
originChannel = i
}
}
if earliestEvent == nil {
// no event anymore, we are done
return
}
// we have an event: consume it and replace it if possible
heads[originChannel] = nil
if event, ok := <-inputs[originChannel]; ok {
heads[originChannel] = event
}
out <- earliestEvent
}
}()
return out
}
// getNewTitle parses body diff given by gitlab api and return it final form
// examples:
// - "changed title from **fourth issue** to **fourth issue{+ changed+}**"
// - "changed title from **fourth issue{- changed-}** to **fourth issue**"
func getNewTitle(diff string) string {
newTitle := strings.Split(diff, "** to **")[1]
newTitle = strings.Replace(newTitle, "{+", "", -1)
newTitle = strings.Replace(newTitle, "+}", "", -1)
return strings.TrimSuffix(newTitle, "**")
}