Skip to content
This repository was archived by the owner on Jun 8, 2023. It is now read-only.

Commit 372ced6

Browse files
author
Tom Bell
committed
Merge pull request #976 from robertfwest/rally_updates
Added new artifact types to Rally plugin
2 parents fd049ba + e9a97b2 commit 372ced6

File tree

1 file changed

+143
-95
lines changed

1 file changed

+143
-95
lines changed

src/scripts/rally.coffee

Lines changed: 143 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Description:
2-
# Rally information for bugs, stories, and users
2+
# Rally information for artifacts
33
#
44
# Dependencies:
55
# None
@@ -9,13 +9,12 @@
99
# HUBOT_RALLY_PASSWORD
1010
#
1111
# Commands:
12-
# hubot rally me <defect id | task id | story id> - Lookup a task, story or defect from Rally
12+
# hubot rally me <formattedID> - Lookup a task, story, defect, etc. from Rally
1313
#
1414
# Notes:
1515
# Since Rally supports rich text for description fields, it will come back as HTML
1616
# to pretty print this we can run it through lynx. Make sure you have lynx installed
1717
# and PATH accessible, otherwise we will degrade to just showing the html description.
18-
# ENV Variables Required:
1918
#
2019
# Author:
2120
# brianmichel
@@ -24,107 +23,156 @@ exec = require('child_process').exec
2423

2524
user = process.env.HUBOT_RALLY_USERNAME
2625
pass = process.env.HUBOT_RALLY_PASSWORD
27-
api_version = '1.40'
26+
api_version = 'v2.0'
27+
28+
logger = null
29+
30+
typeInfoByPrefix =
31+
DE:
32+
name: 'defect'
33+
extraOutputFields: [
34+
'State'
35+
'ScheduleState'
36+
'Severity'
37+
]
38+
DS:
39+
name: 'defectsuite'
40+
extraOutputFields: [
41+
'ScheduleState'
42+
]
43+
F:
44+
name: 'feature'
45+
queryName: 'portfolioitem/feature'
46+
linkName: 'portfolioitem/feature'
47+
extraOutputFields: [
48+
'State._refObjectName'
49+
'Parent._refObjectName'
50+
]
51+
I:
52+
name: 'initiative'
53+
queryName: 'portfolioitem/initiative'
54+
linkName: 'portfolioitem/initiative'
55+
extraOutputFields: [
56+
'State._refObjectName'
57+
'Parent._refObjectName'
58+
]
59+
T:
60+
name: 'theme'
61+
queryName: 'portfolioitem/theme'
62+
linkName: 'portfolioitem/theme'
63+
extraOutputFields: [
64+
'State._refObjectName'
65+
'Parent._refObjectName'
66+
]
67+
TA:
68+
name: 'task'
69+
extraOutputFields: [
70+
'State'
71+
'WorkProduct._refObjectName'
72+
]
73+
TC:
74+
name: 'testcase'
75+
extraOutputFields: [
76+
'WorkProduct._refObjectName'
77+
'Type'
78+
]
79+
US:
80+
name: 'story'
81+
queryName: 'hierarchicalrequirement'
82+
linkName: 'userstory'
83+
extraOutputFields: [
84+
'ScheduleState'
85+
'Parent._refObjectName'
86+
'Feature._refObjectName'
87+
]
88+
2889

2990
module.exports = (robot) ->
30-
robot.respond /(rally)( me)? (.*)/i, (msg) ->
31-
if user && pass
32-
switch msg.match[3].toUpperCase().substring(0,1)
33-
when "D" then bugRequest msg, msg.match[3], (string) ->
34-
msg.send string
35-
when "T" then taskRequest msg, msg.match[3], (string) ->
36-
msg.send string
37-
when "S" then storyRequest msg, msg.match[3], (string) ->
38-
msg.send string
39-
else msg.send "Uhh, that doesn't work"
40-
else
41-
msg.send "You need to set HUBOT_RALLY_USERNAME & HUBOT_RALLY_PASSWORD before making requests!"
91+
logger = robot.logger
92+
robot.respond /(rally)( me)? ([a-z]+)(\d+)/i, (msg) ->
93+
if user && pass
94+
idPrefix = msg.match[3].toUpperCase()
95+
idNumber = msg.match[4]
96+
if typeInfoByPrefix.hasOwnProperty(idPrefix)
97+
queryRequest msg, typeInfoByPrefix[idPrefix], idNumber, (string) ->
98+
msg.send string
99+
else
100+
msg.send "Uhh, I don't know that formatted ID prefix"
101+
else
102+
msg.send 'You need to set HUBOT_RALLY_USERNAME & HUBOT_RALLY_PASSWORD before making requests!'
42103

