forked from fabric8-services/fabric8-wit
/
remoteworkitem.go
265 lines (229 loc) · 10.1 KB
/
remoteworkitem.go
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package remoteworkitem
import (
"encoding/json"
"strconv"
"strings"
"github.com/fabric8-services/fabric8-wit/rendering"
"github.com/fabric8-services/fabric8-wit/workitem"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
)
// List of supported attributes
const (
ProviderGithub = "github"
ProviderJira = "jira"
// The keys in the flattened response JSON of a typical Github issue.
GithubTitle = "title"
GithubDescription = "body"
GithubState = "state"
GithubID = "url"
GithubCreatorLogin = "user.login"
GithubCreatorProfileURL = "user.url"
GithubAssigneesLogin = "assignees.0.login"
GithubAssigneesLoginPattern = "assignees.?.login"
GithubAssigneesProfileURL = "assignees.0.url"
GithubAssigneesProfileURLPattern = "assignees.?.url"
// The keys in the flattened response JSON of a typical Jira issue.
JiraTitle = "fields.summary"
JiraBody = "fields.description"
JiraState = "fields.status.name"
JiraID = "self"
JiraCreatorLogin = "fields.creator.key"
JiraCreatorProfileURL = "fields.creator.self"
JiraAssigneeLogin = "fields.assignee.key"
JiraAssigneeProfileURL = "fields.assignee.self"
)
// RemoteWorkItem a temporary structure that holds the relevant field values retrieved from a remote work item
type RemoteWorkItem struct {
// The field values, according to the field type
Fields map[string]interface{}
// unique id per installation
ID uuid.UUID
// Name of the type of this work item
Type uuid.UUID `sql:"type:uuid"`
}
const (
remoteTitle = workitem.SystemTitle
remoteDescription = workitem.SystemDescription
remoteState = workitem.SystemState
remoteItemID = workitem.SystemRemoteItemID
remoteCreatorLogin = "system.creator.login"
remoteCreatorProfileURL = "system.creator.profile_url"
RemoteAssigneeLogins = "system.assignees.login"
RemoteAssigneeProfileURLs = "system.assignees.profile_url"
)
// RemoteWorkItemKeyMaps relate remote attribute keys to internal representation
var RemoteWorkItemKeyMaps = map[string]RemoteWorkItemMap{
ProviderGithub: {
AttributeMapper{AttributeExpression(GithubTitle), StringConverter{}}: remoteTitle,
AttributeMapper{AttributeExpression(GithubDescription), MarkupConverter{markup: rendering.SystemMarkupMarkdown}}: remoteDescription,
AttributeMapper{AttributeExpression(GithubState), GithubStateConverter{}}: remoteState,
AttributeMapper{AttributeExpression(GithubID), StringConverter{}}: remoteItemID,
AttributeMapper{AttributeExpression(GithubCreatorLogin), StringConverter{}}: remoteCreatorLogin,
AttributeMapper{AttributeExpression(GithubCreatorProfileURL), StringConverter{}}: remoteCreatorProfileURL,
AttributeMapper{AttributeExpression(GithubAssigneesLogin), PatternToListConverter{pattern: GithubAssigneesLoginPattern}}: RemoteAssigneeLogins,
AttributeMapper{AttributeExpression(GithubAssigneesProfileURL), PatternToListConverter{pattern: GithubAssigneesProfileURLPattern}}: RemoteAssigneeProfileURLs,
},
ProviderJira: {
AttributeMapper{AttributeExpression(JiraTitle), StringConverter{}}: remoteTitle,
AttributeMapper{AttributeExpression(JiraBody), MarkupConverter{markup: rendering.SystemMarkupJiraWiki}}: remoteDescription,
AttributeMapper{AttributeExpression(JiraState), JiraStateConverter{}}: remoteState,
AttributeMapper{AttributeExpression(JiraID), StringConverter{}}: remoteItemID,
AttributeMapper{AttributeExpression(JiraCreatorLogin), StringConverter{}}: remoteCreatorLogin,
AttributeMapper{AttributeExpression(JiraCreatorProfileURL), StringConverter{}}: remoteCreatorProfileURL,
AttributeMapper{AttributeExpression(JiraAssigneeLogin), ListConverter{}}: RemoteAssigneeLogins,
AttributeMapper{AttributeExpression(JiraAssigneeProfileURL), ListConverter{}}: RemoteAssigneeProfileURLs,
},
}
type AttributeConverter interface {
Convert(interface{}, AttributeAccessor) (interface{}, error)
}
// StateConverter converts a remote work item state
type StateConverter interface{}
// StringConverter converts a value to a string
type StringConverter struct{}
// ListConverter converts a value into a list containing a single element
type ListConverter struct{}
// PatternToListConverter joins multiple elements matching a regular expression into a single array
type PatternToListConverter struct {
pattern string
}
// MarkupConverter converts to a 'MarkupContent' element with the given 'Markup' value
type MarkupConverter struct {
markup string
}
type ListStringConverter struct{}
type GithubStateConverter struct{}
type JiraStateConverter struct{}
// Convert converts the given value to a string
func (converter StringConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
return value, nil
}
// Convert converts the given value to a list containing this single value as string
func (converter ListConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
if value == nil {
return make([]string, 0), nil
}
result := make([]string, 1)
result[0] = value.(string)
return result, nil
}
// Convert converts all fields from the given item that match this RegexpConverter's pattern, and returns an array of matching values as string
func (converter PatternToListConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
result := []string{}
i := 0
for {
key := AttributeExpression(strings.Replace(converter.pattern, "?", strconv.Itoa(i), 1))
if v := item.Get(key); v != nil {
result = append(result, v.(string))
} else {
break
}
i++
}
return result, nil
}
// Convert returns the given `value` if the `item` is not nil`, otherwise returns `nil`
func (converter MarkupConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
// return a 'nil' result if the supplied 'value' was nil
if value == nil {
return nil, nil
}
switch value.(type) {
case string:
return rendering.NewMarkupContent(value.(string), converter.markup), nil
default:
return nil, errors.Errorf("Unexpected type of value to convert: %T", value)
}
}
// Convert method map the external tracker item to WIT WorkItem
func (sc ListStringConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
return []interface{}{value}, nil
}
func (ghc GithubStateConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
if value.(string) == "closed" {
value = "closed"
} else if value.(string) == "open" {
value = "open"
}
return value, nil
}
func (jhc JiraStateConverter) Convert(value interface{}, item AttributeAccessor) (interface{}, error) {
if value.(string) == "closed" {
value = "closed"
} else if value.(string) == "open" {
value = "open"
} else if value.(string) == "in progress" {
value = "in progress"
} else if value.(string) == "resolved" {
value = "resolved"
}
return value, nil
}
type AttributeMapper struct {
Expression AttributeExpression
AttributeConverter AttributeConverter
}
// RemoteWorkItemMap will define mappings between remote<->internal attribute
type RemoteWorkItemMap map[AttributeMapper]string
// AttributeExpression represents a commonly understood String format for a target path
type AttributeExpression string
// AttributeAccessor defines the interface between a RemoteWorkItem and the Mapper
type AttributeAccessor interface {
// Get returns the value based on a commonly understood attribute expression
Get(field AttributeExpression) interface{}
}
// RemoteWorkItemImplRegistry contains all possible providers
var RemoteWorkItemImplRegistry = map[string]func(TrackerItem) (AttributeAccessor, error){
ProviderGithub: NewGitHubRemoteWorkItem,
ProviderJira: NewJiraRemoteWorkItem,
}
// GitHubRemoteWorkItem knows how to implement a FieldAccessor on a GitHub Issue JSON struct
// and it should also know how to convert a value in remote work item for use in local WI
type GitHubRemoteWorkItem struct {
issue map[string]interface{}
}
// NewGitHubRemoteWorkItem creates a new Decoded AttributeAccessor for a GitHub Issue
func NewGitHubRemoteWorkItem(item TrackerItem) (AttributeAccessor, error) {
var j map[string]interface{}
err := json.Unmarshal([]byte(item.Item), &j)
if err != nil {
return nil, errors.WithStack(err)
}
j = Flatten(j)
return GitHubRemoteWorkItem{issue: j}, nil
}
// Get attribute from issue map
func (gh GitHubRemoteWorkItem) Get(field AttributeExpression) interface{} {
return gh.issue[string(field)]
}
// JiraRemoteWorkItem knows how to implement a FieldAccessor on a Jira Issue JSON struct
type JiraRemoteWorkItem struct {
issue map[string]interface{}
}
// NewJiraRemoteWorkItem creates a new Decoded AttributeAccessor for a GitHub Issue
func NewJiraRemoteWorkItem(item TrackerItem) (AttributeAccessor, error) {
var j map[string]interface{}
err := json.Unmarshal([]byte(item.Item), &j)
if err != nil {
return nil, errors.WithStack(err)
}
j = Flatten(j)
return JiraRemoteWorkItem{issue: j}, nil
}
// Get attribute from issue map
func (jira JiraRemoteWorkItem) Get(field AttributeExpression) interface{} {
return jira.issue[string(field)]
}
// Map maps the remote WorkItem to a local RemoteWorkItem
func Map(remoteItem AttributeAccessor, mapping RemoteWorkItemMap) (RemoteWorkItem, error) {
remoteWorkItem := RemoteWorkItem{Fields: make(map[string]interface{})}
for from, to := range mapping {
originalValue := remoteItem.Get(from.Expression)
convertedValue, err := from.AttributeConverter.Convert(originalValue, remoteItem)
if err == nil {
remoteWorkItem.Fields[to] = convertedValue
}
}
return remoteWorkItem, nil
}