aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/gitlab/event.go
blob: 92592e4de753482a59eafd732d67974b36ac5c4f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package gitlab

import (
	"fmt"
	"strings"
	"time"

	"github.com/xanzy/go-gitlab"

	"github.com/git-bug/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, "**")
}