43-
bugRequest = (msg, defectId, cb) ->
44-
query_string = "/defect.js?query=(FormattedID = #{defectId})&fetch=true"
45-
rallyRequest msg, query_string, (json) ->
46-
if json && json.QueryResult.TotalResultCount > 0
47-
getLinkToItem msg, json.QueryResult.Results[0], "defect"
48-
description = "No Description"
49-
if json.QueryResult.Results[0].Description
50-
prettifyDescription json.QueryResult.Results[0].Description, (output) ->
51-
description = output
52-
return_array = [
53-
"#{json.QueryResult.Results[0].FormattedID} - #{json.QueryResult.Results[0]._refObjectName}"
54-
"Owner - #{ if json.QueryResult.Results[0].Owner then json.QueryResult.Results[0].Owner._refObjectName else "No Owner" }"
55-
"Project - #{ if json.QueryResult.Results[0].Project then json.QueryResult.Results[0].Project._refObjectName else "No Project" }"
56-
"Severity - #{json.QueryResult.Results[0].Severity}"
57-
"State - #{json.QueryResult.Results[0].State}"
58-
"#{description}"
59-
]
60-
cb return_array.join("\n")
61-
else
62-
cb "Aww snap, I couldn't find that bug!"
104+
queryRequest = (msg, typeInfo, idNumber, cb) ->
105+
queryName = typeInfo.queryName || typeInfo.name
106+
queryString = "/#{queryName}.js?query=(FormattedID = #{idNumber})&fetch=true"
107+
rallyRequest msg, queryString, (json) ->
108+
if json && json.QueryResult.TotalResultCount > 0
109+
result = json.QueryResult.Results[0]
110+
linkName = typeInfo.linkName || typeInfo.name
111+
getLinkToItem msg, result, linkName
112+
description = 'No Description'
113+
prettifyDescription result.Description, (output) ->
114+
description = output || description
115+
returnArray = [
116+
"#{result.FormattedID} - #{result.Name}"
117+
labeledField(result, 'Owner._refObjectName')
118+
labeledField(result, 'Project._refObjectName')
119+
]
120+
returnArray.push(labeledField(result, field)) for field in typeInfo.extraOutputFields
121+
returnArray.push("Description:")
122+
returnArray.push("#{description}")
123+
cb returnArray.join("\n")
124+
else
125+
cb "Aww snap, I couldn't find that #{typeInfo.name}!"
63126

64-
taskRequest = (msg, taskId, cb) ->
65-
query_string = "/task.js?query=(FormattedID = #{taskId})&fetch=true"
66-
rallyRequest msg, query_string, (json) ->
67-
if json && json.QueryResult.TotalResultCount > 0
68-
getLinkToItem msg, json.QueryResult.Results[0], "task"
69-
return_array = [
70-
"#{json.QueryResult.Results[0].FormattedID} - #{json.QueryResult.Results[0].Name}"
71-
"Owner - #{ if json.QueryResult.Results[0].Owner then json.QueryResult.Results[0].Owner._refObjectName else "No Owner" }"
72-
"Project - #{ if json.QueryResult.Results[0].Project then json.QueryResult.Results[0].Project._refObjectName else "No Project" }"
73-
"State - #{json.QueryResult.Results[0].State}"
74-
"Feature - #{if json.QueryResult.Results[0].WorkProduct ? json.QueryResult.Results[0].WorkProduct._refObjectName else "Not associated with any feature"}"
75-
]
76-
cb return_array.join("\n")
77-
else
78-
cb "Aww snap, I couldn't find that task!"
127+
labeledField = (result, field) ->
128+
match = field.match(/^(\w+)\._refObjectName$/)
129+
if match
130+
"#{match[1]}: #{refObjectName(result, match[1])}"
131+
else
132+
"#{field}: #{result[field]}"
79133

80-
storyRequest = (msg, storyId, cb) ->
81-
query_string = "/hierarchicalrequirement.js?query=(FormattedID = #{storyId})&fetch=true"
82-
rallyRequest msg, query_string, (json) ->
83-
if json && json.QueryResult.TotalResultCount > 0
84-
getLinkToItem msg, json.QueryResult.Results[0], "userstory"
85-
description = "No Description"
86-
prettifyDescription json.QueryResult.Results[0].Description, (output) ->
87-
description = output || description
88-
return_array = [
89-
"#{json.QueryResult.Results[0].FormattedID} - #{json.QueryResult.Results[0].Name}"
90-
"Owner - #{ if json.QueryResult.Results[0].Owner then json.QueryResult.Results[0].Owner._refObjectName else "No Owner" }"
91-
"Project - #{ if json.QueryResult.Results[0].Project then json.QueryResult.Results[0].Project._refObjectName else "No Project" }"
92-
"ScheduleState - #{json.QueryResult.Results[0].ScheduleState}"
93-
"#{description}"
94-
]
95-
cb return_array.join("\n")
96-
else
97-
cb "Aww snap, I couldn't find that story!"
134+
refObjectName = (result, field) ->
135+
if result[field] then result[field]._refObjectName else "No #{field}"
98136

99137
rallyRequest = (msg, query, cb) ->
100-
rally_url = 'https://rally1.rallydev.com/slm/webservice/' + api_version + query
101-
basicAuthRequest msg, rally_url, (json) ->
102-
cb json
138+
rally_url = 'https://rally1.rallydev.com/slm/webservice/' + api_version + query
139+
# logger.debug "rally_url = #{rally_url}"
140+
basicAuthRequest msg, rally_url, (json) ->
141+
# if json
142+
# logger.debug "json = #{JSON.stringify(json)}"
143+
cb json
103144

104145
basicAuthRequest = (msg, url, cb) ->
105-
auth = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');
106-
msg.http(url)
107-
.headers(Authorization: auth, Accept: 'application/json')
108-
.get() (err, res, body) ->
109-
json_body = null
110-
switch res.statusCode
111-
when 200 then json_body = JSON.parse(body)
112-
else json_body = null
113-
cb json_body
146+
auth = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');
147+
msg.http(url)
148+
.headers(Authorization: auth, Accept: 'application/json')
149+
.get() (err, res, body) ->
150+
json_body = null
151+
switch res.statusCode
152+
when 200 then json_body = JSON.parse(body)
153+
else json_body = null
154+
cb json_body
114155

115156
getLinkToItem = (msg, object, type) ->
116-
project = if object && object.Project then object.Project else null
117-
if project
118-
objectId = object.ObjectID
119-
jsPos = project._ref.lastIndexOf ".js"
120-
lastSlashPos = project._ref.lastIndexOf "/"
121-
projectId = project._ref[(lastSlashPos+1)..(jsPos-1)]
122-
msg.send "https://rally1.rallydev.com/slm/rally.sp#/#{projectId}/detail/#{type}/#{objectId}"
123-
else
124-
#do nothing
157+
project = if object && object.Project then object.Project else null
158+
if project
159+
objectId = object.ObjectID
160+
jsPos = project._ref.lastIndexOf '.js'
161+
lastSlashPos = project._ref.lastIndexOf '/'
162+
projectId = project._ref[(lastSlashPos+1)..(jsPos-1)]
163+
msg.send "https://rally1.rallydev.com/#/#{projectId}/detail/#{type}/#{objectId}"
164+
else
165+
#do nothing
166+
125167
prettifyDescription = (html_description, cb) ->
126-
child = exec "echo \"#{html_description}\" | lynx -dump -stdin", (error, stdout, stderr) ->
127-
return_text = html_description
128-
if !error
129-
return_text = stdout
130-
cb return_text
168+
child = exec "echo \"#{html_description}\" | lynx -dump -stdin", (error, stdout, stderr) ->
169+
return_text = html_description
170+
if !error
171+
return_text = stdout
172+
cb return_text
173+
174+
stripHtml = (html, cb) ->
175+
return_text = html.replace(/<style.+\/style>/g, '')
176+
return_text = return_text.replace(/<\/?.+?>/g, '').replace(/<br ?\/?>/g, "\n\n").replace(/&nbsp;/g, ' ').replace(/[ ]+/g, ' ').replace(/%22/g, '"').replace(/&amp;/g, '&')
177+
return_text = return_text.replace(/&gt;/g, '>').replace(/&lt;/g, '<')
178+
cb return_text

0 commit comments

Comments
 (0)