From 746831e814cfd5d4812c2a5cdc553473e50644af Mon Sep 17 00:00:00 2001 From: jpklein Date: Wed, 3 Sep 2014 23:34:42 -0400 Subject: [PATCH 01/86] Create settings template file in config directory --- .gitignore | 3 +++ scripts/config/default.config.php | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 .gitignore create mode 100644 scripts/config/default.config.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b7c7cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +scripts/config/* +!scripts/config/default.config.php + diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php new file mode 100644 index 0000000..e0dfcc3 --- /dev/null +++ b/scripts/config/default.config.php @@ -0,0 +1,10 @@ + Date: Thu, 4 Sep 2014 00:11:03 -0400 Subject: [PATCH 02/86] Consolidate settings vars in config template file - Remove separate .config and .secrets files - Change include statements to use newly-required config file --- scripts/config/default.config.php | 15 +++++++++++++++ scripts/gifme.php | 2 +- scripts/imageme.php | 16 ++++++++-------- scripts/include/rally.secrets.php | 4 ---- scripts/include/rallyme.config.php | 11 ----------- scripts/include/slack.config.php | 7 ------- scripts/include/slack.secrets.php | 4 ---- scripts/rallyme.php | 5 ++--- scripts/xkcd.php | 4 ++-- 9 files changed, 28 insertions(+), 40 deletions(-) delete mode 100644 scripts/include/rally.secrets.php delete mode 100644 scripts/include/rallyme.config.php delete mode 100644 scripts/include/slack.config.php delete mode 100644 scripts/include/slack.secrets.php diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index e0dfcc3..765962f 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -4,7 +4,22 @@ // Slack settings // //////////////////// +$config['slack']['incominghooktoken'] = "REPLACE ME"; +$config['slack']['outgoinghooktoken'] = "REPLACE ME"; + +$config['slack']['incominghook'] = "https://cim.slack.com/services/hooks/incoming-webhook?token="; + +$config['slack']['hook'] = $config['slack']['incominghook'].$config['slack']['incominghooktoken']; + //////////////////// // Rally settings // //////////////////// +$config['rally']['username'] = "REPLACE ME"; +$config['rally']['password'] = "REPLACE ME"; + +$config['rally']['botname'] = "rallybot"; +$config['rally']['boticon'] = "https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg"; + +$config['rally']['artifactquery'] = "https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(FormattedID%20=%20[[ID]])"; +$config['rally']['defectquery'] = "https://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(FormattedID%20=%20[[ID]])"; diff --git a/scripts/gifme.php b/scripts/gifme.php index 4dc4fc5..714f8a8 100644 --- a/scripts/gifme.php +++ b/scripts/gifme.php @@ -1,7 +1,7 @@ Text); $tries++; @@ -24,13 +24,13 @@ //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} $details = $imageresponse->responseDetails; $status = $imageresponse->responseStatus; - + if($status == 503 && $tries < $maxtries) { sleep(1); goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/ } - + print_r("Sorry @{$userlink}, no image for you! [{$details}:{$status}]\n"); //print_r($imageresponse); die; @@ -44,9 +44,9 @@ $ret = slack_incoming_hook_post($hook, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}"); - - - + + + function RunImageSearch($text) { $enc = urlencode($text); @@ -54,7 +54,7 @@ function RunImageSearch($text) $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc); $imageresponse = json_decode($imageSearchJson); - + return $imageresponse; } ?> diff --git a/scripts/include/rally.secrets.php b/scripts/include/rally.secrets.php deleted file mode 100644 index 1df5ee6..0000000 --- a/scripts/include/rally.secrets.php +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/scripts/include/rallyme.config.php b/scripts/include/rallyme.config.php deleted file mode 100644 index 9e0f85d..0000000 --- a/scripts/include/rallyme.config.php +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/scripts/include/slack.config.php b/scripts/include/slack.config.php deleted file mode 100644 index e14cd95..0000000 --- a/scripts/include/slack.config.php +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/scripts/include/slack.secrets.php b/scripts/include/slack.secrets.php deleted file mode 100644 index e243c8c..0000000 --- a/scripts/include/slack.secrets.php +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/scripts/rallyme.php b/scripts/rallyme.php index aca567c..9409def 100644 --- a/scripts/rallyme.php +++ b/scripts/rallyme.php @@ -2,12 +2,11 @@ require('include/slack.php'); require('include/curl.php'); require('include/rally.php'); -require('include/rallyme.config.php'); -require('include/slack.config.php'); +require('config/config.php'); $slackCommand = BuildSlashCommand($_REQUEST); $rallyFormattedId = strtoupper($slackCommand->Text); $result = HandleItem($slackCommand, $rallyFormattedId); -?> \ No newline at end of file +?> diff --git a/scripts/xkcd.php b/scripts/xkcd.php index 971667b..a36133d 100644 --- a/scripts/xkcd.php +++ b/scripts/xkcd.php @@ -1,7 +1,7 @@ ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook.\n{$ret}"); -?> \ No newline at end of file +?> From 42d8240b853e34d5849ba08f920a054761db1739 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 00:25:16 -0400 Subject: [PATCH 03/86] Add missing config-file require to memegenerator script --- scripts/meme.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/meme.php b/scripts/meme.php index 4c68a81..f378fe5 100644 --- a/scripts/meme.php +++ b/scripts/meme.php @@ -1,7 +1,8 @@ Date: Thu, 4 Sep 2014 00:44:06 -0400 Subject: [PATCH 04/86] Simplify configuration of incoming webhook --- scripts/config/default.config.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 765962f..e9ed345 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -4,12 +4,8 @@ // Slack settings // //////////////////// -$config['slack']['incominghooktoken'] = "REPLACE ME"; $config['slack']['outgoinghooktoken'] = "REPLACE ME"; - -$config['slack']['incominghook'] = "https://cim.slack.com/services/hooks/incoming-webhook?token="; - -$config['slack']['hook'] = $config['slack']['incominghook'].$config['slack']['incominghooktoken']; +$config['slack']['hook'] = "REPLACE ME"; //URL of an Incoming WebHook including https:// and token value //////////////////// // Rally settings // From b2899b0086fbd1b597fa320b35df331d93098f57 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 00:54:54 -0400 Subject: [PATCH 05/86] Remove define-like variables from config file --- scripts/config/default.config.php | 3 --- scripts/include/rally.php | 26 +++++++++++++------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index e9ed345..df12b2a 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -16,6 +16,3 @@ $config['rally']['botname'] = "rallybot"; $config['rally']['boticon'] = "https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg"; - -$config['rally']['artifactquery'] = "https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(FormattedID%20=%20[[ID]])"; -$config['rally']['defectquery'] = "https://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(FormattedID%20=%20[[ID]])"; diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 9713410..f7e6dd5 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -53,11 +53,11 @@ function HandleStory($id, $channel_name) $payload = GetRequirementPayload($ref); $result = postit($channel_name, $payload->text, $payload->attachments); - + if($result=='Invalid channel specified'){ die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - + if($result!="ok"){ print_r($result."\n"); print_r(json_encode($payload)); @@ -71,11 +71,11 @@ function postit($channel_name, $payload, $attachments){ global $config, $slackCommand; return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, + $config['slack']['hook'], + $config['rally']['botname'], + $slackCommand->ChannelName, + $config['rally']['boticon'], + $payload, $attachments); } @@ -169,7 +169,7 @@ function GetDefectPayload($ref) array_push($fields,$firstattachment); global $slackCommand; - + $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; @@ -265,7 +265,7 @@ function GetRequirementPayload($ref) if($blocked) array_push($fields, MakeField("blocked",$blockedreason,true)); - + array_push($fields, MakeField("description",$short_description,false)); if($firstattachment!=null) @@ -340,14 +340,14 @@ function BuildUserLink($username) function GetArtifactQueryUri($id) { - global $config; - return str_replace("[[ID]]", $id, $config['rally']['artifactquery']); + $artifactquery = "https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(FormattedID%20=%20[[ID]])"; + return str_replace("[[ID]]", $id, $artifactquery); } function GetDefectQueryUri($id) { - global $config; - return str_replace("[[ID]]", $id, $config['rally']['defectquery']); + $defectquery = "https://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(FormattedID%20=%20[[ID]])"; + return str_replace("[[ID]]", $id, $defectquery); } function FindDefect($id) From 42ecc51832bba9c7ae3e5c467b916118b904e3f3 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 4 Sep 2014 02:16:42 -0400 Subject: [PATCH 06/86] Update README.md with installation instructions --- README.md | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c01006e..411a0ba 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ slack-integration ================= -A place where I put some integration scripts for the popular Slack messaging platform. +Some integration scripts for the popular Slack messaging platform. +## Features -#Rally Bot - +### Rally Bot We use this to query our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack. E.g.: @@ -15,18 +15,39 @@ E.g.: Each of these returns a nicely formatted summary of the defect, story or task, including the owner/submitter, creation date, story title, project and description. For documents that have attachments, such as uploaded screenshots, the summary will include a link to the first available attachment. -#Image Bot / Gif Bot - +### Image Bot / Gif Bot This one is simple. It uses the google image search JSON API to query for images that match a keyword. It is configured to use the Safe Search feature, since we use this tool in the workplace. Being able to add some levity to our work chats with amusing images from the internet makes life more fun. E.g. /imageme kittens /gifme roflcopter - -#XKCD Bot - +### XKCD Bot As geeks, we are well acquainted with the internet comic XKCD. This bot allows you to post an XKCD comic to the room, including the ALT text, which will be displayed beneath the image. E.g. /xkcd 100 will return the "family circus" episode of XKCD. + +## Installation + +1. Clone this repository to your server + +2. Create your config file from the default template: + + ``` + cd scripts/config + cp default.config.php config.php + ``` + +3. Create a new incoming webhook in Slack + + **Note**: Leave the channel to post to set to "#general", our scripts use channel-overrides to respond wherever the user issues a slash command. + +4. Edit your config file with the incoming webhook's unique webhook URL + +5. Add a slash command to Slack for each feature (for example: "When a user enters /rallyme, POST to http://example.com/rallyme.php") + + **Note**: The scripts will respond to requests over POST or GET. + +6. If you are implementing Rally Bot, add your credentials to the config file + From 3007dbd5acc64d4e6a7bbf63dab15eef9b25d502 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 02:39:55 -0400 Subject: [PATCH 07/86] Fix php tags, trailing whitespace, and terminal newlines --- scripts/gifme.php | 3 +-- scripts/imageme.php | 17 ++++++++--------- scripts/include/curl.php | 3 +-- scripts/include/googleimage.php | 10 ++++------ scripts/include/log.php | 5 ++--- scripts/include/memegenerator.php | 9 ++++----- scripts/include/rally.php | 22 ++++++++++------------ scripts/include/slack.php | 9 ++++----- scripts/meme.php | 3 +-- scripts/rallyme.php | 3 +-- scripts/xkcd.php | 3 +-- 11 files changed, 37 insertions(+), 50 deletions(-) diff --git a/scripts/gifme.php b/scripts/gifme.php index 4dc4fc5..702d52e 100644 --- a/scripts/gifme.php +++ b/scripts/gifme.php @@ -1,4 +1,4 @@ -ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /gifme.\n{$ret}"); -?> diff --git a/scripts/imageme.php b/scripts/imageme.php index 0af8dce..39e04a8 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -1,4 +1,4 @@ -Text); $tries++; @@ -24,13 +24,13 @@ //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} $details = $imageresponse->responseDetails; $status = $imageresponse->responseStatus; - + if($status == 503 && $tries < $maxtries) { sleep(1); goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/ } - + print_r("Sorry @{$userlink}, no image for you! [{$details}:{$status}]\n"); //print_r($imageresponse); die; @@ -44,9 +44,9 @@ $ret = slack_incoming_hook_post($hook, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}"); - - - + + + function RunImageSearch($text) { $enc = urlencode($text); @@ -54,7 +54,6 @@ function RunImageSearch($text) $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc); $imageresponse = json_decode($imageSearchJson); - + return $imageresponse; } -?> diff --git a/scripts/include/curl.php b/scripts/include/curl.php index d3ff09c..5a86e51 100644 --- a/scripts/include/curl.php +++ b/scripts/include/curl.php @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/scripts/include/googleimage.php b/scripts/include/googleimage.php index 67b4cec..1df0a54 100644 --- a/scripts/include/googleimage.php +++ b/scripts/include/googleimage.php @@ -1,4 +1,4 @@ -Size = $size; $cmd->Query = $query; $cmd->Count = $maxresults; $cmd->Safe = $safe; $cmd->FileType = $filetype; - + return $cmd; } function GetImageSearchResponse($cmd) { - + $googleImageSearch = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe={$cmd->Safe}&as_filetype={$cmd->FileType}&rsz={$cmd->Count}&imgsz={$cmd->Size}&q={$cmd->Query}"; $result = CallAPI($googleImageSearch); @@ -35,5 +35,3 @@ function GetRandomResultFromResponse($n, $result) $item = rand(0,$count-1); return $resultArray[$item]; } - -?> diff --git a/scripts/include/log.php b/scripts/include/log.php index 4de5c15..b8fee95 100644 --- a/scripts/include/log.php +++ b/scripts/include/log.php @@ -1,11 +1,10 @@ - diff --git a/scripts/include/memegenerator.php b/scripts/include/memegenerator.php index e994146..d39b09b 100644 --- a/scripts/include/memegenerator.php +++ b/scripts/include/memegenerator.php @@ -1,4 +1,4 @@ - diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 9713410..cdd9de9 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -1,4 +1,4 @@ -text, $payload->attachments); - + if($result=='Invalid channel specified'){ die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - + if($result!="ok"){ print_r($result."\n"); print_r(json_encode($payload)); @@ -71,11 +71,11 @@ function postit($channel_name, $payload, $attachments){ global $config, $slackCommand; return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, + $config['slack']['hook'], + $config['rally']['botname'], + $slackCommand->ChannelName, + $config['rally']['boticon'], + $payload, $attachments); } @@ -169,7 +169,7 @@ function GetDefectPayload($ref) array_push($fields,$firstattachment); global $slackCommand; - + $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; @@ -265,7 +265,7 @@ function GetRequirementPayload($ref) if($blocked) array_push($fields, MakeField("blocked",$blockedreason,true)); - + array_push($fields, MakeField("description",$short_description,false)); if($firstattachment!=null) @@ -397,5 +397,3 @@ function TruncateText($text, $len) return substr($text,0,$len)."...[MORE]"; } - -?> diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..1deb192 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -1,4 +1,4 @@ - $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -58,7 +58,7 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, @@ -105,4 +105,3 @@ function MakeAttachment($pretext, $text, $color, $fields, $fallback){ return $obj; } -?> diff --git a/scripts/meme.php b/scripts/meme.php index 4c68a81..988d50d 100644 --- a/scripts/meme.php +++ b/scripts/meme.php @@ -1,4 +1,4 @@ - diff --git a/scripts/rallyme.php b/scripts/rallyme.php index aca567c..f3047ff 100644 --- a/scripts/rallyme.php +++ b/scripts/rallyme.php @@ -1,4 +1,4 @@ -Text); $result = HandleItem($slackCommand, $rallyFormattedId); -?> \ No newline at end of file diff --git a/scripts/xkcd.php b/scripts/xkcd.php index 971667b..45a3c9d 100644 --- a/scripts/xkcd.php +++ b/scripts/xkcd.php @@ -1,4 +1,4 @@ -ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook.\n{$ret}"); -?> \ No newline at end of file From 69b2a53c9d2d89f28df330cb0609aa190ed5e58a Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 02:47:59 -0400 Subject: [PATCH 08/86] Convert indentation to tabs --- scripts/imageme.php | 10 +++++----- scripts/include/curl.php | 30 +++++++++++++++--------------- scripts/include/googleimage.php | 30 +++++++++++++++--------------- scripts/include/log.php | 10 +++++----- scripts/include/memegenerator.php | 8 ++++---- scripts/include/rally.php | 10 +++++----- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/scripts/imageme.php b/scripts/imageme.php index 39e04a8..c1b5d22 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -27,8 +27,8 @@ if($status == 503 && $tries < $maxtries) { - sleep(1); - goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/ + sleep(1); + goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/ } print_r("Sorry @{$userlink}, no image for you! [{$details}:{$status}]\n"); @@ -51,9 +51,9 @@ function RunImageSearch($text) { $enc = urlencode($text); - $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc); + $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc); - $imageresponse = json_decode($imageSearchJson); + $imageresponse = json_decode($imageSearchJson); - return $imageresponse; + return $imageresponse; } diff --git a/scripts/include/curl.php b/scripts/include/curl.php index 5a86e51..3022c4a 100644 --- a/scripts/include/curl.php +++ b/scripts/include/curl.php @@ -2,29 +2,29 @@ //HTTP Utility Methods function get_url_contents($url) { - $crl = curl_init($url); + $crl = curl_init($url); - curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)'); - curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)'); + curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5); - $response = curl_exec($crl); - curl_close($crl); - return $response; + $response = curl_exec($crl); + curl_close($crl); + return $response; } function get_url_contents_with_basicauth($url, $username, $password) { - $crl = curl_init(); + $crl = curl_init(); - curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)'); - curl_setopt($crl, CURLOPT_URL, $url); + curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)'); + curl_setopt($crl, CURLOPT_URL, $url); curl_setopt($crl, CURLOPT_USERPWD, "{$username}:{$password}"); - curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5); - $ret = curl_exec($crl); - curl_close($crl); - return $ret; + $ret = curl_exec($crl); + curl_close($crl); + return $ret; } function curl_post($uri, $data) diff --git a/scripts/include/googleimage.php b/scripts/include/googleimage.php index 1df0a54..138d810 100644 --- a/scripts/include/googleimage.php +++ b/scripts/include/googleimage.php @@ -8,30 +8,30 @@ function BuildSearchCommand($query, $size, $maxresults, $safe, $filetype) { - $cmd = new stdClass(); - $cmd->Size = $size; - $cmd->Query = $query; - $cmd->Count = $maxresults; - $cmd->Safe = $safe; - $cmd->FileType = $filetype; - - return $cmd; + $cmd = new stdClass(); + $cmd->Size = $size; + $cmd->Query = $query; + $cmd->Count = $maxresults; + $cmd->Safe = $safe; + $cmd->FileType = $filetype; + + return $cmd; } function GetImageSearchResponse($cmd) { - $googleImageSearch = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe={$cmd->Safe}&as_filetype={$cmd->FileType}&rsz={$cmd->Count}&imgsz={$cmd->Size}&q={$cmd->Query}"; + $googleImageSearch = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe={$cmd->Safe}&as_filetype={$cmd->FileType}&rsz={$cmd->Count}&imgsz={$cmd->Size}&q={$cmd->Query}"; - $result = CallAPI($googleImageSearch); + $result = CallAPI($googleImageSearch); - return $result; + return $result; } function GetRandomResultFromResponse($n, $result) { - $resultArray = $result->responseData->results; - $count = count($resultArray); - $item = rand(0,$count-1); - return $resultArray[$item]; + $resultArray = $result->responseData->results; + $count = count($resultArray); + $item = rand(0,$count-1); + return $resultArray[$item]; } diff --git a/scripts/include/log.php b/scripts/include/log.php index b8fee95..ac5a9f7 100644 --- a/scripts/include/log.php +++ b/scripts/include/log.php @@ -1,10 +1,10 @@ text, $payload->attachments); if($result=='Invalid channel specified'){ - die("Sorry, the rallyme command can't post messages to your private chat.\n"); + die("Sorry, the rallyme command can't post messages to your private chat.\n"); } if($result!="ok"){ @@ -159,8 +159,8 @@ function GetDefectPayload($ref) MakeField("priority",$priority,true), MakeField("severity",$severity,true), - MakeField("frequency",$frequency,true), - MakeField("found in",$foundinbuild,true), + MakeField("frequency",$frequency,true), + MakeField("found in",$foundinbuild,true), MakeField("description",$short_description,false) ); @@ -334,8 +334,8 @@ function FindRequirement($id) function BuildUserLink($username) { - $userlink = ""; - return $userlink; + $userlink = ""; + return $userlink; } function GetArtifactQueryUri($id) From 6f4993023a316f208334268c42a884fab4bbd294 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 02:59:27 -0400 Subject: [PATCH 09/86] Standardize linespacing in/between function declarations --- scripts/imageme.php | 3 --- scripts/include/curl.php | 6 ++++-- scripts/include/googleimage.php | 1 - scripts/include/log.php | 1 + scripts/include/memegenerator.php | 2 +- scripts/include/rally.php | 14 ++------------ scripts/include/slack.php | 16 ++++++---------- scripts/meme.php | 1 - 8 files changed, 14 insertions(+), 30 deletions(-) diff --git a/scripts/imageme.php b/scripts/imageme.php index c1b5d22..ae16e7b 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -14,7 +14,6 @@ $maxtries = 2; $tries = 0; - startover: $imageresponse = RunImageSearch($command->Text); @@ -45,8 +44,6 @@ if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}"); - - function RunImageSearch($text) { $enc = urlencode($text); diff --git a/scripts/include/curl.php b/scripts/include/curl.php index 3022c4a..1b1db52 100644 --- a/scripts/include/curl.php +++ b/scripts/include/curl.php @@ -1,7 +1,8 @@ _refObjectName; - $ProjectFull = CallAPI($projecturi); $projectid = $ProjectFull->Project->ObjectID; $storyid = $requirement->ObjectID; @@ -245,8 +241,6 @@ function GetRequirementPayload($ref) $dovegray = "#CEC7B8"; - - $fields = array( MakeField("link",$linktext,false), MakeField("parent",$parent,false), @@ -271,7 +265,6 @@ function GetRequirementPayload($ref) if($firstattachment!=null) array_push($fields,$firstattachment); - global $slackCommand; $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok {$userlink}, here's the story you requested."; @@ -284,7 +277,6 @@ function GetRequirementPayload($ref) return $obj; } - function MakeField($title, $value, $short=false) { $attachmentfield = array( @@ -310,7 +302,6 @@ function CallAPI($uri) return $object; } - function GetProjectID($projectref) { $ProjectFull = CallAPI($projectref); @@ -374,7 +365,6 @@ function NotFound($id) print_r("Sorry {$userlink}, I couldn't find {$id}");die; } - function GetFirstObjectFromSearchResult($objectName, $result) { foreach ($result->QueryResult->Results as $result) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 1deb192..7966667 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,8 +29,8 @@ function BuildSlashCommand($request) return $cmd; } - -function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ +function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload) +{ $data = array( "text" => $payload, @@ -53,9 +53,8 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } - - -function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ +function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments) +{ $data = array( "text" => $payload, @@ -69,12 +68,9 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, return curl_post($uri, $data_string); } - - /* slack attachment format -{ "fallback": "Required text summary of the attachment that is shown by clients that understand attachments but choose not to show them.", "text": "Optional text that should appear within the attachment", @@ -90,9 +86,9 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, "short": false // Optional flag indicating whether the `value` is short enough to be displayed side-by-side with other values } ] -} */ -function MakeAttachment($pretext, $text, $color, $fields, $fallback){ +function MakeAttachment($pretext, $text, $color, $fields, $fallback) +{ $obj = new stdClass; $obj->fallback = $fallback; diff --git a/scripts/meme.php b/scripts/meme.php index 988d50d..b217f49 100644 --- a/scripts/meme.php +++ b/scripts/meme.php @@ -17,7 +17,6 @@ //meme generator API $cmd = BuildSlashCommand($_REQUEST); - $payload = json_encode($cmd); mylog('received.txt',$payload); From 868fdc21806ca9891a2bc45d8f8911953068a769 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 03:34:11 -0400 Subject: [PATCH 10/86] Reformat to modified PEAR coding standards --- scripts/gifme.php | 8 +- scripts/imageme.php | 11 +-- scripts/include/curl.php | 4 +- scripts/include/googleimage.php | 2 +- scripts/include/log.php | 8 +- scripts/include/rally.php | 167 +++++++++++++++----------------- scripts/include/slack.php | 60 ++++++------ scripts/meme.php | 8 +- scripts/xkcd.php | 2 +- 9 files changed, 129 insertions(+), 141 deletions(-) diff --git a/scripts/gifme.php b/scripts/gifme.php index 702d52e..eb19f93 100644 --- a/scripts/gifme.php +++ b/scripts/gifme.php @@ -13,13 +13,13 @@ $enc = urlencode($command->Text); -$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&as_filetype=gif&rsz=8&imgsz=medium&q=animated+'.$enc); +$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&as_filetype=gif&rsz=8&imgsz=medium&q=animated+' . $enc); $imageresponse = json_decode($imageSearchJson); $userlink = "UserName}|{$command->UserName}>"; -if($imageresponse->responseData == null){ +if ($imageresponse->responseData == null) { //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} $details = $imageresponse->responseDetails; $status = $imageresponse->responseStatus; @@ -29,11 +29,11 @@ die; } -$whichImage = rand(0,7); +$whichImage = rand(0, 7); $returnedimageurl = $imageresponse->responseData->results[$whichImage]->url; $payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}"; $ret = slack_incoming_hook_post($hook, "gifbot", $command->ChannelName, $iconurl, $emoji, $payload); -if($ret!="ok") +if ($ret != "ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /gifme.\n{$ret}"); diff --git a/scripts/imageme.php b/scripts/imageme.php index ae16e7b..2807637 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -19,13 +19,12 @@ $imageresponse = RunImageSearch($command->Text); $tries++; -if($imageresponse->responseData == null){ +if ($imageresponse->responseData == null) { //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} $details = $imageresponse->responseDetails; $status = $imageresponse->responseStatus; - if($status == 503 && $tries < $maxtries) - { + if ($status == 503 && $tries < $maxtries) { sleep(1); goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/ } @@ -35,20 +34,20 @@ die; } -$whichImage = rand(0,7); +$whichImage = rand(0, 7); $returnedimageurl = $imageresponse->responseData->results[$whichImage]->url; $payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}"; $ret = slack_incoming_hook_post($hook, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload); -if($ret!="ok") +if ($ret != "ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}"); function RunImageSearch($text) { $enc = urlencode($text); - $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc); + $imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q=' . $enc); $imageresponse = json_decode($imageSearchJson); diff --git a/scripts/include/curl.php b/scripts/include/curl.php index 1b1db52..ab6050c 100644 --- a/scripts/include/curl.php +++ b/scripts/include/curl.php @@ -35,7 +35,9 @@ function curl_post($uri, $data) curl_setopt($crl, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($crl, CURLOPT_POSTFIELDS, $data); curl_setopt($crl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($crl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data))); + curl_setopt($crl, CURLOPT_HTTPHEADER, array( + 'Content-Length: ' . strlen($data) + )); $response = curl_exec($crl); curl_close($crl); diff --git a/scripts/include/googleimage.php b/scripts/include/googleimage.php index dd61565..230f577 100644 --- a/scripts/include/googleimage.php +++ b/scripts/include/googleimage.php @@ -31,6 +31,6 @@ function GetRandomResultFromResponse($n, $result) { $resultArray = $result->responseData->results; $count = count($resultArray); - $item = rand(0,$count-1); + $item = rand(0, $count - 1); return $resultArray[$item]; } diff --git a/scripts/include/log.php b/scripts/include/log.php index 7343794..54c617b 100644 --- a/scripts/include/log.php +++ b/scripts/include/log.php @@ -2,10 +2,10 @@ function mylog($logfile, $payload) { - $fh = fopen($logfile,"ra+"); - fwrite($fh,time()."\n"); - fwrite($fh,$payload); - fwrite($fh,"\n____________________\n"); + $fh = fopen($logfile, "ra+"); + fwrite($fh, time() . "\n"); + fwrite($fh, $payload); + fwrite($fh, "\n____________________\n"); fclose($fh); } diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 6453b1b..a57d354 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,23 +3,23 @@ function HandleItem($slackCommand, $rallyFormattedId) { - $rallyItemType = substr($rallyFormattedId,0,2); - - switch($rallyItemType){ - - case "DE": - return HandleDefect($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - case "US": - case "TA": - return HandleStory($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - default: - print_r("Sorry, I don't know what kind of rally object {$rallyFormattedId} is. If you need rallyme to work with these, buy a :beer:. I hear he likes IPAs."); - die; - break; + $rallyItemType = substr($rallyFormattedId, 0, 2); + + switch ($rallyItemType) { + + case "DE": + return HandleDefect($rallyFormattedId, $slackCommand->ChannelName); + die; + break; + case "US": + case "TA": + return HandleStory($rallyFormattedId, $slackCommand->ChannelName); + die; + break; + default: + print_r("Sorry, I don't know what kind of rally object {$rallyFormattedId} is. If you need rallyme to work with these, buy a :beer:. I hear he likes IPAs."); + die; + break; } } @@ -31,12 +31,12 @@ function HandleDefect($id, $channel_name) $result = postit($channel_name, $payload->text, $payload->attachments); - if($result=='Invalid channel specified'){ + if ($result == 'Invalid channel specified') { die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - if($result!="ok"){ - print_r($result."\n"); + if ($result != "ok") { + print_r($result . "\n"); print_r(json_encode($payload)); print_r("\n"); die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); @@ -52,12 +52,12 @@ function HandleStory($id, $channel_name) $result = postit($channel_name, $payload->text, $payload->attachments); - if($result=='Invalid channel specified'){ + if ($result == 'Invalid channel specified') { die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - if($result!="ok"){ - print_r($result."\n"); + if ($result != "ok") { + print_r($result . "\n"); print_r(json_encode($payload)); print_r("\n"); die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); @@ -69,13 +69,7 @@ function postit($channel_name, $payload, $attachments) { global $config, $slackCommand; - return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, - $attachments); + return slack_incoming_hook_post_with_attachments($config['slack']['hook'], $config['rally']['botname'], $slackCommand->ChannelName, $config['rally']['boticon'], $payload, $attachments); } function GetRallyAttachmentLink($attachmentRef) @@ -94,7 +88,7 @@ function GetRallyAttachmentLink($attachmentRef) function GetDefectPayload($ref) { - global $show,$requesting_user_name; + global $show, $requesting_user_name; $object = CallAPI($ref); @@ -125,10 +119,9 @@ function GetDefectPayload($ref) $attachmentcount = $defect->Attachments->Count; $firstattachment = null; - if($attachmentcount>0) - { + if ($attachmentcount > 0) { $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); + $firstattachment = MakeField("attachment", $linktxt, false); } $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; @@ -138,32 +131,32 @@ function GetDefectPayload($ref) $color = "bad"; - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); $short_description = TruncateText($clean_description, 300); $fields = array( - MakeField("link",$linktext,false), + MakeField("link", $linktext, false), - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), - MakeField("project",$projectName,true), - MakeField("created",$created,true), + MakeField("project", $projectName, true), + MakeField("created", $created, true), - MakeField("submitter",$submitter,true), - MakeField("state",$state,true), + MakeField("submitter", $submitter, true), + MakeField("state", $state, true), - MakeField("priority",$priority,true), - MakeField("severity",$severity,true), + MakeField("priority", $priority, true), + MakeField("severity", $severity, true), - MakeField("frequency",$frequency,true), - MakeField("found in",$foundinbuild,true), + MakeField("frequency", $frequency, true), + MakeField("found in", $foundinbuild, true), - MakeField("description",$short_description,false) + MakeField("description", $short_description, false) ); - if($firstattachment!=null) - array_push($fields,$firstattachment); + if ($firstattachment != null) + array_push($fields, $firstattachment); global $slackCommand; @@ -182,16 +175,11 @@ function GetRequirementPayload($ref) $requirement = null; - if($object->HierarchicalRequirement) - { + if ($object->HierarchicalRequirement) { $requirement = $object->HierarchicalRequirement; - } - elseif($object->Task) - { + } elseif ($object->Task) { $requirement = $object->Task; - } - else - { + } else { $class = get_class($object); global $slackCommand; $userlink = BuildUserLink($slackCommand->UserName); @@ -222,17 +210,16 @@ function GetRequirementPayload($ref) $attachmentcount = $requirement->Attachments->Count; $firstattachment = null; - if($attachmentcount>0) - { + if ($attachmentcount > 0) { $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); + $firstattachment = MakeField("attachment", $linktxt, false); } $parent = null; - if($hasparent) + if ($hasparent) $parent = $requirement->Parent->_refObjectName; - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); $short_description = TruncateText($clean_description, 300); $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; @@ -242,28 +229,29 @@ function GetRequirementPayload($ref) $dovegray = "#CEC7B8"; $fields = array( - MakeField("link",$linktext,false), - MakeField("parent",$parent,false), + MakeField("link", $linktext, false), + MakeField("parent", $parent, false), - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), - MakeField("project",$projectName,true), - MakeField("created",$created,true), + MakeField("project", $projectName, true), + MakeField("created", $created, true), - MakeField("estimate",$estimate,true), - MakeField("state",$state,true)); + MakeField("estimate", $estimate, true), + MakeField("state", $state, true) + ); - if($childcount>0) - array_push($fields,MakeField("children",$childcount,true)); + if ($childcount > 0) + array_push($fields, MakeField("children", $childcount, true)); - if($blocked) - array_push($fields, MakeField("blocked",$blockedreason,true)); + if ($blocked) + array_push($fields, MakeField("blocked", $blockedreason, true)); - array_push($fields, MakeField("description",$short_description,false)); + array_push($fields, MakeField("description", $short_description, false)); - if($firstattachment!=null) - array_push($fields,$firstattachment); + if ($firstattachment != null) + array_push($fields, $firstattachment); global $slackCommand; $userlink = BuildUserLink($slackCommand->UserName); @@ -272,17 +260,18 @@ function GetRequirementPayload($ref) $obj = new stdClass; $obj->text = ""; $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); -// print_r(json_encode($obj));die; + // print_r(json_encode($obj));die; return $obj; } -function MakeField($title, $value, $short=false) +function MakeField($title, $value, $short = false) { $attachmentfield = array( "title" => $title, "value" => $value, - "short" => $short); + "short" => $short + ); return $attachmentfield; } @@ -314,10 +303,10 @@ function FindRequirement($id) $query = GetArtifactQueryUri($id); $searchresult = CallAPI($query); -// print_r($searchresult);die; + // print_r($searchresult);die; $count = GetCount($searchresult); - if($count == 0) + if ($count == 0) NotFound($id); return GetFirstObjectFromSearchResult("HierarchicalRequirement", $searchresult); @@ -347,7 +336,7 @@ function FindDefect($id) $searchresult = CallAPI($query); $count = GetCount($searchresult); - if($count == 0) + if ($count == 0) NotFound($id); return GetFirstObjectFromSearchResult("Defect", $searchresult); @@ -362,14 +351,14 @@ function NotFound($id) { global $slackCommand; $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I couldn't find {$id}");die; + print_r("Sorry {$userlink}, I couldn't find {$id}"); + die; } function GetFirstObjectFromSearchResult($objectName, $result) { - foreach ($result->QueryResult->Results as $result) - { - if($result->_type == $objectName) + foreach ($result->QueryResult->Results as $result) { + if ($result->_type == $objectName) return $result->_ref; } global $slackCommand; @@ -382,8 +371,8 @@ function GetFirstObjectFromSearchResult($objectName, $result) function TruncateText($text, $len) { - if(strlen($text) <= $len) + if (strlen($text) <= $len) return $text; - return substr($text,0,$len)."...[MORE]"; + return substr($text, 0, $len) . "...[MORE]"; } diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 7966667..0c60740 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -34,22 +34,19 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload $data = array( "text" => $payload, - "channel" => "#".$channel, - "username"=>$user - ); + "channel" => "#" . $channel, + "username" => $user + ); - if($icon!=null) - { + if ($icon != null) { $data['icon_url'] = $icon; - } - elseif($emoji!=null) - { + } elseif ($emoji != null) { $data['icon_emoji'] = $emoji; } - $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); + $data_string = "payload=" . json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); - mylog('sent.txt',$data_string); + mylog('sent.txt', $data_string); return curl_post($uri, $data_string); } @@ -58,34 +55,37 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $data = array( "text" => $payload, - "channel" => "#".$channel, - "username"=>$user, - "icon_url"=>$icon, - "attachments"=>array($attachments)); - - $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); - mylog('sent.txt',$data_string); + "channel" => "#" . $channel, + "username" => $user, + "icon_url" => $icon, + "attachments" => array( + $attachments + ) + ); + + $data_string = "payload=" . json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); + mylog('sent.txt', $data_string); return curl_post($uri, $data_string); } /* slack attachment format - "fallback": "Required text summary of the attachment that is shown by clients that understand attachments but choose not to show them.", +"fallback": "Required text summary of the attachment that is shown by clients that understand attachments but choose not to show them.", - "text": "Optional text that should appear within the attachment", - "pretext": "Optional text that should appear above the formatted data", +"text": "Optional text that should appear within the attachment", +"pretext": "Optional text that should appear above the formatted data", - "color": "#36a64f", // Can either be one of 'good', 'warning', 'danger', or any hex color code +"color": "#36a64f", // Can either be one of 'good', 'warning', 'danger', or any hex color code - // Fields are displayed in a table on the message - "fields": [ - { - "title": "Required Field Title", // The title may not contain markup and will be escaped for you - "value": "Text value of the field. May contain standard message markup and must be escaped as normal. May be multi-line.", - "short": false // Optional flag indicating whether the `value` is short enough to be displayed side-by-side with other values - } - ] +// Fields are displayed in a table on the message +"fields": [ +{ +"title": "Required Field Title", // The title may not contain markup and will be escaped for you +"value": "Text value of the field. May contain standard message markup and must be escaped as normal. May be multi-line.", +"short": false // Optional flag indicating whether the `value` is short enough to be displayed side-by-side with other values +} +] */ function MakeAttachment($pretext, $text, $color, $fields, $fallback) { @@ -96,7 +96,7 @@ function MakeAttachment($pretext, $text, $color, $fields, $fallback) $obj->pretext = $pretext; $obj->color = $color; - if(sizeof($fields)>0) + if (sizeof($fields) > 0) $obj->fields = $fields; return $obj; diff --git a/scripts/meme.php b/scripts/meme.php index b217f49..a8ce9f6 100644 --- a/scripts/meme.php +++ b/scripts/meme.php @@ -19,7 +19,7 @@ $payload = json_encode($cmd); -mylog('received.txt',$payload); +mylog('received.txt', $payload); $cmdText = $cmd->Text; $memetext = str_replace("memebot ", "", $cmdText); @@ -31,18 +31,16 @@ $bottom = urlencode($parts[2]); $meme = CreateNewMeme($gen, $top, $bottom); -mylog('sent.txt',$meme); +mylog('sent.txt', $meme); $response = slack_incoming_hook_post($config['slack']['hook'], $cmd->UserName, $cmd->ChannelName, null, ":bow:", $meme); -mylog('sent.txt',$response); +mylog('sent.txt', $response); //str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] ) //print_r($cmd->Text);die; - - //$out = new stdClass(); //$out->text = $meme; diff --git a/scripts/xkcd.php b/scripts/xkcd.php index 45a3c9d..f1780f2 100644 --- a/scripts/xkcd.php +++ b/scripts/xkcd.php @@ -25,5 +25,5 @@ $payload = "{$image}\n\n"; $ret = slack_incoming_hook_post($hook, "xkcdbot", $command->ChannelName, $iconurl, $emoji, $payload); -if($ret!="ok") +if ($ret != "ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook.\n{$ret}"); From 79252c1db8ad79d4d73baab91f2f54c32e3f9176 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 03:54:41 -0400 Subject: [PATCH 11/86] Revert overzealous comment reformatting --- scripts/include/slack.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 0c60740..009a0f1 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -71,21 +71,25 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, /* slack attachment format -"fallback": "Required text summary of the attachment that is shown by clients that understand attachments but choose not to show them.", +{ + "fallback": "Required text summary of the attachment that is shown by clients that understand attachments but choose not to show them.", -"text": "Optional text that should appear within the attachment", -"pretext": "Optional text that should appear above the formatted data", + "text": "Optional text that should appear within the attachment", + "pretext": "Optional text that should appear above the formatted data", -"color": "#36a64f", // Can either be one of 'good', 'warning', 'danger', or any hex color code + "color": "#36a64f", // Can either be one of 'good', 'warning', 'danger', or any hex color code -// Fields are displayed in a table on the message -"fields": [ -{ -"title": "Required Field Title", // The title may not contain markup and will be escaped for you -"value": "Text value of the field. May contain standard message markup and must be escaped as normal. May be multi-line.", -"short": false // Optional flag indicating whether the `value` is short enough to be displayed side-by-side with other values + // Fields are displayed in a table on the message + "fields": [ + { + "title": "Required Field Title", // The title may not contain markup and will be escaped for you + + "value": "Text value of the field. May contain standard message markup and must be escaped as normal. May be multi-line.", + + "short": false // Optional flag indicating whether the `value` is short enough to be displayed side-by-side with other values + } + ] } -] */ function MakeAttachment($pretext, $text, $color, $fields, $fallback) { From 13c1ec79a113274656b045d82854638bf15dfa57 Mon Sep 17 00:00:00 2001 From: jpklein Date: Thu, 4 Sep 2014 04:15:49 -0400 Subject: [PATCH 12/86] Parameterize userlink creation --- scripts/config/default.config.php | 2 ++ scripts/gifme.php | 2 +- scripts/imageme.php | 2 +- scripts/include/rally.php | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index df12b2a..90dafc0 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -7,6 +7,8 @@ $config['slack']['outgoinghooktoken'] = "REPLACE ME"; $config['slack']['hook'] = "REPLACE ME"; //URL of an Incoming WebHook including https:// and token value +$config['slack']['subdomain'] = "REPLACE ME"; //subdomain used to identify your team's instance, like "cim" + //////////////////// // Rally settings // //////////////////// diff --git a/scripts/gifme.php b/scripts/gifme.php index 714f8a8..afd2a47 100644 --- a/scripts/gifme.php +++ b/scripts/gifme.php @@ -17,7 +17,7 @@ $imageresponse = json_decode($imageSearchJson); -$userlink = "UserName}|{$command->UserName}>"; +$userlink = 'UserName . '|' . $command->UserName . '>'; if($imageresponse->responseData == null){ //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} diff --git a/scripts/imageme.php b/scripts/imageme.php index 2bf5798..a990b4c 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -10,7 +10,7 @@ //use one or the other of $emoji or $iconurl $emoji = ":camera:"; $iconurl = null; -$userlink = "UserName}|{$command->UserName}>"; +$userlink = 'UserName . '|' . $command->UserName . '>'; $maxtries = 2; $tries = 0; diff --git a/scripts/include/rally.php b/scripts/include/rally.php index f7e6dd5..66a0820 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -334,7 +334,8 @@ function FindRequirement($id) function BuildUserLink($username) { - $userlink = ""; + global $config; + $userlink = ''; return $userlink; } From 88aa7094553a61e8080cfe28f002395b2aceec63 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 4 Sep 2014 14:43:05 -0400 Subject: [PATCH 13/86] WIP! Create cron-job script file --- scripts/rallycron.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 scripts/rallycron.php diff --git a/scripts/rallycron.php b/scripts/rallycron.php new file mode 100644 index 0000000..1e93f2c --- /dev/null +++ b/scripts/rallycron.php @@ -0,0 +1,13 @@ + Date: Fri, 5 Sep 2014 04:04:53 -0400 Subject: [PATCH 14/86] Fix includes and define pseudoconstants in cron script --- scripts/include/rally.php | 20 ++++++++++---------- scripts/include/rally.secrets.php | 1 + scripts/rallycron.php | 8 +++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 9713410..1eead35 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -1,6 +1,6 @@ text, $payload->attachments); - + if($result=='Invalid channel specified'){ die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - + if($result!="ok"){ print_r($result."\n"); print_r(json_encode($payload)); @@ -71,11 +71,11 @@ function postit($channel_name, $payload, $attachments){ global $config, $slackCommand; return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, + $config['slack']['hook'], + $config['rally']['botname'], + $slackCommand->ChannelName, + $config['rally']['boticon'], + $payload, $attachments); } @@ -169,7 +169,7 @@ function GetDefectPayload($ref) array_push($fields,$firstattachment); global $slackCommand; - + $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; @@ -265,7 +265,7 @@ function GetRequirementPayload($ref) if($blocked) array_push($fields, MakeField("blocked",$blockedreason,true)); - + array_push($fields, MakeField("description",$short_description,false)); if($firstattachment!=null) diff --git a/scripts/include/rally.secrets.php b/scripts/include/rally.secrets.php index 1df5ee6..c7049d9 100644 --- a/scripts/include/rally.secrets.php +++ b/scripts/include/rally.secrets.php @@ -1,4 +1,5 @@ diff --git a/scripts/rallycron.php b/scripts/rallycron.php index 1e93f2c..a186074 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -2,12 +2,14 @@ require('include/curl.php'); require('include/slack.php'); require('include/rally.php'); -require('config/config.php'); +require('include/rallyme.config.php'); +require('include/slack.config.php'); date_default_timezone_set('UTC'); -$config['cron_interval'] = 61; //seconds between cron runs; pad for script run time and latency -$since = date($config['rally']['date_format'], time() - $config['cron_interval']); +$CRON_INTERVAL = 61; //seconds between cron runs; pad for script run time and latency + +$since = date($RALLY_TIMESTAMP_FORMAT, time() - $CRON_INTERVAL); $items = FetchLatestRallyItems($since); $result = SendRallyNotifications($items); From 45967557c47b77b4b52f3baf6de064c2104fde74 Mon Sep 17 00:00:00 2001 From: jpklein Date: Fri, 5 Sep 2014 04:10:23 -0400 Subject: [PATCH 15/86] Define function to fetch latest conversation posts from Rally --- scripts/include/rally.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 1eead35..b5936aa 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -1,7 +1,46 @@ +' . $since . '))&fetch=Artifact,Text,User&order=CreationDate+asc'; + + $results = CallAPI($query_url); + $results = $results->QueryResult->Results; + + $project_url = $RALLY_URL . '#/' . $RALLY_PROJECT_ID; + + $items = array(); + foreach ($results as $Result) { + switch ($type = $Result->Artifact->_type) { + case 'Defect': + $path = '/detail/defect/'; + break; + case 'HierarchicalRequirement': + $type = 'User Story'; + $path = '/detail/userstory/'; + break; + default: + continue 2; //don't display comments attached to other artifact types + } + + $items[] = array( + 'type' => $type, + 'title' => $Result->Artifact->_refObjectName, + 'url' => $project_url . $path . basename($Result->Artifact->_ref) . '/discussion', + 'user' => $Result->User->_refObjectName, + 'text' => $Result->Text + ); + } + + return $items; +} + function HandleItem($slackCommand, $rallyFormattedId) { $rallyItemType = substr($rallyFormattedId,0,2); From a096dc4be4b6924ac9b598b362130a9f20a9cab9 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Fri, 5 Sep 2014 13:25:02 -0400 Subject: [PATCH 16/86] Rearchitect function placement to hint at OO code structure --- scripts/include/rally.php | 38 -------------------------- scripts/include/rallycron.inc.php | 44 +++++++++++++++++++++++++++++++ scripts/rallycron.php | 6 +---- 3 files changed, 45 insertions(+), 43 deletions(-) create mode 100644 scripts/include/rallycron.inc.php diff --git a/scripts/include/rally.php b/scripts/include/rally.php index b5936aa..bc311b3 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,44 +3,6 @@ $RALLY_URL = 'https://rally1.rallydev.com/'; $RALLY_TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.u\Z'; -function FetchLatestRallyItems($since) -{ - global $RALLY_URL, $RALLY_PROJECT_ID; - - $api_url = $RALLY_URL . 'slm/webservice/v2.0/'; - $query_url = $api_url . 'conversationpost?query=((Artifact.Project.ObjectID+%3D+' . $RALLY_PROJECT_ID . ')AND(CreationDate+>+' . $since . '))&fetch=Artifact,Text,User&order=CreationDate+asc'; - - $results = CallAPI($query_url); - $results = $results->QueryResult->Results; - - $project_url = $RALLY_URL . '#/' . $RALLY_PROJECT_ID; - - $items = array(); - foreach ($results as $Result) { - switch ($type = $Result->Artifact->_type) { - case 'Defect': - $path = '/detail/defect/'; - break; - case 'HierarchicalRequirement': - $type = 'User Story'; - $path = '/detail/userstory/'; - break; - default: - continue 2; //don't display comments attached to other artifact types - } - - $items[] = array( - 'type' => $type, - 'title' => $Result->Artifact->_refObjectName, - 'url' => $project_url . $path . basename($Result->Artifact->_ref) . '/discussion', - 'user' => $Result->User->_refObjectName, - 'text' => $Result->Text - ); - } - - return $items; -} - function HandleItem($slackCommand, $rallyFormattedId) { $rallyItemType = substr($rallyFormattedId,0,2); diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php new file mode 100644 index 0000000..aad3f2b --- /dev/null +++ b/scripts/include/rallycron.inc.php @@ -0,0 +1,44 @@ ++' . $since . '))&fetch=Artifact,Text,User&order=CreationDate+asc'; + + $results = CallAPI($query_url); + $results = $results->QueryResult->Results; + + $project_url = $RALLY_URL . '#/' . $RALLY_PROJECT_ID; + + $items = array(); + foreach ($results as $Result) { + switch ($type = $Result->Artifact->_type) { + case 'Defect': + $path = '/detail/defect/'; + break; + case 'HierarchicalRequirement': + $type = 'User Story'; + $path = '/detail/userstory/'; + break; + default: + continue 2; //don't display comments attached to other artifact types + } + + $items[] = array( + 'type' => $type, + 'title' => $Result->Artifact->_refObjectName, + 'url' => $project_url . $path . basename($Result->Artifact->_ref) . '/discussion', + 'user' => $Result->User->_refObjectName, + 'text' => $Result->Text + ); + } + + return $items; +} diff --git a/scripts/rallycron.php b/scripts/rallycron.php index a186074..ebb56dc 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -1,9 +1,5 @@ Date: Fri, 5 Sep 2014 13:31:28 -0400 Subject: [PATCH 17/86] Refactor function names for clarity --- scripts/include/rallycron.inc.php | 2 +- scripts/rallycron.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index aad3f2b..d10adab 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -5,7 +5,7 @@ require('rallyme.config.php'); require('rally.php'); -function FetchLatestRallyItems($since) +function FetchLatestRallyComments($since) { global $RALLY_URL, $RALLY_PROJECT_ID; diff --git a/scripts/rallycron.php b/scripts/rallycron.php index ebb56dc..362bb15 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -7,5 +7,5 @@ $since = date($RALLY_TIMESTAMP_FORMAT, time() - $CRON_INTERVAL); -$items = FetchLatestRallyItems($since); -$result = SendRallyNotifications($items); +$items = FetchLatestRallyComments($since); +$result = SendRallyCommentNotifications($items); From cdc95bf113b6205fe01604fb4cf91a840887018e Mon Sep 17 00:00:00 2001 From: JP Klein Date: Fri, 5 Sep 2014 14:04:42 -0400 Subject: [PATCH 18/86] Add Slack utility functions --- scripts/include/slack.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..fab9607 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -30,10 +30,31 @@ function BuildSlashCommand($request) } +//text-formatting functions + +function SanitizeText($text) +{ + $text = strtr($text, array('
' => '\n')); + return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); +} + +function l($text, $url) +{ + return '<' . $url . '|' . $text . '>'; +} + +function em($text) +{ + return '_' . $text . '_'; +} + + +//posting functions + function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - + $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -58,7 +79,7 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, From de7aed961a7759014703319d50110567125db090 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Fri, 5 Sep 2014 14:52:33 -0400 Subject: [PATCH 19/86] Add Slack utility functions --- scripts/include/slack.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index fab9607..26f97e1 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -74,7 +74,28 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } - +function SendIncomingWebHookMessage($channel, $payload, $attachments) +{ + global $config; + + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; + + $reply = slack_incoming_hook_post_with_attachments( + $config['slack']['hook'], + $config['slack']['botname'], + $channel, + $config['slack']['boticon'], + $payload, + $attachments + ); + + $success = ($reply == 'ok'); + if (!$success) { + trigger_error('Unable to send Incoming WebHook message: ' . $reply); + } + return $success; +} function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ From 705abb6a2480dffa89d50dfd5cdeff8e409d5beb Mon Sep 17 00:00:00 2001 From: JP Klein Date: Fri, 5 Sep 2014 14:55:01 -0400 Subject: [PATCH 20/86] Add settings for rally cron job --- scripts/config/rallycron.conf.php | 3 +++ scripts/include/rallycron.inc.php | 1 + 2 files changed, 4 insertions(+) create mode 100644 scripts/config/rallycron.conf.php diff --git a/scripts/config/rallycron.conf.php b/scripts/config/rallycron.conf.php new file mode 100644 index 0000000..cff2a07 --- /dev/null +++ b/scripts/config/rallycron.conf.php @@ -0,0 +1,3 @@ + Date: Fri, 5 Sep 2014 15:02:13 -0400 Subject: [PATCH 21/86] Add settings for rally cron job --- scripts/include/rally.secrets.php | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/include/rally.secrets.php b/scripts/include/rally.secrets.php index c7049d9..1df5ee6 100644 --- a/scripts/include/rally.secrets.php +++ b/scripts/include/rally.secrets.php @@ -1,5 +1,4 @@ From 5edcee70fcac2087ffbf4dad4fb6c214fc2fabb3 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Fri, 5 Sep 2014 16:02:28 -0400 Subject: [PATCH 22/86] Post new-comment notifications to Slack --- scripts/config/rallycron.conf.php | 1 + scripts/include/rallycron.inc.php | 28 +++++++++++++++++++++++++++- scripts/include/slack.php | 7 ++++--- scripts/rallycron.php | 4 +--- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/scripts/config/rallycron.conf.php b/scripts/config/rallycron.conf.php index cff2a07..bbd2816 100644 --- a/scripts/config/rallycron.conf.php +++ b/scripts/config/rallycron.conf.php @@ -1,3 +1,4 @@ ' => '\n')); + $text = strtr($text, array('
' => '\n', '
' => '\n', '

' => '\n')); return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); } @@ -83,9 +83,9 @@ function SendIncomingWebHookMessage($channel, $payload, $attachments) $reply = slack_incoming_hook_post_with_attachments( $config['slack']['hook'], - $config['slack']['botname'], + $config['rally']['botname'], $channel, - $config['slack']['boticon'], + $config['rally']['boticon'], $payload, $attachments ); @@ -107,6 +107,7 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, "attachments"=>array($attachments)); $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); + $data_string = strtr($data_string, array('\\\\n' => '\n')); //unescape slashes in newline characters mylog('sent.txt',$data_string); return curl_post($uri, $data_string); } diff --git a/scripts/rallycron.php b/scripts/rallycron.php index 362bb15..1dc9d3e 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -1,10 +1,8 @@ Date: Sat, 6 Sep 2014 10:43:01 -0400 Subject: [PATCH 23/86] Add settings for rally cron job --- scripts/config/rallycron.conf.php | 4 ++++ scripts/include/rally.php | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 scripts/config/rallycron.conf.php diff --git a/scripts/config/rallycron.conf.php b/scripts/config/rallycron.conf.php new file mode 100644 index 0000000..bbd2816 --- /dev/null +++ b/scripts/config/rallycron.conf.php @@ -0,0 +1,4 @@ +text, $payload->attachments); - + if($result=='Invalid channel specified'){ die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - + if($result!="ok"){ print_r($result."\n"); print_r(json_encode($payload)); @@ -71,11 +72,11 @@ function postit($channel_name, $payload, $attachments){ global $config, $slackCommand; return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, + $config['slack']['hook'], + $config['rally']['botname'], + $slackCommand->ChannelName, + $config['rally']['boticon'], + $payload, $attachments); } @@ -169,7 +170,7 @@ function GetDefectPayload($ref) array_push($fields,$firstattachment); global $slackCommand; - + $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; @@ -265,7 +266,7 @@ function GetRequirementPayload($ref) if($blocked) array_push($fields, MakeField("blocked",$blockedreason,true)); - + array_push($fields, MakeField("description",$short_description,false)); if($firstattachment!=null) From cbd19baeaa8115682ecfbe515a50b500cf3cb355 Mon Sep 17 00:00:00 2001 From: jpklein Date: Sat, 6 Sep 2014 10:50:02 -0400 Subject: [PATCH 24/86] Add Slack utility functions --- scripts/include/slack.php | 54 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..7a1dc21 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,11 +29,35 @@ function BuildSlashCommand($request) return $cmd; } +//text-formatting functions + +function SanitizeText($text) +{ + $text = strtr($text, array('
' => '\n', '

' => '\n', '

' => '\n')); + return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); +} + +function l($text, $url) +{ + return '<' . $url . '|' . $text . '>'; +} + +function em($text) +{ + return '_' . $text . '_'; +} + +function b($text) +{ + return '*' . $text . '*'; +} + +//posting functions function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - + $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -53,18 +77,40 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } - +function SendIncomingWebHookMessage($channel, $payload, $attachments) +{ + global $config; + + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; + + $reply = slack_incoming_hook_post_with_attachments( + $config['slack']['hook'], + $config['rally']['botname'], + $channel, + $config['rally']['boticon'], + $payload, + $attachments + ); + + $success = ($reply == 'ok'); + if (!$success) { + trigger_error('Unable to send Incoming WebHook message: ' . $reply); + } + return $success; +} function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, "attachments"=>array($attachments)); $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); + $data_string = strtr($data_string, array('\\\\n' => '\n')); //unescape slashes in newline characters mylog('sent.txt',$data_string); return curl_post($uri, $data_string); } From 22fdf5e30327859ea4c29f485f4465f67f02a41e Mon Sep 17 00:00:00 2001 From: jpklein Date: Sat, 6 Sep 2014 11:03:19 -0400 Subject: [PATCH 25/86] Define rally cron job's main routine --- scripts/include/rallycron.inc.php | 6 ++++++ scripts/rallycron.php | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 scripts/include/rallycron.inc.php create mode 100644 scripts/rallycron.php diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php new file mode 100644 index 0000000..c87d906 --- /dev/null +++ b/scripts/include/rallycron.inc.php @@ -0,0 +1,6 @@ + Date: Sat, 6 Sep 2014 15:03:19 -0400 Subject: [PATCH 26/86] WIP! Fetch new and significantly-updated artifacts from Rally --- scripts/include/rallycron.inc.php | 112 ++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index c87d906..e63586d 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -4,3 +4,115 @@ require('slack.php'); require('rallyme.config.php'); require('rally.php'); + +function FetchUpdatedRallyArtifacts($since) +{ + global $RALLY_URL, $RALLY_PROJECT_ID, $RALLY_TIMESTAMP_FORMAT; + + $api_url = $RALLY_URL . 'slm/webservice/v2.0/'; + $query_url = $api_url . 'artifact?query=((Project.ObjectID+%3D+' . $RALLY_PROJECT_ID . ')AND(LastUpdateDate+>+' . $since . '))&fetch=CreationDate,FormattedID,LastUpdateDate,Owner,Ready,RevisionHistory,ScheduleState,SubmittedBy&order=LastUpdateDate+asc&pagesize=200'; +var_dump($query_url); + $results = CallAPI($query_url); + $results = $results->QueryResult->Results; + + $project_url = $RALLY_URL . '#/' . $RALLY_PROJECT_ID; + + $items = array(); + foreach ($results as $Result) { +var_dump('processing ' . $Result->FormattedID . ' ' . $Result->_refObjectName); + $user = ''; + switch ($type = $Result->_type) { + case 'Defect': + $path = '/detail/defect/'; + $user = $Result->SubmittedBy->_refObjectName; + break; + case 'HierarchicalRequirement': + $type = 'User Story'; + $path = '/detail/userstory/'; + break; + case 'TestCase': + $type = 'Test Case'; + $path = '/detail/testcase/'; + break; + default: +var_dump('-> skipping unimportant new artifact'); + continue 2; //don't display other artifact types + } + if (empty($user) && isset($Result->Owner)) { + $user = $Result->Owner->_refObjectName; + } + + //was the artifact just created? + $lastUpDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Result->LastUpdateDate)->getTimestamp(); + $creationDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Result->CreationDate)->getTimestamp(); + + if (($lastUpDate - $creationDate) < 2) { //assume items updated within 1 sec haven't changed state +var_dump('-> reporting newly-created artifact'); + $items[] = array( //report newly-created artifacts + 'type' => $type, + 'title' => $Result->_refObjectName, + 'url' => $project_url . $path . basename($Result->_ref), + 'user' => $user, + 'id' => $Result->FormattedID + ); + + } elseif ($type == 'User Story') { //track progress of user stories + switch ($Result->ScheduleState) { + case 'Completed': + $fact_table = array(1 => 'SCHEDULE STATE changed'); + $state = 'acceptance-ready'; + break; + case 'In-Progress': + if ($Result->Ready) { + $fact_table = array(1 => 'READY changed from [false] to [true]'); + $state = 'verification-ready'; + } else { + $fact_table = array( + 0 => 'SCHEDULE STATE changed', + 1 => 'READY changed from [true] to [false]' + ); + $state = 'needs-work'; + } + break; + default: +var_dump('-> skipping unimportant status'); + continue 2; //don't parse other state changes + } + + //parse latest revision messages to verify state change + $query2_url = $Result->RevisionHistory->_ref . '/Revisions?query=(CreationDate+>+' . $since . ')&fetch=CreationDate,Description,User'; + $statusResults = CallAPI($query2_url); + + $is_verified = FALSE; + foreach ($statusResults->QueryResult->Results as $Revision) { + if (isset($fact_table[0]) && (strpos($Revision->Description, $fact_table[0]) !== FALSE)){ +var_dump('-> poisoning the well'); + continue 2; //stop parsing if the negative fact has appeared + } + if (strpos($Revision->Description, $fact_table[1]) !== FALSE) { + $is_verified = TRUE; + $user = $Revision->User->_refObjectName; + } + } + if (!$is_verified) { +var_dump('-> skipping unconfirmed state change'); + continue; //skip artifacts with unconfirmed state change + } +var_dump('-> reporting artifact state change'); + $items[] = array( //report stories that have changed state + 'type' => $type, + 'title' => $Result->_refObjectName, + 'url' => $project_url . $path . basename($Result->_ref), + 'user' => $user, + 'id' => $Result->FormattedID, + 'state' => $state //presence of this key indicates state-change notification + ); + } + } + return $items; +} + +function SendRallyUpdateNotifications($items) +{ + +} From 76721732ac66d580647424ca1b1d27edee1568bd Mon Sep 17 00:00:00 2001 From: jpklein Date: Sat, 6 Sep 2014 20:56:47 -0400 Subject: [PATCH 27/86] Remove troubleshooting code from rally fetch --- scripts/include/rallycron.inc.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index e63586d..ab8e3ef 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -11,7 +11,7 @@ function FetchUpdatedRallyArtifacts($since) $api_url = $RALLY_URL . 'slm/webservice/v2.0/'; $query_url = $api_url . 'artifact?query=((Project.ObjectID+%3D+' . $RALLY_PROJECT_ID . ')AND(LastUpdateDate+>+' . $since . '))&fetch=CreationDate,FormattedID,LastUpdateDate,Owner,Ready,RevisionHistory,ScheduleState,SubmittedBy&order=LastUpdateDate+asc&pagesize=200'; -var_dump($query_url); + $results = CallAPI($query_url); $results = $results->QueryResult->Results; @@ -19,7 +19,7 @@ function FetchUpdatedRallyArtifacts($since) $items = array(); foreach ($results as $Result) { -var_dump('processing ' . $Result->FormattedID . ' ' . $Result->_refObjectName); + $user = ''; switch ($type = $Result->_type) { case 'Defect': @@ -35,7 +35,6 @@ function FetchUpdatedRallyArtifacts($since) $path = '/detail/testcase/'; break; default: -var_dump('-> skipping unimportant new artifact'); continue 2; //don't display other artifact types } if (empty($user) && isset($Result->Owner)) { @@ -47,7 +46,6 @@ function FetchUpdatedRallyArtifacts($since) $creationDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Result->CreationDate)->getTimestamp(); if (($lastUpDate - $creationDate) < 2) { //assume items updated within 1 sec haven't changed state -var_dump('-> reporting newly-created artifact'); $items[] = array( //report newly-created artifacts 'type' => $type, 'title' => $Result->_refObjectName, @@ -75,7 +73,6 @@ function FetchUpdatedRallyArtifacts($since) } break; default: -var_dump('-> skipping unimportant status'); continue 2; //don't parse other state changes } @@ -86,7 +83,6 @@ function FetchUpdatedRallyArtifacts($since) $is_verified = FALSE; foreach ($statusResults->QueryResult->Results as $Revision) { if (isset($fact_table[0]) && (strpos($Revision->Description, $fact_table[0]) !== FALSE)){ -var_dump('-> poisoning the well'); continue 2; //stop parsing if the negative fact has appeared } if (strpos($Revision->Description, $fact_table[1]) !== FALSE) { @@ -95,10 +91,8 @@ function FetchUpdatedRallyArtifacts($since) } } if (!$is_verified) { -var_dump('-> skipping unconfirmed state change'); continue; //skip artifacts with unconfirmed state change } -var_dump('-> reporting artifact state change'); $items[] = array( //report stories that have changed state 'type' => $type, 'title' => $Result->_refObjectName, From d160f9f823b00aaed8d78f49f6e85803c9bf790e Mon Sep 17 00:00:00 2001 From: jpklein Date: Sat, 6 Sep 2014 22:09:17 -0400 Subject: [PATCH 28/86] Post state-change notifications to Slack --- scripts/include/rallycron.inc.php | 81 ++++++++++++++++++++++++------- scripts/rallycron.php | 5 +- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index ab8e3ef..6fcb918 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -18,13 +18,13 @@ function FetchUpdatedRallyArtifacts($since) $project_url = $RALLY_URL . '#/' . $RALLY_PROJECT_ID; $items = array(); - foreach ($results as $Result) { + foreach ($results as $Artifact) { $user = ''; - switch ($type = $Result->_type) { + switch ($type = $Artifact->_type) { case 'Defect': $path = '/detail/defect/'; - $user = $Result->SubmittedBy->_refObjectName; + $user = $Artifact->SubmittedBy->_refObjectName; break; case 'HierarchicalRequirement': $type = 'User Story'; @@ -37,31 +37,31 @@ function FetchUpdatedRallyArtifacts($since) default: continue 2; //don't display other artifact types } - if (empty($user) && isset($Result->Owner)) { - $user = $Result->Owner->_refObjectName; + if (empty($user) && isset($Artifact->Owner)) { + $user = $Artifact->Owner->_refObjectName; } //was the artifact just created? - $lastUpDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Result->LastUpdateDate)->getTimestamp(); - $creationDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Result->CreationDate)->getTimestamp(); + $lastUpDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Artifact->LastUpdateDate)->getTimestamp(); + $creationDate = date_create_from_format($RALLY_TIMESTAMP_FORMAT, $Artifact->CreationDate)->getTimestamp(); if (($lastUpDate - $creationDate) < 2) { //assume items updated within 1 sec haven't changed state $items[] = array( //report newly-created artifacts 'type' => $type, - 'title' => $Result->_refObjectName, - 'url' => $project_url . $path . basename($Result->_ref), + 'title' => $Artifact->_refObjectName, + 'url' => $project_url . $path . basename($Artifact->_ref), 'user' => $user, - 'id' => $Result->FormattedID + 'id' => $Artifact->FormattedID ); } elseif ($type == 'User Story') { //track progress of user stories - switch ($Result->ScheduleState) { + switch ($Artifact->ScheduleState) { case 'Completed': $fact_table = array(1 => 'SCHEDULE STATE changed'); $state = 'acceptance-ready'; break; case 'In-Progress': - if ($Result->Ready) { + if ($Artifact->Ready) { $fact_table = array(1 => 'READY changed from [false] to [true]'); $state = 'verification-ready'; } else { @@ -77,11 +77,13 @@ function FetchUpdatedRallyArtifacts($since) } //parse latest revision messages to verify state change - $query2_url = $Result->RevisionHistory->_ref . '/Revisions?query=(CreationDate+>+' . $since . ')&fetch=CreationDate,Description,User'; - $statusResults = CallAPI($query2_url); + $query2_url = $Artifact->RevisionHistory->_ref . '/Revisions?query=(CreationDate+>+' . $since . ')&fetch=CreationDate,Description,User'; + + $results2 = CallAPI($query2_url); + $results2 = $results2->QueryResult->Results; $is_verified = FALSE; - foreach ($statusResults->QueryResult->Results as $Revision) { + foreach ($results2 as $Revision) { if (isset($fact_table[0]) && (strpos($Revision->Description, $fact_table[0]) !== FALSE)){ continue 2; //stop parsing if the negative fact has appeared } @@ -93,12 +95,13 @@ function FetchUpdatedRallyArtifacts($since) if (!$is_verified) { continue; //skip artifacts with unconfirmed state change } + $items[] = array( //report stories that have changed state 'type' => $type, - 'title' => $Result->_refObjectName, - 'url' => $project_url . $path . basename($Result->_ref), + 'title' => $Artifact->_refObjectName, + 'url' => $project_url . $path . basename($Artifact->_ref), 'user' => $user, - 'id' => $Result->FormattedID, + 'id' => $Artifact->FormattedID, 'state' => $state //presence of this key indicates state-change notification ); } @@ -108,5 +111,47 @@ function FetchUpdatedRallyArtifacts($since) function SendRallyUpdateNotifications($items) { + global $SLACK_CHANNEL_FOR_RALLY_PROJECT; + $success = TRUE; + + foreach ($items as $item) { + $item['title'] = SanitizeText($item['title']); + $item['title'] = TruncateText($item['title'], 300); + $slug = l($item['title'], $item['url']); + + //display a state-change notification as a message attachment + if (isset($item['state'])) { + switch ($item['state']) { + case 'verification-ready': + $item['state'] = ' is ready for QA'; + $color = '#F29513'; //github orange + break; + case 'needs-work': + $item['state'] = ' needs additional work'; + $color = '#D84A63'; //paletton-suggested red + break; + case 'acceptance-ready': + $item['state'] = ' is ready for acceptance'; + $color = '#6CC644'; //github green + } + + $pretext = em($item['type'] . ' updated by ' . $item['user']); + $text = b($item['id']) . $item['state']; + $fields = array(MakeField('', $slug)); + $fallback = $item['type'] . ' ' . $item['id'] . $item['state']; + + //display a link to the new artifact as a message attachment + } else { + $pretext = em('New ' . $item['type'] . ' added by ' . $item['user']); + $text = ''; + $color = '#6CC644'; //github green + $fields = array(MakeField($item['id'], $slug)); + $fallback = $item['type'] . ' ' . $item['id'] . ' added by ' . $item['user']; + } + + $message = MakeAttachment($pretext, $text, $color, $fields, $fallback); + $success = sendIncomingWebHookMessage($SLACK_CHANNEL_FOR_RALLY_PROJECT, '', $message) && $success; + } + return $success; } diff --git a/scripts/rallycron.php b/scripts/rallycron.php index 7fa7541..87e9c8a 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -5,5 +5,6 @@ date_default_timezone_set('UTC'); $since = date($RALLY_TIMESTAMP_FORMAT, time() - $CRON_INTERVAL); -$items = FetchUpdatedRallyArtifacts($since); -$result = SendRallyUpdateNotifications($items); +if ($items = FetchUpdatedRallyArtifacts($since)) { + $result = SendRallyUpdateNotifications($items); +} From 71cdec587cf8b72d508ca1d0ba8878c17fd84130 Mon Sep 17 00:00:00 2001 From: jpklein Date: Sun, 7 Sep 2014 00:28:07 -0400 Subject: [PATCH 29/86] Move rally cron script settings to centralized config file --- scripts/config/default.config.php | 9 +++++++++ scripts/config/rallycron.conf.php | 4 ---- scripts/include/rallycron.inc.php | 2 -- scripts/rallycron.php | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 scripts/config/rallycron.conf.php diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 90dafc0..96f8e4b 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -18,3 +18,12 @@ $config['rally']['botname'] = "rallybot"; $config['rally']['boticon'] = "https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg"; + + //////////////////////// + // Rallycron settings // +//////////////////////// + +$CRON_INTERVAL = 61; //seconds between cron runs; pad for script run time and latency + +$RALLY_PROJECT_ID = REPLACE_ME; //number that follows '#/' in the URI of the project to track +$SLACK_CHANNEL_FOR_RALLY_PROJECT = 'REPLACE ME'; //do not include hash symbol diff --git a/scripts/config/rallycron.conf.php b/scripts/config/rallycron.conf.php deleted file mode 100644 index bbd2816..0000000 --- a/scripts/config/rallycron.conf.php +++ /dev/null @@ -1,4 +0,0 @@ - Date: Sun, 7 Sep 2014 22:40:23 -0400 Subject: [PATCH 30/86] Refactor global variables --- scripts/config/default.config.php | 13 ++++++------- scripts/gifme.php | 6 ++---- scripts/imageme.php | 6 ++---- scripts/include/rally.php | 25 ++++++++++--------------- scripts/include/slack.php | 6 +++--- scripts/meme.php | 2 +- scripts/xkcd.php | 6 ++---- 7 files changed, 26 insertions(+), 38 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 90dafc0..d2db7e7 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -4,17 +4,16 @@ // Slack settings // //////////////////// -$config['slack']['outgoinghooktoken'] = "REPLACE ME"; -$config['slack']['hook'] = "REPLACE ME"; //URL of an Incoming WebHook including https:// and token value +$SLACK_INCOMING_HOOK_URL = 'REPLACE ME'; //URL of an Incoming WebHook including https:// and token value -$config['slack']['subdomain'] = "REPLACE ME"; //subdomain used to identify your team's instance, like "cim" +$SLACK_SUBDOMAIN = 'REPLACE ME'; //subdomain used to identify your team's instance, like 'cim' //////////////////// // Rally settings // //////////////////// -$config['rally']['username'] = "REPLACE ME"; -$config['rally']['password'] = "REPLACE ME"; +$RALLY_USERNAME = 'REPLACE ME'; +$RALLY_PASSWORD = 'REPLACE ME'; -$config['rally']['botname'] = "rallybot"; -$config['rally']['boticon'] = "https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg"; +$RALLYBOT_NAME = 'rallybot'; +$RALLYBOT_ICON = 'https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg'; diff --git a/scripts/gifme.php b/scripts/gifme.php index afd2a47..6c66610 100644 --- a/scripts/gifme.php +++ b/scripts/gifme.php @@ -5,8 +5,6 @@ $command = BuildSlashCommand($_REQUEST); -$hook = $config['slack']['hook']; - //use one or the other of $emoji or $iconurl $emoji = ":camera:"; $iconurl = null; @@ -17,7 +15,7 @@ $imageresponse = json_decode($imageSearchJson); -$userlink = 'UserName . '|' . $command->UserName . '>'; +$userlink = 'UserName . '|' . $command->UserName . '>'; if($imageresponse->responseData == null){ //{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503} @@ -34,7 +32,7 @@ $payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}"; -$ret = slack_incoming_hook_post($hook, "gifbot", $command->ChannelName, $iconurl, $emoji, $payload); +$ret = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, "gifbot", $command->ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /gifme.\n{$ret}"); ?> diff --git a/scripts/imageme.php b/scripts/imageme.php index a990b4c..828c4c2 100644 --- a/scripts/imageme.php +++ b/scripts/imageme.php @@ -5,12 +5,10 @@ $command = BuildSlashCommand($_REQUEST); -$hook = $config['slack']['hook']; - //use one or the other of $emoji or $iconurl $emoji = ":camera:"; $iconurl = null; -$userlink = 'UserName . '|' . $command->UserName . '>'; +$userlink = 'UserName . '|' . $command->UserName . '>'; $maxtries = 2; $tries = 0; @@ -41,7 +39,7 @@ $payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}"; -$ret = slack_incoming_hook_post($hook, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload); +$ret = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload); if($ret!="ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}"); diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 66a0820..e29f800 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -68,15 +68,10 @@ function HandleStory($id, $channel_name) } function postit($channel_name, $payload, $attachments){ - global $config, $slackCommand; - - return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, - $attachments); + + global $SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $RALLYBOT_ICON, $slackCommand; + + return slack_incoming_hook_post_with_attachments($SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $slackCommand->ChannelName, $RALLYBOT_ICON, $payload, $attachments); } @@ -300,11 +295,11 @@ function getProjectPayload($projectRefUri) $project = CallAPI($projectRefUri); } -function CallAPI($uri) +function CallAPI($url) { - global $config; + global $RALLY_USERNAME, $RALLY_PASSWORD; - $json = get_url_contents_with_basicauth($uri, $config['rally']['username'], $config['rally']['password']); + $json = get_url_contents_with_basicauth($url, $RALLY_USERNAME, $RALLY_PASSWORD); $object = json_decode($json); return $object; @@ -334,9 +329,9 @@ function FindRequirement($id) function BuildUserLink($username) { - global $config; - $userlink = ''; - return $userlink; + global $SLACK_SUBDOMAIN; + $userlink = ''; + return $userlink; } function GetArtifactQueryUri($id) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..12ce6c5 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -31,9 +31,9 @@ function BuildSlashCommand($request) function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - + $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -58,7 +58,7 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, diff --git a/scripts/meme.php b/scripts/meme.php index f378fe5..f5af660 100644 --- a/scripts/meme.php +++ b/scripts/meme.php @@ -35,7 +35,7 @@ $meme = CreateNewMeme($gen, $top, $bottom); mylog('sent.txt',$meme); -$response = slack_incoming_hook_post($config['slack']['hook'], $cmd->UserName, $cmd->ChannelName, null, ":bow:", $meme); +$response = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, $cmd->UserName, $cmd->ChannelName, null, ":bow:", $meme); mylog('sent.txt',$response); diff --git a/scripts/xkcd.php b/scripts/xkcd.php index a36133d..e4de2cd 100644 --- a/scripts/xkcd.php +++ b/scripts/xkcd.php @@ -5,8 +5,6 @@ $command = BuildSlashCommand($_REQUEST); -$hook = $config['slack']['hook']; - //use one or the other of $emoji or $iconurl $emoji = null; $iconurl = "http://upload.wikimedia.org/wikipedia/en/1/13/Stick_figure.png"; @@ -24,7 +22,7 @@ $payload = "{$image}\n\n"; -$ret = slack_incoming_hook_post($hook, "xkcdbot", $command->ChannelName, $iconurl, $emoji, $payload); -if($ret!="ok") +$ret = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, "xkcdbot", $command->ChannelName, $iconurl, $emoji, $payload); +if ($ret != "ok") print_r("@tdm, gifbot got this response when it tried to post to the incoming hook.\n{$ret}"); ?> From 55b21220cfa83c80fa258655d2a3a1cd2c79f437 Mon Sep 17 00:00:00 2001 From: jpklein Date: Sun, 7 Sep 2014 23:13:27 -0400 Subject: [PATCH 31/86] Update global variables in rally cron code --- scripts/config/default.config.php | 2 +- scripts/include/slack.php | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 54240b6..f70717f 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -24,5 +24,5 @@ $CRON_INTERVAL = 61; //seconds between cron runs; pad for script run time and latency -$RALLY_PROJECT_ID = REPLACE_ME; //number that follows '#/' in the URI of the project to track +$RALLY_PROJECT_ID = REPLACE_ME; //number after '#/' in the URI of the project to track $SLACK_CHANNEL_FOR_RALLY_PROJECT = 'REPLACE ME'; //do not include hash symbol diff --git a/scripts/include/slack.php b/scripts/include/slack.php index bbf4dea..66632d5 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -77,19 +77,12 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function SendIncomingWebHookMessage($channel, $payload, $attachments) { - global $config; + global $SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $RALLYBOT_ICON; //allow bot to display formatted attachment text $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; - $reply = slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $channel, - $config['rally']['boticon'], - $payload, - $attachments - ); + $reply = slack_incoming_hook_post_with_attachments($SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $channel, $RALLYBOT_ICON, $payload, $attachments); $success = ($reply == 'ok'); if (!$success) { From e1316d03a5b6b6332c859bb03a3dc06b671920ad Mon Sep 17 00:00:00 2001 From: jpklein Date: Mon, 8 Sep 2014 00:53:11 -0400 Subject: [PATCH 32/86] Separate rallyme script functions from library code --- scripts/include/rally.php | 373 -------------------------------- scripts/include/rallyme.inc.php | 333 ++++++++++++++++++++++++++++ scripts/include/slack.php | 30 ++- scripts/rallyme.php | 6 +- 4 files changed, 362 insertions(+), 380 deletions(-) create mode 100644 scripts/include/rallyme.inc.php diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 9713410..f2a1bba 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -2,299 +2,6 @@ //rally commands -function HandleItem($slackCommand, $rallyFormattedId) -{ - $rallyItemType = substr($rallyFormattedId,0,2); - - switch($rallyItemType){ - - case "DE": - return HandleDefect($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - case "US": - case "TA": - return HandleStory($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - default: - print_r("Sorry, I don't know what kind of rally object {$rallyFormattedId} is. If you need rallyme to work with these, buy a :beer:. I hear he likes IPAs."); - die; - break; - } -} - -function HandleDefect($id, $channel_name) -{ - $defectref = FindDefect($id); - - $payload = GetDefectPayload($defectref); - - $result = postit($channel_name, $payload->text, $payload->attachments); - - if($result=='Invalid channel specified'){ - die("Sorry, the rallyme command can't post messages to your private chat.\n"); - } - - if($result!="ok"){ - print_r($result."\n"); - print_r(json_encode($payload)); - print_r("\n"); - die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); - } - return $result; -} - - -function HandleStory($id, $channel_name) -{ - $ref = FindRequirement($id); - - $payload = GetRequirementPayload($ref); - - $result = postit($channel_name, $payload->text, $payload->attachments); - - if($result=='Invalid channel specified'){ - die("Sorry, the rallyme command can't post messages to your private chat.\n"); - } - - if($result!="ok"){ - print_r($result."\n"); - print_r(json_encode($payload)); - print_r("\n"); - die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); - } - return $result; -} - -function postit($channel_name, $payload, $attachments){ - global $config, $slackCommand; - - return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, - $attachments); -} - - - -function GetRallyAttachmentLink($attachmentRef) -{ - $attachments = CallAPI($attachmentRef); - $firstattachment = $attachments->QueryResult->Results[0]; - - $attachmentname = $firstattachment->_refObjectName; - $encodedattachmentname = urlencode($attachmentname); - $id = $firstattachment->ObjectID; - - $uri = "https://rally1.rallydev.com/slm/attachment/{$id}/{$encodedattachmentname}"; - $linktxt = "<{$uri}|{$attachmentname}>"; - return $linktxt; -} - -function GetDefectPayload($ref) -{ - global $show,$requesting_user_name; - - $object = CallAPI($ref); - - $defect = $object->Defect; - - $projecturi = $defect->Project->_ref; - - $title = $defect->_refObjectName; - $description = $defect->Description; - $owner = $defect->Owner->_refObjectName; - $submitter = $defect->SubmittedBy->_refObjectName; - $project = $object->Project->_refObjectName; - $created = $defect->_CreatedAt; - $state = $defect->State; - $priority = $defect->Priority; - $severity = $defect->Severity; - $frequency = $defect->c_Frequency; - $foundinbuild = $defect->FoundInBuild; - - $short_description = TruncateText(strip_tags($description), 200); - - $ProjectFull = CallAPI($projecturi); - $projectid = $ProjectFull->Project->ObjectID; - $defectid = $defect->ObjectID; - $projectName = $defect->Project->_refObjectName; - $itemid = $defect->FormattedID; - - $attachmentcount = $defect->Attachments->Count; - - $firstattachment = null; - if($attachmentcount>0) - { - $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); - } - - $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; - - $enctitle = urlencode($title); - $linktext = "<{$defecturi}|{$enctitle}>"; - - $color = "bad"; - - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - - $fields = array( - MakeField("link",$linktext,false), - - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), - - MakeField("project",$projectName,true), - MakeField("created",$created,true), - - MakeField("submitter",$submitter,true), - MakeField("state",$state,true), - - MakeField("priority",$priority,true), - MakeField("severity",$severity,true), - - MakeField("frequency",$frequency,true), - MakeField("found in",$foundinbuild,true), - - MakeField("description",$short_description,false) - ); - - if($firstattachment!=null) - array_push($fields,$firstattachment); - - global $slackCommand; - - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok, {$userlink}, here's the defect you requested."; - - $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); - return $obj; -} - -function GetRequirementPayload($ref) -{ - $object = CallAPI($ref); - - $requirement = null; - - if($object->HierarchicalRequirement) - { - $requirement = $object->HierarchicalRequirement; - } - elseif($object->Task) - { - $requirement = $object->Task; - } - else - { - $class = get_class($object); - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I can't handle a {$class} yet. I'll let @tdm know about it."); - die; - } - - $projecturi = $requirement->Project->_ref; - - $title = $requirement->_refObjectName; - - - $ProjectFull = CallAPI($projecturi); - $projectid = $ProjectFull->Project->ObjectID; - $storyid = $requirement->ObjectID; - $description = $requirement->Description; - $owner = $requirement->Owner->_refObjectName; - $projectName = $requirement->Project->_refObjectName; - $itemid = $requirement->FormattedID; - $created = $requirement->_CreatedAt; - $estimate = $requirement->PlanEstimate; - $hasparent = $requirement->HasParent; - $childcount = $requirement->DirectChildrenCount; - $state = $requirement->ScheduleState; - $blocked = $requirement->Blocked; - $blockedreason = $requirement->BlockedReason; - $ready = $requirement->Ready; - - $attachmentcount = $requirement->Attachments->Count; - - $firstattachment = null; - if($attachmentcount>0) - { - $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); - } - - $parent = null; - if($hasparent) - $parent = $requirement->Parent->_refObjectName; - - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - - $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; - $enctitle = urlencode($title); - $linktext = "<{$storyuri}|{$enctitle}>"; - - $dovegray = "#CEC7B8"; - - - - $fields = array( - MakeField("link",$linktext,false), - MakeField("parent",$parent,false), - - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), - - MakeField("project",$projectName,true), - MakeField("created",$created,true), - - MakeField("estimate",$estimate,true), - MakeField("state",$state,true)); - - if($childcount>0) - array_push($fields,MakeField("children",$childcount,true)); - - if($blocked) - array_push($fields, MakeField("blocked",$blockedreason,true)); - - array_push($fields, MakeField("description",$short_description,false)); - - if($firstattachment!=null) - array_push($fields,$firstattachment); - - - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok {$userlink}, here's the story you requested."; - - $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); -// print_r(json_encode($obj));die; - - return $obj; -} - - -function MakeField($title, $value, $short=false) -{ - $attachmentfield = array( - "title" => $title, - "value" => $value, - "short" => $short); - - return $attachmentfield; -} - function getProjectPayload($projectRefUri) { $project = CallAPI($projectRefUri); @@ -318,84 +25,4 @@ function GetProjectID($projectref) return $projectid; } -function FindRequirement($id) -{ - $query = GetArtifactQueryUri($id); - - $searchresult = CallAPI($query); -// print_r($searchresult);die; - - $count = GetCount($searchresult); - if($count == 0) - NotFound($id); - - return GetFirstObjectFromSearchResult("HierarchicalRequirement", $searchresult); -} - -function BuildUserLink($username) -{ - $userlink = ""; - return $userlink; -} - -function GetArtifactQueryUri($id) -{ - global $config; - return str_replace("[[ID]]", $id, $config['rally']['artifactquery']); -} - -function GetDefectQueryUri($id) -{ - global $config; - return str_replace("[[ID]]", $id, $config['rally']['defectquery']); -} - -function FindDefect($id) -{ - $query = GetDefectQueryUri($id); - $searchresult = CallAPI($query); - - $count = GetCount($searchresult); - if($count == 0) - NotFound($id); - - return GetFirstObjectFromSearchResult("Defect", $searchresult); -} - -function GetCount($searchresult) -{ - return $searchresult->QueryResult->TotalResultCount; -} - -function NotFound($id) -{ - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I couldn't find {$id}");die; -} - - -function GetFirstObjectFromSearchResult($objectName, $result) -{ - foreach ($result->QueryResult->Results as $result) - { - if($result->_type == $objectName) - return $result->_ref; - } - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry @{$userlink}, your search for '{$slackCommand->Text}' was ambiguous.:\n"); - print_r("Here's what Rally told me:\n"); - print_r($result); - die; -} - -function TruncateText($text, $len) -{ - if(strlen($text) <= $len) - return $text; - - return substr($text,0,$len)."...[MORE]"; -} - ?> diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php new file mode 100644 index 0000000..ae2ad6e --- /dev/null +++ b/scripts/include/rallyme.inc.php @@ -0,0 +1,333 @@ +ChannelName); + die; + break; + case "US": + case "TA": + return HandleStory($rallyFormattedId, $slackCommand->ChannelName); + die; + break; + default: + print_r("Sorry, I don't know what kind of rally object {$rallyFormattedId} is. If you need rallyme to work with these, buy a :beer:. I hear he likes IPAs."); + die; + break; + } +} + +function HandleDefect($id, $channel_name) +{ + $defectref = FindDefect($id); + + $payload = GetDefectPayload($defectref); + + $result = postit($channel_name, $payload->text, $payload->attachments); + + if ($result == 'Invalid channel specified') { + die("Sorry, the rallyme command can't post messages to your private chat.\n"); + } + + if ($result != "ok") { + print_r($result . "\n"); + print_r(json_encode($payload)); + print_r("\n"); + die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); + } + return $result; +} + +function FindDefect($id) +{ + $query = GetDefectQueryUri($id); + $searchresult = CallAPI($query); + + $count = GetCount($searchresult); + if ($count == 0) + NotFound($id); + + return GetFirstObjectFromSearchResult("Defect", $searchresult); +} + +function GetDefectQueryUri($id) +{ + $defectquery = "https://rally1.rallydev.com/slm/webservice/v2.0/defect?query=(FormattedID%20=%20[[ID]])"; + return str_replace("[[ID]]", $id, $defectquery); +} + +function GetDefectPayload($ref) +{ + global $show, $requesting_user_name; + + $object = CallAPI($ref); + + $defect = $object->Defect; + + $projecturi = $defect->Project->_ref; + + $title = $defect->_refObjectName; + $description = $defect->Description; + $owner = $defect->Owner->_refObjectName; + $submitter = $defect->SubmittedBy->_refObjectName; + $project = $object->Project->_refObjectName; + $created = $defect->_CreatedAt; + $state = $defect->State; + $priority = $defect->Priority; + $severity = $defect->Severity; + $frequency = $defect->c_Frequency; + $foundinbuild = $defect->FoundInBuild; + + $short_description = TruncateText(strip_tags($description), 200); + + $ProjectFull = CallAPI($projecturi); + $projectid = $ProjectFull->Project->ObjectID; + $defectid = $defect->ObjectID; + $projectName = $defect->Project->_refObjectName; + $itemid = $defect->FormattedID; + + $attachmentcount = $defect->Attachments->Count; + + $firstattachment = null; + if ($attachmentcount > 0) { + $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } + + $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; + + $enctitle = urlencode($title); + $linktext = "<{$defecturi}|{$enctitle}>"; + + $color = "bad"; + + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); + $short_description = TruncateText($clean_description, 300); + + $fields = array( + MakeField("link", $linktext, false), + + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), + + MakeField("project", $projectName, true), + MakeField("created", $created, true), + + MakeField("submitter", $submitter, true), + MakeField("state", $state, true), + + MakeField("priority", $priority, true), + MakeField("severity", $severity, true), + + MakeField("frequency", $frequency, true), + MakeField("found in", $foundinbuild, true), + + MakeField("description", $short_description, false) + ); + + if ($firstattachment != null) + array_push($fields, $firstattachment); + + global $slackCommand; + + $userlink = BuildUserLink($slackCommand->UserName); + $user_message = "Ok, {$userlink}, here's the defect you requested."; + + $obj = new stdClass; + $obj->text = ""; + $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); + return $obj; +} + +function HandleStory($id, $channel_name) +{ + $ref = FindRequirement($id); + + $payload = GetRequirementPayload($ref); + + $result = postit($channel_name, $payload->text, $payload->attachments); + + if ($result == 'Invalid channel specified') { + die("Sorry, the rallyme command can't post messages to your private chat.\n"); + } + + if ($result != "ok") { + print_r($result . "\n"); + print_r(json_encode($payload)); + print_r("\n"); + die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); + } + return $result; +} + +function FindRequirement($id) +{ + $query = GetArtifactQueryUri($id); + + $searchresult = CallAPI($query); + // print_r($searchresult);die; + + $count = GetCount($searchresult); + if ($count == 0) + NotFound($id); + + return GetFirstObjectFromSearchResult("HierarchicalRequirement", $searchresult); +} + +function GetArtifactQueryUri($id) +{ + $artifactquery = "https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(FormattedID%20=%20[[ID]])"; + return str_replace("[[ID]]", $id, $artifactquery); +} + +function GetRequirementPayload($ref) +{ + $object = CallAPI($ref); + + $requirement = null; + + if ($object->HierarchicalRequirement) { + $requirement = $object->HierarchicalRequirement; + } elseif ($object->Task) { + $requirement = $object->Task; + } else { + $class = get_class($object); + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + print_r("Sorry {$userlink}, I can't handle a {$class} yet. I'll let @tdm know about it."); + die; + } + + $projecturi = $requirement->Project->_ref; + + $title = $requirement->_refObjectName; + + $ProjectFull = CallAPI($projecturi); + $projectid = $ProjectFull->Project->ObjectID; + $storyid = $requirement->ObjectID; + $description = $requirement->Description; + $owner = $requirement->Owner->_refObjectName; + $projectName = $requirement->Project->_refObjectName; + $itemid = $requirement->FormattedID; + $created = $requirement->_CreatedAt; + $estimate = $requirement->PlanEstimate; + $hasparent = $requirement->HasParent; + $childcount = $requirement->DirectChildrenCount; + $state = $requirement->ScheduleState; + $blocked = $requirement->Blocked; + $blockedreason = $requirement->BlockedReason; + $ready = $requirement->Ready; + + $attachmentcount = $requirement->Attachments->Count; + + $firstattachment = null; + if ($attachmentcount > 0) { + $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } + + $parent = null; + if ($hasparent) + $parent = $requirement->Parent->_refObjectName; + + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); + $short_description = TruncateText($clean_description, 300); + + $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; + $enctitle = urlencode($title); + $linktext = "<{$storyuri}|{$enctitle}>"; + + $dovegray = "#CEC7B8"; + + $fields = array( + MakeField("link", $linktext, false), + MakeField("parent", $parent, false), + + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), + + MakeField("project", $projectName, true), + MakeField("created", $created, true), + + MakeField("estimate", $estimate, true), + MakeField("state", $state, true) + ); + + if ($childcount > 0) + array_push($fields, MakeField("children", $childcount, true)); + + if ($blocked) + array_push($fields, MakeField("blocked", $blockedreason, true)); + + array_push($fields, MakeField("description", $short_description, false)); + + if ($firstattachment != null) + array_push($fields, $firstattachment); + + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + $user_message = "Ok {$userlink}, here's the story you requested."; + + $obj = new stdClass; + $obj->text = ""; + $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); + // print_r(json_encode($obj));die; + + return $obj; +} + +function GetCount($searchresult) +{ + return $searchresult->QueryResult->TotalResultCount; +} + +function NotFound($id) +{ + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + print_r("Sorry {$userlink}, I couldn't find {$id}"); + die; +} + +function GetFirstObjectFromSearchResult($objectName, $result) +{ + foreach ($result->QueryResult->Results as $result) { + if ($result->_type == $objectName) + return $result->_ref; + } + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + print_r("Sorry @{$userlink}, your search for '{$slackCommand->Text}' was ambiguous.:\n"); + print_r("Here's what Rally told me:\n"); + print_r($result); + die; +} + +function GetRallyAttachmentLink($attachmentRef) +{ + $attachments = CallAPI($attachmentRef); + $firstattachment = $attachments->QueryResult->Results[0]; + + $attachmentname = $firstattachment->_refObjectName; + $encodedattachmentname = urlencode($attachmentname); + $id = $firstattachment->ObjectID; + + $uri = "https://rally1.rallydev.com/slm/attachment/{$id}/{$encodedattachmentname}"; + $linktxt = "<{$uri}|{$attachmentname}>"; + return $linktxt; +} + +function postit($channel_name, $payload, $attachments) +{ + global $config, $slackCommand; + + return slack_incoming_hook_post_with_attachments($config['slack']['hook'], $config['rally']['botname'], $slackCommand->ChannelName, $config['rally']['boticon'], $payload, $attachments); +} diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..4a77cfd 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,11 +29,24 @@ function BuildSlashCommand($request) return $cmd; } +function BuildUserLink($username) +{ + $userlink = ""; + return $userlink; +} + +function TruncateText($text, $len) +{ + if(strlen($text) <= $len) + return $text; + + return substr($text,0,$len)."...[MORE]"; +} function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - + $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -58,7 +71,7 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, @@ -105,4 +118,15 @@ function MakeAttachment($pretext, $text, $color, $fields, $fallback){ return $obj; } + +function MakeField($title, $value, $short = false) +{ + $attachmentfield = array( + "title" => $title, + "value" => $value, + "short" => $short + ); + + return $attachmentfield; +} ?> diff --git a/scripts/rallyme.php b/scripts/rallyme.php index aca567c..e73c018 100644 --- a/scripts/rallyme.php +++ b/scripts/rallyme.php @@ -1,13 +1,11 @@ Text); $result = HandleItem($slackCommand, $rallyFormattedId); -?> \ No newline at end of file +?> From 1e109fd01c5fa91fb712f621255e9f6ac6effe96 Mon Sep 17 00:00:00 2001 From: jpklein Date: Mon, 8 Sep 2014 01:00:42 -0400 Subject: [PATCH 33/86] Remove unused library functions --- scripts/include/rally.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index f2a1bba..269fd05 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -2,11 +2,6 @@ //rally commands -function getProjectPayload($projectRefUri) -{ - $project = CallAPI($projectRefUri); -} - function CallAPI($uri) { global $config; @@ -16,13 +11,4 @@ function CallAPI($uri) return $object; } - - -function GetProjectID($projectref) -{ - $ProjectFull = CallAPI($projectref); - $projectid = $ProjectFull->Project->ObjectID; - return $projectid; -} - ?> From 03fba941354ac99c14a18f4d7675bdb34252ad95 Mon Sep 17 00:00:00 2001 From: jpklein Date: Mon, 8 Sep 2014 02:01:31 -0400 Subject: [PATCH 34/86] Update library functions to use global variables --- scripts/include/rallycron.inc.php | 16 ++++++++++++++++ scripts/include/rallyme.inc.php | 5 ++--- scripts/include/slack.php | 26 ++++++-------------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index 34b9e0e..8c38f6f 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -218,3 +218,19 @@ function SendRallyUpdateNotifications($items) return $success; } + +function SendIncomingWebHookMessage($channel, $payload, $attachments) +{ + global $SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $RALLYBOT_ICON; + + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; + + $reply = slack_incoming_hook_post_with_attachments($SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $channel, $RALLYBOT_ICON, $payload, $attachments); + + $success = ($reply == 'ok'); + if (!$success) { + trigger_error('Unable to send Incoming WebHook message: ' . $reply); + } + return $success; +} diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index ae2ad6e..94dc5a9 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -8,7 +8,6 @@ function HandleItem($slackCommand, $rallyFormattedId) $rallyItemType = substr($rallyFormattedId, 0, 2); switch ($rallyItemType) { - case "DE": return HandleDefect($rallyFormattedId, $slackCommand->ChannelName); die; @@ -327,7 +326,7 @@ function GetRallyAttachmentLink($attachmentRef) function postit($channel_name, $payload, $attachments) { - global $config, $slackCommand; + global $SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $RALLYBOT_ICON, $slackCommand; - return slack_incoming_hook_post_with_attachments($config['slack']['hook'], $config['rally']['botname'], $slackCommand->ChannelName, $config['rally']['boticon'], $payload, $attachments); + return slack_incoming_hook_post_with_attachments($SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $slackCommand->ChannelName, $RALLYBOT_ICON, $payload, $attachments); } diff --git a/scripts/include/slack.php b/scripts/include/slack.php index a4d9fca..c1b95d9 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -33,8 +33,10 @@ function BuildSlashCommand($request) function BuildUserLink($username) { - $userlink = ""; - return $userlink; + global $SLACK_SUBDOMAIN; + + $userlink = ''; + return $userlink; } function SanitizeText($text) @@ -45,10 +47,10 @@ function SanitizeText($text) function TruncateText($text, $len) { - if(strlen($text) <= $len) + if (strlen($text) <= $len) return $text; - return substr($text,0,$len)."...[MORE]"; + return substr($text, 0, $len) . "...[MORE]"; } function l($text, $url) @@ -89,22 +91,6 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } -function SendIncomingWebHookMessage($channel, $payload, $attachments) -{ - global $SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $RALLYBOT_ICON; - - //allow bot to display formatted attachment text - $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; - - $reply = slack_incoming_hook_post_with_attachments($SLACK_INCOMING_HOOK_URL, $RALLYBOT_NAME, $channel, $RALLYBOT_ICON, $payload, $attachments); - - $success = ($reply == 'ok'); - if (!$success) { - trigger_error('Unable to send Incoming WebHook message: ' . $reply); - } - return $success; -} - function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments) { From 9c516bdbf0b577c8cb790f13bea88c797a7ee2f3 Mon Sep 17 00:00:00 2001 From: jpklein Date: Mon, 8 Sep 2014 02:09:21 -0400 Subject: [PATCH 35/86] Refactor rally cron posting function --- scripts/include/rallycron.inc.php | 24 ++++++++++++++++++++++++ scripts/include/slack.php | 23 ----------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index cb538b5..8f09bed 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -69,3 +69,27 @@ function SendRallyCommentNotifications($items) return $success; } + +function SendIncomingWebHookMessage($channel, $payload, $attachments) +{ + global $config; + + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; + + $reply = slack_incoming_hook_post_with_attachments( + $config['slack']['hook'], + $config['rally']['botname'], + $channel, + $config['rally']['boticon'], + $payload, + $attachments + ); + + $success = ($reply == 'ok'); + if (!$success) { + trigger_error('Unable to send Incoming WebHook message: ' . $reply); + } + return $success; +} + diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 1f62e8d..eea5a1a 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -74,29 +74,6 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } -function SendIncomingWebHookMessage($channel, $payload, $attachments) -{ - global $config; - - //allow bot to display formatted attachment text - $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; - - $reply = slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $channel, - $config['rally']['boticon'], - $payload, - $attachments - ); - - $success = ($reply == 'ok'); - if (!$success) { - trigger_error('Unable to send Incoming WebHook message: ' . $reply); - } - return $success; -} - function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( From 907eed9ecadd192da9592fb1ccb2342217604388 Mon Sep 17 00:00:00 2001 From: jpklein Date: Mon, 8 Sep 2014 02:13:23 -0400 Subject: [PATCH 36/86] Refactor rally cron posting function --- scripts/include/rallycron.inc.php | 24 ++++++++++++++++++++++++ scripts/include/slack.php | 23 ----------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index 6fcb918..981f500 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -155,3 +155,27 @@ function SendRallyUpdateNotifications($items) return $success; } + +function SendIncomingWebHookMessage($channel, $payload, $attachments) +{ + global $config; + + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; + + $reply = slack_incoming_hook_post_with_attachments( + $config['slack']['hook'], + $config['rally']['botname'], + $channel, + $config['rally']['boticon'], + $payload, + $attachments + ); + + $success = ($reply == 'ok'); + if (!$success) { + trigger_error('Unable to send Incoming WebHook message: ' . $reply); + } + return $success; +} + diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 7a1dc21..c8d1be3 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -77,29 +77,6 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload return curl_post($uri, $data_string); } -function SendIncomingWebHookMessage($channel, $payload, $attachments) -{ - global $config; - - //allow bot to display formatted attachment text - $attachments->mrkdwn_in = ['pretext', 'text', 'title', 'fields']; - - $reply = slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $channel, - $config['rally']['boticon'], - $payload, - $attachments - ); - - $success = ($reply == 'ok'); - if (!$success) { - trigger_error('Unable to send Incoming WebHook message: ' . $reply); - } - return $success; -} - function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( From 4559f6ae2d857f0d00da3db53bbd4eb05a583f90 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:12:47 -0400 Subject: [PATCH 37/86] Add an overview of rallybot notifications --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c01006e..6a4c638 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,17 @@ A place where I put some integration scripts for the popular Slack messaging pla #Rally Bot -We use this to query our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack. +Pushes notifications from Rally and allows users to fetch ticket details. Notifications are sent whenever: + +1. a comment is added to a ticket +2. a new defect, user story, or test case is created +3. a user story changes state + +The bot uses a combination of Rally's _State_ and _Ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and ready is checked, rallybot will announce that the story is ready for testing. After testing is completed by a member of the QA team, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. + + **Note**: As soon as all of a story's tasks are set to "Complete", Rally will automatically set the story's State to "Complete". So be sure to leave at least one task open in order to correctly track stories with defects. + +The bot can also be configured to respond to a /rallyme slash command to query our Rally instance for defects, tasks and user stories. E.g.: /rallyme DE12345 From 202d9e52178b857cc7cde65d32464b886dffff94 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:20:09 -0400 Subject: [PATCH 38/86] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a4c638..465f1ba 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ slack-integration A place where I put some integration scripts for the popular Slack messaging platform. - #Rally Bot Pushes notifications from Rally and allows users to fetch ticket details. Notifications are sent whenever: @@ -14,7 +13,7 @@ Pushes notifications from Rally and allows users to fetch ticket details. Notifi The bot uses a combination of Rally's _State_ and _Ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and ready is checked, rallybot will announce that the story is ready for testing. After testing is completed by a member of the QA team, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. - **Note**: As soon as all of a story's tasks are set to "Complete", Rally will automatically set the story's State to "Complete". So be sure to leave at least one task open in order to correctly track stories with defects. + **Note**: As soon as all of a story's tasks are completed, Rally will automatically set the story's state to "Completed". So be sure to leave at least one task open in order to correctly track stories with defects. The bot can also be configured to respond to a /rallyme slash command to query our Rally instance for defects, tasks and user stories. From 553c952607db4b9b8d3f6549bf48bec8e18bd043 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:25:41 -0400 Subject: [PATCH 39/86] Remove mention of unimplemented functionality from README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 465f1ba..9b9cb97 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,12 @@ A place where I put some integration scripts for the popular Slack messaging pla Pushes notifications from Rally and allows users to fetch ticket details. Notifications are sent whenever: -1. a comment is added to a ticket -2. a new defect, user story, or test case is created -3. a user story changes state +1. a new defect, user story, or test case is created +2. a user story changes state -The bot uses a combination of Rally's _State_ and _Ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and ready is checked, rallybot will announce that the story is ready for testing. After testing is completed by a member of the QA team, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. +The bot uses a combination of Rally's _State_ and _Ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. After testing is completed by a member of the QA team, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. - **Note**: As soon as all of a story's tasks are completed, Rally will automatically set the story's state to "Completed". So be sure to leave at least one task open in order to correctly track stories with defects. +> **Note**: As soon as all of a story's tasks are completed, Rally will automatically set the story's state to "Completed". So be sure to leave at least one task open in order to correctly track stories with defects. The bot can also be configured to respond to a /rallyme slash command to query our Rally instance for defects, tasks and user stories. From 84b5cf05a4d1ba3ea8efed291121d70979471094 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:29:05 -0400 Subject: [PATCH 40/86] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b9cb97..2da0286 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Pushes notifications from Rally and allows users to fetch ticket details. Notifi 1. a new defect, user story, or test case is created 2. a user story changes state -The bot uses a combination of Rally's _State_ and _Ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. After testing is completed by a member of the QA team, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. +The bot uses a combination of Rally's _state_ and _ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. When the QA team has completed testing, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. > **Note**: As soon as all of a story's tasks are completed, Rally will automatically set the story's state to "Completed". So be sure to leave at least one task open in order to correctly track stories with defects. From c4effb954a64e6512e9ce29d33bd8e806878d536 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:52:52 -0400 Subject: [PATCH 41/86] Update README.md with note of new functionality --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c01006e..1bc0c1e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A place where I put some integration scripts for the popular Slack messaging pla #Rally Bot -We use this to query our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack. +Pushes notifications from Rally whenever a comment is added to a ticket, and queries our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack. E.g.: /rallyme DE12345 From 31ce9c2982122b086a4d18943a73a4d5b61f254e Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 13:16:56 -0400 Subject: [PATCH 42/86] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4cc2c6..2e8345f 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Pushes notifications from Rally and allows users to fetch ticket details. Notifi 2. a new defect, user story, or test case is created 3. a user story changes state -The bot uses a combination of Rally's _state_ and _ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. When the QA team has completed testing, they may either: 1) uncheck the Ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for review. +The bot uses a combination of Rally's _state_ and _ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. When the QA team has completed testing, they may either: 1) uncheck the ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for acceptance. -> **Note**: As soon as all of a story's tasks are completed, Rally will automatically set the story's state to "Completed". So be sure to leave at least one task open in order to correctly track stories with defects. +> **Note**: Rally automatically sets a story's state to "Completed" when all of its tasks are completed, so be sure to leave at least one open task in order to correctly track stories with defects. The bot can also be configured to respond to a /rallyme slash command to query our Rally instance for defects, tasks and user stories. From 59d4f661f626f914711063518c5ef6b2273f1061 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 14:56:55 -0400 Subject: [PATCH 43/86] Fix bug introduced in ab23b9e (merge branch issue10) --- scripts/include/rally.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 1dd41bd..eeebf9b 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,7 +3,7 @@ $RALLY_URL = 'https://rally1.rallydev.com/'; $RALLY_TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.u\Z'; -function CallAPI($uri) +function CallAPI($url) { global $RALLY_USERNAME, $RALLY_PASSWORD; From 5010c3052cfb692123d9624db49504fffe410f41 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 15:07:49 -0400 Subject: [PATCH 44/86] Fix bug introduced in a9ffe3e (merge branch issue4) --- scripts/rallycron.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/rallycron.php b/scripts/rallycron.php index 9374aad..fd63019 100644 --- a/scripts/rallycron.php +++ b/scripts/rallycron.php @@ -5,6 +5,8 @@ date_default_timezone_set('UTC'); $since = date($RALLY_TIMESTAMP_FORMAT, time() - $CRON_INTERVAL); +$result = NULL; + if ($items = FetchLatestRallyComments($since)) { $result = SendRallyCommentNotifications($items); } From 6461ba9e74039040008d7cace7ea71298432304c Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 15:21:35 -0400 Subject: [PATCH 45/86] Update styling of state-change notifications (for mobile users) --- scripts/include/rallycron.inc.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php index 981f500..af951db 100644 --- a/scripts/include/rallycron.inc.php +++ b/scripts/include/rallycron.inc.php @@ -134,11 +134,12 @@ function SendRallyUpdateNotifications($items) $item['state'] = ' is ready for acceptance'; $color = '#6CC644'; //github green } + $item['state'] = $item['id'] . $item['state']; $pretext = em($item['type'] . ' updated by ' . $item['user']); - $text = b($item['id']) . $item['state']; - $fields = array(MakeField('', $slug)); - $fallback = $item['type'] . ' ' . $item['id'] . $item['state']; + $text = ''; + $fields = array(MakeField($item['state'], $slug)); + $fallback = $item['type'] . ' ' . $item['state']; //display a link to the new artifact as a message attachment } else { From 12d12b6a07cdb4629ad8013c7602a28fb0dd8374 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 15:38:57 -0400 Subject: [PATCH 46/86] Allow bot to link at-mentions in Rally comments to Slack users --- scripts/include/slack.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index eea5a1a..1eb0d44 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -81,7 +81,9 @@ function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, - "attachments"=>array($attachments)); + "attachments" => array($attachments), + 'link_names' => 1 //allow bot to linkify at-mentions in attachments + ); $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); $data_string = strtr($data_string, array('\\\\n' => '\n')); //unescape slashes in newline characters From 4eba754126696f81f7ac64dfa0a2ea10ece6c8d9 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 10:56:38 -0400 Subject: [PATCH 47/86] WIP! Rearchitect rallyme script to handle different output methods --- scripts/config/default.config.php | 3 +++ scripts/include/rallyme.inc.php | 19 +++++++++++++++++++ scripts/rallyme.php | 12 +++++++----- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 scripts/config/default.config.php create mode 100644 scripts/include/rallyme.inc.php diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php new file mode 100644 index 0000000..2a80113 --- /dev/null +++ b/scripts/config/default.config.php @@ -0,0 +1,3 @@ +Text); - -$result = HandleItem($slackCommand, $rallyFormattedId); -?> \ No newline at end of file +if (isset($_REQUEST['token']) && $_REQUEST['token'] == $SLACK_OUTGOING_HOOK_TOKEN && isset($_REQUEST['text'])) { + $payload = FetchArtifactPayload($_REQUEST['text']); + $result = isset($_REQUEST['command']) ? SendArtifactPayload($payload) : ReturnArtifactPayload($payload); +} From 69129a12e53c66aff2c97a2e2d9a47a0a3b19f1a Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 17:42:12 -0400 Subject: [PATCH 48/86] Return details of Rally defects as Slack-formatted JSON in response body --- scripts/include/rally.php | 21 ++-- scripts/include/rallyme.inc.php | 181 ++++++++++++++++++++++++++++++++ scripts/include/slack.php | 23 +++- 3 files changed, 212 insertions(+), 13 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 9713410..40c93fb 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -1,6 +1,7 @@ text, $payload->attachments); - + if($result=='Invalid channel specified'){ die("Sorry, the rallyme command can't post messages to your private chat.\n"); } - + if($result!="ok"){ print_r($result."\n"); print_r(json_encode($payload)); @@ -71,11 +72,11 @@ function postit($channel_name, $payload, $attachments){ global $config, $slackCommand; return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, + $config['slack']['hook'], + $config['rally']['botname'], + $slackCommand->ChannelName, + $config['rally']['boticon'], + $payload, $attachments); } @@ -169,7 +170,7 @@ function GetDefectPayload($ref) array_push($fields,$firstattachment); global $slackCommand; - + $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; @@ -265,7 +266,7 @@ function GetRequirementPayload($ref) if($blocked) array_push($fields, MakeField("blocked",$blockedreason,true)); - + array_push($fields, MakeField("description",$short_description,false)); if($firstattachment!=null) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 516b2bd..a41889e 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -1,19 +1,200 @@ QueryResult->TotalResultCount == 0) { + die('Sorry, I couldn\'t find ' . $formatted_id); + } + + foreach ($Results->QueryResult->Results as $Result) { + if ($Result->_type == $artifact_type) { + return call_user_func($func, $Result); + } + } + die('Sorry, your search for "' . $formatted_id . '" was ambiguous.'); } +/** + * Prepares a table of fields attached to a Rally defect for display. + * + * @param object $Defect + * + * @return string[] + */ +function ParseDefectPayload($Defect) +{ + $state = $Defect->State; + if ($state == 'Closed') { + $Date = new DateTime($Defect->ClosedDate); + $state .= ' ' . $Date->format('M j'); + } + + $ret = array( + 'item_id' => $Defect->FormattedID, + 'item_url' => $Defect->_ref, + 'title' => $Defect->_refObjectName, + + 'Creator' => $Defect->SubmittedBy->_refObjectName, + 'Created' => $Defect->_CreatedAt, + 'Owner' => $Defect->Owner->_refObjectName, + 'State' => $state, + 'Priority' => $Defect->Priority, + 'Severity' => $Defect->Severity, + 'Description' => $Defect->Description, + ); + + if ($Defect->Attachments->Count > 0) { + $ret['Attachment'] = GetAttachmentLinks($Defect->Attachments->_ref); + } + + return $ret; +} + +/** + * Prepares a table of fields attached to a Rally artifact for display. + * + * @param object $Artifact + * + * @return string[] + */ +function ParseTaskPayload($Artifact) +{ + +} + +/** + * Prepares a table of fields attached to a Rally user story for display. + * + * @param object $Artifact + * + * @return string[] + */ +function ParseStoryPayload($Artifact) +{ + +} + +/** + * Returns an array of file links listed in a Rally attachment object. + * + * @param string $attachment_ref + * + * @return string[] + */ +function GetAttachmentLinks($attachment_ref) +{ + $url = $RALLY_BASE_URL . 'slm/attachment/'; + $ret = array(); + + $Attachments = CallAPI($attachment_ref); + foreach ($Attachments->QueryResult->Results as $Attachment) { + $filename = $Attachment->_refObjectName; + $link_url = $url . $Attachment->ObjectID . '/' . urlencode($filename); + $ret[$filename] = $link_url; + } + + return $ret; +} + +/** + * Posts artifact details to a Slack channel via an incoming webhook. + * + * @param string[] $payload + * + * @return mixed + */ function SendArtifactPayload($payload) { } +/** + * Returns artifact details as Slack-formatted JSON in the body of the response. + * + * @param string[] $payload + * + * @return mixed + */ function ReturnArtifactPayload($payload) { + $text = em('Details for ' . $payload['item_id'] . ' ' . l($payload['title'], $payload['item_url'])); + + foreach (array_slice($payload, 3) as $title => $value) { + switch ($title) { + + case 'Attachment': + case 'Parent': + $link_url = reset($value); + $value = l(key($value), $link_url); + break; + + case 'Block Reason': + $title = ''; + $value = SanitizeText($value); + break; + + case 'Description': + $value = TruncateText(SanitizeText($value), 300, $payload['item_url']); + $value = '\n> ' . strtr($value, ['\n' => '\n> ']); + } + + if ($title) { + $title .= ':'; + $text .= '\n`' . str_pad($title, 15) . '`\t' . $value; + } else { + $text .= '\n>' . $value; + } + } + + $data = ['text' => $text]; + $text = json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); + $text = strtr($text, ['\n' => 'n', '\t' => 't']); //fix double-escaped codes + return print_r($text); } diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 38639a2..16af231 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,11 +29,28 @@ function BuildSlashCommand($request) return $cmd; } +//text-formatting functions + +function SanitizeText($text) +{ + $text = strtr($text, array('
' => '\n', '

' => '\n', '

' => '\n')); + return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); +} + +function l($text, $url) +{ + return '<' . $url . '|' . $text . '>'; +} + +function em($text) +{ + return '_' . $text . '_'; +} function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - + $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user ); @@ -58,7 +75,7 @@ function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ $data = array( - "text" => $payload, + "text" => $payload, "channel" => "#".$channel, "username"=>$user, "icon_url"=>$icon, From fe3994df074b98bc0ce456f5bbccfcc280688ab2 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 18:28:29 -0400 Subject: [PATCH 49/86] Fix public link to defect --- scripts/include/rallyme.inc.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index a41889e..6125df8 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -69,6 +69,8 @@ function FetchArtifactPayload($command_text) */ function ParseDefectPayload($Defect) { + global $RALLY_BASE_URL; + $state = $Defect->State; if ($state == 'Closed') { $Date = new DateTime($Defect->ClosedDate); @@ -77,7 +79,7 @@ function ParseDefectPayload($Defect) $ret = array( 'item_id' => $Defect->FormattedID, - 'item_url' => $Defect->_ref, + 'item_url' => $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID, 'title' => $Defect->_refObjectName, 'Creator' => $Defect->SubmittedBy->_refObjectName, @@ -129,6 +131,7 @@ function ParseStoryPayload($Artifact) */ function GetAttachmentLinks($attachment_ref) { + global $RALLY_BASE_URL; $url = $RALLY_BASE_URL . 'slm/attachment/'; $ret = array(); From 1600e92825c3c446db3146fcfe8105a2e2f807d3 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 19:19:08 -0400 Subject: [PATCH 50/86] Move existing GetDefectPayload to rallyme include file --- scripts/include/rally.php | 84 --------------------------------- scripts/include/rallyme.inc.php | 83 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 84 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 40c93fb..834409f 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -96,90 +96,6 @@ function GetRallyAttachmentLink($attachmentRef) return $linktxt; } -function GetDefectPayload($ref) -{ - global $show,$requesting_user_name; - - $object = CallAPI($ref); - - $defect = $object->Defect; - - $projecturi = $defect->Project->_ref; - - $title = $defect->_refObjectName; - $description = $defect->Description; - $owner = $defect->Owner->_refObjectName; - $submitter = $defect->SubmittedBy->_refObjectName; - $project = $object->Project->_refObjectName; - $created = $defect->_CreatedAt; - $state = $defect->State; - $priority = $defect->Priority; - $severity = $defect->Severity; - $frequency = $defect->c_Frequency; - $foundinbuild = $defect->FoundInBuild; - - $short_description = TruncateText(strip_tags($description), 200); - - $ProjectFull = CallAPI($projecturi); - $projectid = $ProjectFull->Project->ObjectID; - $defectid = $defect->ObjectID; - $projectName = $defect->Project->_refObjectName; - $itemid = $defect->FormattedID; - - $attachmentcount = $defect->Attachments->Count; - - $firstattachment = null; - if($attachmentcount>0) - { - $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); - } - - $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; - - $enctitle = urlencode($title); - $linktext = "<{$defecturi}|{$enctitle}>"; - - $color = "bad"; - - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - - $fields = array( - MakeField("link",$linktext,false), - - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), - - MakeField("project",$projectName,true), - MakeField("created",$created,true), - - MakeField("submitter",$submitter,true), - MakeField("state",$state,true), - - MakeField("priority",$priority,true), - MakeField("severity",$severity,true), - - MakeField("frequency",$frequency,true), - MakeField("found in",$foundinbuild,true), - - MakeField("description",$short_description,false) - ); - - if($firstattachment!=null) - array_push($fields,$firstattachment); - - global $slackCommand; - - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok, {$userlink}, here's the defect you requested."; - - $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); - return $obj; -} - function GetRequirementPayload($ref) { $object = CallAPI($ref); diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 6125df8..ea229a2 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -98,6 +98,89 @@ function ParseDefectPayload($Defect) return $ret; } +function GetDefectPayload($ref) +{ + global $show, $requesting_user_name; + + $object = CallAPI($ref); + + $defect = $object->Defect; + + $projecturi = $defect->Project->_ref; + + $title = $defect->_refObjectName; + $description = $defect->Description; + $owner = $defect->Owner->_refObjectName; + $submitter = $defect->SubmittedBy->_refObjectName; + $project = $object->Project->_refObjectName; + $created = $defect->_CreatedAt; + $state = $defect->State; + $priority = $defect->Priority; + $severity = $defect->Severity; + $frequency = $defect->c_Frequency; + $foundinbuild = $defect->FoundInBuild; + + $short_description = TruncateText(strip_tags($description), 200); + + $ProjectFull = CallAPI($projecturi); + $projectid = $ProjectFull->Project->ObjectID; + $defectid = $defect->ObjectID; + $projectName = $defect->Project->_refObjectName; + $itemid = $defect->FormattedID; + + $attachmentcount = $defect->Attachments->Count; + + $firstattachment = null; + if ($attachmentcount > 0) { + $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } + + $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; + + $enctitle = urlencode($title); + $linktext = "<{$defecturi}|{$enctitle}>"; + + $color = "bad"; + + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); + $short_description = TruncateText($clean_description, 300); + + $fields = array( + MakeField("link", $linktext, false), + + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), + + MakeField("project", $projectName, true), + MakeField("created", $created, true), + + MakeField("submitter", $submitter, true), + MakeField("state", $state, true), + + MakeField("priority", $priority, true), + MakeField("severity", $severity, true), + + MakeField("frequency", $frequency, true), + MakeField("found in", $foundinbuild, true), + + MakeField("description", $short_description, false) + ); + + if ($firstattachment != null) + array_push($fields, $firstattachment); + + global $slackCommand; + + $userlink = BuildUserLink($slackCommand->UserName); + $user_message = "Ok, {$userlink}, here's the defect you requested."; + + $obj = new stdClass; + $obj->text = ""; + $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); + return $obj; +} + /** * Prepares a table of fields attached to a Rally artifact for display. * From 233f2b0e72182da3d6a0410eff4310d13f7bbb8a Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 19:24:57 -0400 Subject: [PATCH 51/86] Re-engineer GetDefectPayload interface for Fetch routine --- scripts/include/rallyme.inc.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index ea229a2..9d82283 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -98,14 +98,10 @@ function ParseDefectPayload($Defect) return $ret; } -function GetDefectPayload($ref) +function GetDefectPayload($defect) { global $show, $requesting_user_name; - $object = CallAPI($ref); - - $defect = $object->Defect; - $projecturi = $defect->Project->_ref; $title = $defect->_refObjectName; From f185ecd23cabe654917ed140086a75f6a035eb7f Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 29 Sep 2014 19:42:13 -0400 Subject: [PATCH 52/86] Remove unused & single-use variables in GetDefectPayload --- scripts/include/rallyme.inc.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 9d82283..10ef0f1 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -100,11 +100,6 @@ function ParseDefectPayload($Defect) function GetDefectPayload($defect) { - global $show, $requesting_user_name; - - $projecturi = $defect->Project->_ref; - - $title = $defect->_refObjectName; $description = $defect->Description; $owner = $defect->Owner->_refObjectName; $submitter = $defect->SubmittedBy->_refObjectName; @@ -116,25 +111,21 @@ function GetDefectPayload($defect) $frequency = $defect->c_Frequency; $foundinbuild = $defect->FoundInBuild; - $short_description = TruncateText(strip_tags($description), 200); - - $ProjectFull = CallAPI($projecturi); + $ProjectFull = CallAPI($defect->Project->_ref); $projectid = $ProjectFull->Project->ObjectID; $defectid = $defect->ObjectID; $projectName = $defect->Project->_refObjectName; $itemid = $defect->FormattedID; - $attachmentcount = $defect->Attachments->Count; - $firstattachment = null; - if ($attachmentcount > 0) { + if ($defect->Attachments->Count > 0) { $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); $firstattachment = MakeField("attachment", $linktxt, false); } $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; - $enctitle = urlencode($title); + $enctitle = urlencode($defect->_refObjectName); $linktext = "<{$defecturi}|{$enctitle}>"; $color = "bad"; @@ -163,8 +154,9 @@ function GetDefectPayload($defect) MakeField("description", $short_description, false) ); - if ($firstattachment != null) + if ($firstattachment != null) { array_push($fields, $firstattachment); + } global $slackCommand; From 6d2a966dbb49753200aebf4fc91f7d90af83aaf8 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 01:45:33 -0400 Subject: [PATCH 53/86] Refactor defect link creation in GetDefectPayload --- scripts/include/rallyme.inc.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 10ef0f1..e5d0b09 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -111,9 +111,6 @@ function GetDefectPayload($defect) $frequency = $defect->c_Frequency; $foundinbuild = $defect->FoundInBuild; - $ProjectFull = CallAPI($defect->Project->_ref); - $projectid = $ProjectFull->Project->ObjectID; - $defectid = $defect->ObjectID; $projectName = $defect->Project->_refObjectName; $itemid = $defect->FormattedID; @@ -123,10 +120,11 @@ function GetDefectPayload($defect) $firstattachment = MakeField("attachment", $linktxt, false); } - $defecturi = "https://rally1.rallydev.com/#/{$projectid}d/detail/defect/{$defectid}"; - $enctitle = urlencode($defect->_refObjectName); - $linktext = "<{$defecturi}|{$enctitle}>"; + $projectid = basename($defect->Project->_ref); + $defectid = $defect->ObjectID; + $defecturl = $RALLY_BASE_URL . '#/' . $projectid . '/detail/defect/' . $defectid; + $linktext = l($enctitle, $defecturl); $color = "bad"; From 124b016cd4365f20293fcf3a483344fb642fcacf Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 01:57:59 -0400 Subject: [PATCH 54/86] Rearrange field definitions & delete unused vars in GetDefectPayload --- scripts/include/rallyme.inc.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index e5d0b09..2ba0886 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -100,10 +100,7 @@ function ParseDefectPayload($Defect) function GetDefectPayload($defect) { - $description = $defect->Description; - $owner = $defect->Owner->_refObjectName; $submitter = $defect->SubmittedBy->_refObjectName; - $project = $object->Project->_refObjectName; $created = $defect->_CreatedAt; $state = $defect->State; $priority = $defect->Priority; @@ -111,23 +108,25 @@ function GetDefectPayload($defect) $frequency = $defect->c_Frequency; $foundinbuild = $defect->FoundInBuild; - $projectName = $defect->Project->_refObjectName; - $itemid = $defect->FormattedID; - $firstattachment = null; if ($defect->Attachments->Count > 0) { $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); $firstattachment = MakeField("attachment", $linktxt, false); } + global $RALLY_API_URL; + $enctitle = urlencode($defect->_refObjectName); $projectid = basename($defect->Project->_ref); $defectid = $defect->ObjectID; $defecturl = $RALLY_BASE_URL . '#/' . $projectid . '/detail/defect/' . $defectid; $linktext = l($enctitle, $defecturl); - $color = "bad"; + $itemid = $defect->FormattedID; + $owner = $defect->Owner->_refObjectName; + $projectName = $defect->Project->_refObjectName; + $description = $defect->Description; $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); $short_description = TruncateText($clean_description, 300); @@ -161,6 +160,8 @@ function GetDefectPayload($defect) $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok, {$userlink}, here's the defect you requested."; + $color = "bad"; + $obj = new stdClass; $obj->text = ""; $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); From 63865483f6f69e06c798abc35ed7cf087c2350b5 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 02:13:49 -0400 Subject: [PATCH 55/86] Rearrange code in GetDefectPayload for readability --- scripts/include/rallyme.inc.php | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 2ba0886..961f3df 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -100,21 +100,12 @@ function ParseDefectPayload($Defect) function GetDefectPayload($defect) { - $submitter = $defect->SubmittedBy->_refObjectName; - $created = $defect->_CreatedAt; - $state = $defect->State; - $priority = $defect->Priority; - $severity = $defect->Severity; - $frequency = $defect->c_Frequency; - $foundinbuild = $defect->FoundInBuild; + global $slackCommand, $RALLY_BASE_URL; - $firstattachment = null; - if ($defect->Attachments->Count > 0) { - $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField("attachment", $linktxt, false); - } + $userlink = BuildUserLink($slackCommand->UserName); + $user_message = "Ok, {$userlink}, here's the defect you requested."; - global $RALLY_API_URL; + $color = "bad"; $enctitle = urlencode($defect->_refObjectName); $projectid = basename($defect->Project->_ref); @@ -125,6 +116,13 @@ function GetDefectPayload($defect) $itemid = $defect->FormattedID; $owner = $defect->Owner->_refObjectName; $projectName = $defect->Project->_refObjectName; + $created = $defect->_CreatedAt; + $submitter = $defect->SubmittedBy->_refObjectName; + $state = $defect->State; + $priority = $defect->Priority; + $severity = $defect->Severity; + $frequency = $defect->c_Frequency; + $foundinbuild = $defect->FoundInBuild; $description = $defect->Description; $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); @@ -151,20 +149,19 @@ function GetDefectPayload($defect) MakeField("description", $short_description, false) ); + $firstattachment = null; + if ($defect->Attachments->Count > 0) { + $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } + if ($firstattachment != null) { array_push($fields, $firstattachment); } - global $slackCommand; - - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok, {$userlink}, here's the defect you requested."; - - $color = "bad"; - $obj = new stdClass; $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $storyuri); + $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $defecturl); return $obj; } From ce2adc919bdc6cb16c863d3c7bf9bc0c0792b8b5 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 02:46:04 -0400 Subject: [PATCH 56/86] Remove unnecessary variable definitions in GetDefectPayload --- scripts/include/rallyme.inc.php | 45 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 961f3df..b3bf6f0 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -103,9 +103,9 @@ function GetDefectPayload($defect) global $slackCommand, $RALLY_BASE_URL; $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok, {$userlink}, here's the defect you requested."; + $user_message = 'Ok, ' . $userlink . ', here's the defect you requested.'; - $color = "bad"; + $color = 'bad'; $enctitle = urlencode($defect->_refObjectName); $projectid = basename($defect->Project->_ref); @@ -113,46 +113,35 @@ function GetDefectPayload($defect) $defecturl = $RALLY_BASE_URL . '#/' . $projectid . '/detail/defect/' . $defectid; $linktext = l($enctitle, $defecturl); - $itemid = $defect->FormattedID; - $owner = $defect->Owner->_refObjectName; - $projectName = $defect->Project->_refObjectName; - $created = $defect->_CreatedAt; - $submitter = $defect->SubmittedBy->_refObjectName; - $state = $defect->State; - $priority = $defect->Priority; - $severity = $defect->Severity; - $frequency = $defect->c_Frequency; - $foundinbuild = $defect->FoundInBuild; - $description = $defect->Description; $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); $short_description = TruncateText($clean_description, 300); $fields = array( - MakeField("link", $linktext, false), + MakeField('link', $linktext, false), - MakeField("id", $itemid, true), - MakeField("owner", $owner, true), + MakeField('id', $defect->FormattedID, true), + MakeField('owner', $defect->Owner->_refObjectName, true), - MakeField("project", $projectName, true), - MakeField("created", $created, true), + MakeField('project', $defect->Project->_refObjectName, true), + MakeField('created', $defect->_CreatedAt, true), - MakeField("submitter", $submitter, true), - MakeField("state", $state, true), + MakeField('submitter', $defect->SubmittedBy->_refObjectName, true), + MakeField('state', $defect->State, true), - MakeField("priority", $priority, true), - MakeField("severity", $severity, true), + MakeField('priority', $defect->Priority, true), + MakeField('severity', $defect->Severity, true), - MakeField("frequency", $frequency, true), - MakeField("found in", $foundinbuild, true), + MakeField('frequency', $defect->c_Frequency, true), + MakeField('found in', $defect->FoundInBuild, true), - MakeField("description", $short_description, false) + MakeField('description', $short_description, false) ); $firstattachment = null; if ($defect->Attachments->Count > 0) { $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField("attachment", $linktxt, false); + $firstattachment = MakeField('attachment', $linktxt, false); } if ($firstattachment != null) { @@ -160,8 +149,8 @@ function GetDefectPayload($defect) } $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $color, $fields, $defecturl); + $obj->text = ''; + $obj->attachments = MakeAttachment($user_message, '', $color, $fields, $defecturl); return $obj; } From 8d3386ad66065bfdb61d4ac71b7801d5b2bd4ed8 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 03:01:31 -0400 Subject: [PATCH 57/86] Micro-optimize object declaration to piss off ircmaxell --- scripts/include/rallyme.inc.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index b3bf6f0..1c34244 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -148,10 +148,8 @@ function GetDefectPayload($defect) array_push($fields, $firstattachment); } - $obj = new stdClass; - $obj->text = ''; - $obj->attachments = MakeAttachment($user_message, '', $color, $fields, $defecturl); - return $obj; + $payload = array('text' => '', 'attachments' => MakeAttachment($user_message, '', $color, $fields, $defecturl)); + return (object) $payload; } /** From 39bb4795b527b8a53690858e74dc182ce6ef961c Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 03:07:05 -0400 Subject: [PATCH 58/86] Update rallyme.inc.php --- scripts/include/rallyme.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 1c34244..05655f0 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -103,7 +103,7 @@ function GetDefectPayload($defect) global $slackCommand, $RALLY_BASE_URL; $userlink = BuildUserLink($slackCommand->UserName); - $user_message = 'Ok, ' . $userlink . ', here's the defect you requested.'; + $user_message = 'Ok, ' . $userlink . ', here\'s the defect you requested.'; $color = 'bad'; From 7263d8c18a388fe3667ec081c2d36b80c3ff7b20 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 10:08:24 -0400 Subject: [PATCH 59/86] Move BuildUserLink to Slack library file --- scripts/include/rally.php | 6 ------ scripts/include/slack.php | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 834409f..7219916 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -249,12 +249,6 @@ function FindRequirement($id) return GetFirstObjectFromSearchResult("HierarchicalRequirement", $searchresult); } -function BuildUserLink($username) -{ - $userlink = ""; - return $userlink; -} - function GetArtifactQueryUri($id) { global $config; diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 16af231..efc4974 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -31,6 +31,12 @@ function BuildSlashCommand($request) //text-formatting functions +function BuildUserLink($username) +{ + $userlink = ""; + return $userlink; +} + function SanitizeText($text) { $text = strtr($text, array('
' => '\n', '

' => '\n', '

' => '\n')); From 4fc5427d0dcd4bc09f541dc66c71902279f5073d Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 10:12:16 -0400 Subject: [PATCH 60/86] Generalize Slack's BuildUserLink --- scripts/config/default.config.php | 1 + scripts/include/slack.php | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 2a80113..955b245 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -1,3 +1,4 @@ "; - return $userlink; + global $SLACK_SUBDOMAIN; + + $userlink = ''; + return $userlink; } function SanitizeText($text) From 47fd3fded1e6b66ab7f2918daa69d4dec53bba33 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 14:15:12 -0400 Subject: [PATCH 61/86] Remove deprecated functions --- scripts/include/rally.php | 68 ------------------------------ scripts/include/rallyme.config.php | 1 - 2 files changed, 69 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 7219916..4ee6eeb 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,28 +3,6 @@ $RALLY_BASE_URL = 'https://rally1.rallydev.com/'; $RALLY_API_URL = $RALLY_BASE_URL . 'slm/webservice/v2.0/'; -function HandleItem($slackCommand, $rallyFormattedId) -{ - $rallyItemType = substr($rallyFormattedId,0,2); - - switch($rallyItemType){ - - case "DE": - return HandleDefect($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - case "US": - case "TA": - return HandleStory($rallyFormattedId, $slackCommand->ChannelName); - die; - break; - default: - print_r("Sorry, I don't know what kind of rally object {$rallyFormattedId} is. If you need rallyme to work with these, buy a :beer:. I hear he likes IPAs."); - die; - break; - } -} - function HandleDefect($id, $channel_name) { $defectref = FindDefect($id); @@ -255,52 +233,6 @@ function GetArtifactQueryUri($id) return str_replace("[[ID]]", $id, $config['rally']['artifactquery']); } -function GetDefectQueryUri($id) -{ - global $config; - return str_replace("[[ID]]", $id, $config['rally']['defectquery']); -} - -function FindDefect($id) -{ - $query = GetDefectQueryUri($id); - $searchresult = CallAPI($query); - - $count = GetCount($searchresult); - if($count == 0) - NotFound($id); - - return GetFirstObjectFromSearchResult("Defect", $searchresult); -} - -function GetCount($searchresult) -{ - return $searchresult->QueryResult->TotalResultCount; -} - -function NotFound($id) -{ - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I couldn't find {$id}");die; -} - - -function GetFirstObjectFromSearchResult($objectName, $result) -{ - foreach ($result->QueryResult->Results as $result) - { - if($result->_type == $objectName) - return $result->_ref; - } - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry @{$userlink}, your search for '{$slackCommand->Text}' was ambiguous.:\n"); - print_r("Here's what Rally told me:\n"); - print_r($result); - die; -} - function TruncateText($text, $len) { if(strlen($text) <= $len) diff --git a/scripts/include/rallyme.config.php b/scripts/include/rallyme.config.php index 9e0f85d..2170aff 100644 --- a/scripts/include/rallyme.config.php +++ b/scripts/include/rallyme.config.php @@ -1,7 +1,6 @@ Date: Tue, 30 Sep 2014 16:59:48 -0400 Subject: [PATCH 62/86] Report user errors either via incoming webhook or response body --- scripts/include/rallyme.inc.php | 63 +++++++++++++++++------- scripts/include/slack.php | 85 ++++++++++++++++++++++----------- 2 files changed, 105 insertions(+), 43 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 05655f0..ae333e6 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -6,6 +6,8 @@ require_once('slack.php'); require_once('rally.php'); +set_error_handler('_HandleRallyMeErrors', E_USER_ERROR); + /** * Parses artifact ID from request, queries Rally, and selects handler to gather * field values. @@ -21,43 +23,76 @@ function FetchArtifactPayload($command_text) list($formatted_id) = explode(' ', trim($command_text)); $formatted_id = strtoupper($formatted_id); - $artifact_query = $RALLY_API_URL; + //handle item + $query_url = $RALLY_API_URL; switch (substr($formatted_id, 0, 2)) { - case 'DE': - $artifact_query .= 'defect'; + case 'DE': //find defect + $query_url .= 'defect'; $artifact_type = 'Defect'; $func = 'ParseDefectPayload'; break; case 'TA': - $artifact_query .= 'artifact'; + $query_url .= 'artifact'; $artifact_type = 'Task'; $func = 'ParseTaskPayload'; break; case 'US': - $artifact_query .= 'artifact'; + $query_url .= 'artifact'; $artifact_type = 'HierarchicalRequirement'; $func = 'ParseStoryPayload'; break; default: - die('Sorry, I don\'t know how to handle "' . $command_text . '". You can look up user stories, defects, and tasks by ID, like "DE1234".'); + trigger_error('Sorry, @user, I don\'t know how to handle "' . $command_text . '". You can look up user stories, defects, and tasks by ID, like "DE1234".', E_USER_ERROR); } - $artifact_query .= '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; + $query_url .= '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; - $Results = CallAPI($artifact_query); - if ($Results->QueryResult->TotalResultCount == 0) { - die('Sorry, I couldn\'t find ' . $formatted_id); + $Results = CallAPI($query_url); + if ($Results->QueryResult->TotalResultCount == 0) { //get count + trigger_error('Sorry, @user, I couldn\'t find ' . $formatted_id, E_USER_ERROR); //not found } + //get first object from search result foreach ($Results->QueryResult->Results as $Result) { if ($Result->_type == $artifact_type) { return call_user_func($func, $Result); } } - die('Sorry, your search for "' . $formatted_id . '" was ambiguous.'); + trigger_error('Sorry, @user, your search for "' . $formatted_id . '" was ambiguous.', E_USER_ERROR); +} + +/** + * Notifies Slack users of errors either via an incoming webhook or in the body + * of the HTTP response. + * + * @param int $errno + * @param string $errstr + * + * @return void + */ +function _HandleRallyMeErrors($errno, $errstr) +{ + //assume at-mentions are linkified over either transmission channel + $user = '@' . $_REQUEST['user_name']; + $errstr = strtr($errstr, array('@user' => $user)); + + if (isSlashCommand()) { + //use an incoming webhook to report error + return slack_incoming_hook_post( + $config['slack']['hook'], + $config['rally']['botname'], + $_REQUEST['channel_name'], + $config['rally']['boticon'], + NULL, + $errstr + ); + } else { + //otherwise return Slack-formatted JSON in the response body + return PrintJsonResponse($errstr); + } } /** @@ -249,9 +284,5 @@ function ReturnArtifactPayload($payload) } } - $data = ['text' => $text]; - $text = json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); - $text = strtr($text, ['\n' => 'n', '\t' => 't']); //fix double-escaped codes - - return print_r($text); + return PrintJsonResponse($text); } diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 2ba5284..39251b4 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,6 +29,16 @@ function BuildSlashCommand($request) return $cmd; } +/** + * Determine if the incoming request was made via a slash command. + * + * @return boolean + */ +function isSlashCommand() +{ + return isset($_REQUEST['command']) ? $_REQUEST['command'] : FALSE; +} + //text-formatting functions function BuildUserLink($username) @@ -55,46 +65,67 @@ function em($text) return '_' . $text . '_'; } -function slack_incoming_hook_post($uri, $user, $channel, $icon, $emoji, $payload){ - +function slack_incoming_hook_post($url, $user, $channel, $icon, $emoji, $payload) +{ $data = array( - "text" => $payload, - "channel" => "#".$channel, - "username"=>$user - ); + 'text' => $payload, + 'channel' => '#' . $channel, + 'username' => $user, + 'link_names' => 1 + ); - if($icon!=null) - { + if ($icon != null) { $data['icon_url'] = $icon; - } - elseif($emoji!=null) - { + } elseif ($emoji != null) { $data['icon_emoji'] = $emoji; } - $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); - - mylog('sent.txt',$data_string); - return curl_post($uri, $data_string); + return _incoming_hook_post($url, $data); } - - -function slack_incoming_hook_post_with_attachments($uri, $user, $channel, $icon, $payload, $attachments){ +function slack_incoming_hook_post_with_attachments($url, $user, $channel, $icon, $payload, $attachments) +{ + //allow bot to display formatted attachment text + $attachments->mrkdwn_in = array('pretext', 'text', 'title', 'fields'); $data = array( - "text" => $payload, - "channel" => "#".$channel, - "username"=>$user, - "icon_url"=>$icon, - "attachments"=>array($attachments)); - - $data_string = "payload=" . json_encode($data, JSON_HEX_AMP|JSON_HEX_APOS|JSON_NUMERIC_CHECK|JSON_PRETTY_PRINT); - mylog('sent.txt',$data_string); - return curl_post($uri, $data_string); + 'text' => $payload, + 'channel' => '#' . $channel, + 'username' => $user, + 'icon_url' => $icon, + 'attachments' => array($attachments), + 'link_names' => 1 //allow bot to linkify at-mentions in attachments + ); + + return _incoming_hook_post($url, $data); +} + +function _incoming_hook_post($url, $data) +{ + $data_string = 'payload=' . json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); + $data_string = strtr($data_string, array('\\\\n' => '\n')); //unescape slashes in newline characters + + $result = curl_post($url, $data_string); + switch ($result) { + case 'ok': + mylog('sent.txt', $data_string); + return $result; + case 'Invalid channel specified': + exit('Unable to post messages to a private chat'); + default: + exit('Unable to send Incoming WebHook message: ' . $reply); + } } +function PrintJsonResponse($payload) +{ + $data = array('text' => $payload); + + $data_string = json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); + $data_string = strtr($data_string, ['\n' => 'n', '\t' => 't']); //fix double-escaped codes + return print_r($data_string); +} /* slack attachment format From 639b35842ea01919d8aae422c9564130f18f738c Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 17:11:23 -0400 Subject: [PATCH 63/86] Exit on user error --- scripts/include/rallyme.inc.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index ae333e6..e3f3062 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -81,7 +81,7 @@ function _HandleRallyMeErrors($errno, $errstr) if (isSlashCommand()) { //use an incoming webhook to report error - return slack_incoming_hook_post( + slack_incoming_hook_post( $config['slack']['hook'], $config['rally']['botname'], $_REQUEST['channel_name'], @@ -91,8 +91,10 @@ function _HandleRallyMeErrors($errno, $errstr) ); } else { //otherwise return Slack-formatted JSON in the response body - return PrintJsonResponse($errstr); + PrintJsonResponse($errstr); } + + exit(); } /** From 837360f5fdc5f50d0074895ff50d40d1d6c81b9e Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 17:24:50 -0400 Subject: [PATCH 64/86] Fix error in rallyme error-handler --- scripts/include/rallyme.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index e3f3062..41c5f07 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -75,6 +75,8 @@ function FetchArtifactPayload($command_text) */ function _HandleRallyMeErrors($errno, $errstr) { + global $config; + //assume at-mentions are linkified over either transmission channel $user = '@' . $_REQUEST['user_name']; $errstr = strtr($errstr, array('@user' => $user)); From e0aa70f13fa1095759e88d25b8b142a1d6bb111e Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 20:42:42 -0400 Subject: [PATCH 65/86] Enable posting two versions of defect info via incoming webhook --- scripts/config/default.config.php | 2 + scripts/include/rallyme.inc.php | 149 +++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 35 deletions(-) diff --git a/scripts/config/default.config.php b/scripts/config/default.config.php index 955b245..80f23db 100644 --- a/scripts/config/default.config.php +++ b/scripts/config/default.config.php @@ -2,3 +2,5 @@ $SLACK_OUTGOING_HOOK_TOKEN = 'REPLACE ME'; //used to validate requests to fetch details about Rally artifacts $SLACK_SUBDOMAIN = 'REPLACE ME'; //subdomain used to identify your team's instance, like 'cim' + +$RALLYME_DISPLAY_VERSION = 2; //alters the fields that are displayed for fetched artifacts diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 41c5f07..31f0bc1 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -108,33 +108,62 @@ function _HandleRallyMeErrors($errno, $errstr) */ function ParseDefectPayload($Defect) { - global $RALLY_BASE_URL; - - $state = $Defect->State; - if ($state == 'Closed') { - $Date = new DateTime($Defect->ClosedDate); - $state .= ' ' . $Date->format('M j'); - } + global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + + $title = $Defect->_refObjectName; + $header = array('title' => $title); + $item_url = $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID; + + switch ($RALLYME_DISPLAY_VERSION) { + + case 2: + $header['item_id'] = $Defect->FormattedID; + $header['item_url'] = $item_url; + + $state = $Defect->State; + if ($state == 'Closed') { + $Date = new DateTime($Defect->ClosedDate); + $state .= ' ' . $Date->format('M j'); + } + + $fields = array( + 'Creator' => $Defect->SubmittedBy->_refObjectName, + 'Created' => $Defect->_CreatedAt, + 'Owner' => $Defect->Owner->_refObjectName, + 'State' => $state, + 'Priority' => $Defect->Priority, + 'Severity' => $Defect->Severity, + 'Description' => $Defect->Description, + ); + if ($Defect->Attachments->Count > 0) { + $fields['Attachment'] = GetAttachmentLinks($Defect->Attachments->_ref); + } + break; - $ret = array( - 'item_id' => $Defect->FormattedID, - 'item_url' => $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID, - 'title' => $Defect->_refObjectName, - - 'Creator' => $Defect->SubmittedBy->_refObjectName, - 'Created' => $Defect->_CreatedAt, - 'Owner' => $Defect->Owner->_refObjectName, - 'State' => $state, - 'Priority' => $Defect->Priority, - 'Severity' => $Defect->Severity, - 'Description' => $Defect->Description, - ); + default: + $header['type'] = 'defect'; + + $fields = array( + 'link' => array($title => $item_url), + 'id' => $Defect->FormattedID, + 'owner' => $Defect->Owner->_refObjectName, + 'project' => $Defect->Project->_refObjectName, + 'created' => $Defect->_CreatedAt, + 'submitter' => $Defect->SubmittedBy->_refObjectName, + 'state' => $Defect->State, + 'priority' => $Defect->Priority, + 'severity' => $Defect->Severity, + 'frequency' => $Defect->c_Frequency, + 'found in' => $Defect->FoundInBuild, + 'description' => $Defect->Description, + ); + if ($Defect->Attachments->Count > 0) { + $fields['attachment'] = GetAttachmentLinks($Defect->Attachments->_ref); + } + break; - if ($Defect->Attachments->Count > 0) { - $ret['Attachment'] = GetAttachmentLinks($Defect->Attachments->_ref); } - - return $ret; + return array('header' => $header, 'fields' => $fields); } function GetDefectPayload($defect) @@ -226,16 +255,17 @@ function GetAttachmentLinks($attachment_ref) { global $RALLY_BASE_URL; $url = $RALLY_BASE_URL . 'slm/attachment/'; - $ret = array(); + $links = array(); $Attachments = CallAPI($attachment_ref); + foreach ($Attachments->QueryResult->Results as $Attachment) { $filename = $Attachment->_refObjectName; $link_url = $url . $Attachment->ObjectID . '/' . urlencode($filename); - $ret[$filename] = $link_url; + $links[$filename] = $link_url; } - return $ret; + return $links; } /** @@ -247,7 +277,44 @@ function GetAttachmentLinks($attachment_ref) */ function SendArtifactPayload($payload) { + global $config; + + $prextext = ArtifactPretext($payload['header']); + $color = 'bad'; + $fields = array(); + foreach ($payload['fields'] as $label => $value) { + $short = TRUE; + switch ($label) { + + case 'Parent': + case 'Attachment': + case 'link': + case 'attachment': + $link_url = reset($value); + $value = l(urlencode(key($value)), $link_url); + $short = FALSE; + break; + + case 'Description': + case 'description': + $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); + $short = FALSE; + break; + } + $fields[] = MakeField($label, $value, $short); + } + + $attachment = MakeAttachment($prextext, '', $color, $fields, $payload['header']['item_url']); + + return slack_incoming_hook_post_with_attachments( + $config['slack']['hook'], + $config['rally']['botname'], + $_REQUEST['channel_name'], + $config['rally']['boticon'], + '', + $attachment + ); } /** @@ -259,10 +326,10 @@ function SendArtifactPayload($payload) */ function ReturnArtifactPayload($payload) { - $text = em('Details for ' . $payload['item_id'] . ' ' . l($payload['title'], $payload['item_url'])); + $text = ArtifactPretext($payload['header']); - foreach (array_slice($payload, 3) as $title => $value) { - switch ($title) { + foreach ($payload['fields'] as $label => $value) { + switch ($label) { case 'Attachment': case 'Parent': @@ -271,18 +338,18 @@ function ReturnArtifactPayload($payload) break; case 'Block Reason': - $title = ''; + $label = ''; $value = SanitizeText($value); break; case 'Description': - $value = TruncateText(SanitizeText($value), 300, $payload['item_url']); + $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); $value = '\n> ' . strtr($value, ['\n' => '\n> ']); } - if ($title) { - $title .= ':'; - $text .= '\n`' . str_pad($title, 15) . '`\t' . $value; + if ($label) { + $label .= ':'; + $text .= '\n`' . str_pad($label, 15) . '`\t' . $value; } else { $text .= '\n>' . $value; } @@ -290,3 +357,15 @@ function ReturnArtifactPayload($payload) return PrintJsonResponse($text); } + +function ArtifactPretext($info) +{ + global $RALLYME_DISPLAY_VERSION; + + switch ($RALLYME_DISPLAY_VERSION) { + case 2: + return em('Details for ' . $info['item_id'] . ' ' . l($info['title'], $info['item_url'])); + default: + return 'Ok, @' . $_REQUEST['user_name'] . ', here\'s the ' . $info['type'] . ' you requested.'; + } +} From 2837b9b2679d1bfa04e1bf53aa8adf1f2ceddc93 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 20:49:12 -0400 Subject: [PATCH 66/86] Remove deprecated functions --- scripts/include/rally.php | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 4ee6eeb..e8f20fb 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,27 +3,6 @@ $RALLY_BASE_URL = 'https://rally1.rallydev.com/'; $RALLY_API_URL = $RALLY_BASE_URL . 'slm/webservice/v2.0/'; -function HandleDefect($id, $channel_name) -{ - $defectref = FindDefect($id); - - $payload = GetDefectPayload($defectref); - - $result = postit($channel_name, $payload->text, $payload->attachments); - - if($result=='Invalid channel specified'){ - die("Sorry, the rallyme command can't post messages to your private chat.\n"); - } - - if($result!="ok"){ - print_r($result."\n"); - print_r(json_encode($payload)); - print_r("\n"); - die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); - } - return $result; -} - function HandleStory($id, $channel_name) { @@ -190,11 +169,6 @@ function MakeField($title, $value, $short=false) return $attachmentfield; } -function getProjectPayload($projectRefUri) -{ - $project = CallAPI($projectRefUri); -} - function CallAPI($uri) { global $config; @@ -205,14 +179,6 @@ function CallAPI($uri) return $object; } - -function GetProjectID($projectref) -{ - $ProjectFull = CallAPI($projectref); - $projectid = $ProjectFull->Project->ObjectID; - return $projectid; -} - function FindRequirement($id) { $query = GetArtifactQueryUri($id); From 0ec67dae8993742f060edc96e7a38a5ff4afb8f4 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 20:56:02 -0400 Subject: [PATCH 67/86] Remove deprecated functions --- scripts/include/rallyme.inc.php | 54 --------------------------------- 1 file changed, 54 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 31f0bc1..768b3dc 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -166,60 +166,6 @@ function ParseDefectPayload($Defect) return array('header' => $header, 'fields' => $fields); } -function GetDefectPayload($defect) -{ - global $slackCommand, $RALLY_BASE_URL; - - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = 'Ok, ' . $userlink . ', here\'s the defect you requested.'; - - $color = 'bad'; - - $enctitle = urlencode($defect->_refObjectName); - $projectid = basename($defect->Project->_ref); - $defectid = $defect->ObjectID; - $defecturl = $RALLY_BASE_URL . '#/' . $projectid . '/detail/defect/' . $defectid; - $linktext = l($enctitle, $defecturl); - - $description = $defect->Description; - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - - $fields = array( - MakeField('link', $linktext, false), - - MakeField('id', $defect->FormattedID, true), - MakeField('owner', $defect->Owner->_refObjectName, true), - - MakeField('project', $defect->Project->_refObjectName, true), - MakeField('created', $defect->_CreatedAt, true), - - MakeField('submitter', $defect->SubmittedBy->_refObjectName, true), - MakeField('state', $defect->State, true), - - MakeField('priority', $defect->Priority, true), - MakeField('severity', $defect->Severity, true), - - MakeField('frequency', $defect->c_Frequency, true), - MakeField('found in', $defect->FoundInBuild, true), - - MakeField('description', $short_description, false) - ); - - $firstattachment = null; - if ($defect->Attachments->Count > 0) { - $linktxt = GetRallyAttachmentLink($defect->Attachments->_ref); - $firstattachment = MakeField('attachment', $linktxt, false); - } - - if ($firstattachment != null) { - array_push($fields, $firstattachment); - } - - $payload = array('text' => '', 'attachments' => MakeAttachment($user_message, '', $color, $fields, $defecturl)); - return (object) $payload; -} - /** * Prepares a table of fields attached to a Rally artifact for display. * From 269a8d20d78131f1ffa51d0c47f7532b2da8c2b1 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 21:17:45 -0400 Subject: [PATCH 68/86] Add abstractions related to script execution for reuse/elaboration --- scripts/include/slack.php | 12 ++++++++++++ scripts/rallyme.php | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 39251b4..26e4d37 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -29,6 +29,18 @@ function BuildSlashCommand($request) return $cmd; } +/** + * Determine if the incoming request is using the correct token. + * + * @return boolean + */ +function isValidOutgoingHookRequest() +{ + global $SLACK_OUTGOING_HOOK_TOKEN; + + return isset($_REQUEST['token']) && $_REQUEST['token'] == $SLACK_OUTGOING_HOOK_TOKEN; +} + /** * Determine if the incoming request was made via a slash command. * diff --git a/scripts/rallyme.php b/scripts/rallyme.php index a0aced7..5935b23 100644 --- a/scripts/rallyme.php +++ b/scripts/rallyme.php @@ -9,7 +9,7 @@ $result = NULL; -if (isset($_REQUEST['token']) && $_REQUEST['token'] == $SLACK_OUTGOING_HOOK_TOKEN && isset($_REQUEST['text'])) { +if (isValidOutgoingHookRequest() && isset($_REQUEST['text'])) { $payload = FetchArtifactPayload($_REQUEST['text']); - $result = isset($_REQUEST['command']) ? SendArtifactPayload($payload) : ReturnArtifactPayload($payload); + $result = isSlashCommand() ? SendArtifactPayload($payload) : ReturnArtifactPayload($payload); } From d98c212c92b63f0563160da96c13baa1cefe7c9c Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 21:34:18 -0400 Subject: [PATCH 69/86] Remove short-form array declaration for back-compatibility --- scripts/include/rallyme.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 768b3dc..5d3a7f3 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -290,7 +290,7 @@ function ReturnArtifactPayload($payload) case 'Description': $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); - $value = '\n> ' . strtr($value, ['\n' => '\n> ']); + $value = '\n> ' . strtr($value, array('\n' => '\n> ')); } if ($label) { From 8b0c1d862d6f070d75eac715d1750f0bb2499b67 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 21:36:24 -0400 Subject: [PATCH 70/86] Remove short-form array declaration for back-compatibility --- scripts/include/slack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 26e4d37..12bbd2a 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -134,7 +134,7 @@ function PrintJsonResponse($payload) $data = array('text' => $payload); $data_string = json_encode($data, JSON_HEX_AMP | JSON_HEX_APOS | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT); - $data_string = strtr($data_string, ['\n' => 'n', '\t' => 't']); //fix double-escaped codes + $data_string = strtr($data_string, array('\n' => 'n', '\t' => 't')); //fix double-escaped codes return print_r($data_string); } From 388e597d6692cabf159558798d71958ad54d67f5 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 21:51:34 -0400 Subject: [PATCH 71/86] Move GetRequirementPayload to rallyme include file for re-engineering --- scripts/include/rally.php | 106 -------------------------------- scripts/include/rallyme.inc.php | 100 ++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 106 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index e8f20fb..438d5f2 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -53,112 +53,6 @@ function GetRallyAttachmentLink($attachmentRef) return $linktxt; } -function GetRequirementPayload($ref) -{ - $object = CallAPI($ref); - - $requirement = null; - - if($object->HierarchicalRequirement) - { - $requirement = $object->HierarchicalRequirement; - } - elseif($object->Task) - { - $requirement = $object->Task; - } - else - { - $class = get_class($object); - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I can't handle a {$class} yet. I'll let @tdm know about it."); - die; - } - - $projecturi = $requirement->Project->_ref; - - $title = $requirement->_refObjectName; - - - $ProjectFull = CallAPI($projecturi); - $projectid = $ProjectFull->Project->ObjectID; - $storyid = $requirement->ObjectID; - $description = $requirement->Description; - $owner = $requirement->Owner->_refObjectName; - $projectName = $requirement->Project->_refObjectName; - $itemid = $requirement->FormattedID; - $created = $requirement->_CreatedAt; - $estimate = $requirement->PlanEstimate; - $hasparent = $requirement->HasParent; - $childcount = $requirement->DirectChildrenCount; - $state = $requirement->ScheduleState; - $blocked = $requirement->Blocked; - $blockedreason = $requirement->BlockedReason; - $ready = $requirement->Ready; - - $attachmentcount = $requirement->Attachments->Count; - - $firstattachment = null; - if($attachmentcount>0) - { - $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); - $firstattachment = MakeField("attachment",$linktxt,false); - } - - $parent = null; - if($hasparent) - $parent = $requirement->Parent->_refObjectName; - - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401|ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - - $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; - $enctitle = urlencode($title); - $linktext = "<{$storyuri}|{$enctitle}>"; - - $dovegray = "#CEC7B8"; - - - - $fields = array( - MakeField("link",$linktext,false), - MakeField("parent",$parent,false), - - MakeField("id",$itemid,true), - MakeField("owner",$owner,true), - - MakeField("project",$projectName,true), - MakeField("created",$created,true), - - MakeField("estimate",$estimate,true), - MakeField("state",$state,true)); - - if($childcount>0) - array_push($fields,MakeField("children",$childcount,true)); - - if($blocked) - array_push($fields, MakeField("blocked",$blockedreason,true)); - - array_push($fields, MakeField("description",$short_description,false)); - - if($firstattachment!=null) - array_push($fields,$firstattachment); - - - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok {$userlink}, here's the story you requested."; - - $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); -// print_r(json_encode($obj));die; - - return $obj; -} - - function MakeField($title, $value, $short=false) { $attachmentfield = array( diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 5d3a7f3..e44f6c0 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -190,6 +190,106 @@ function ParseStoryPayload($Artifact) } +function GetRequirementPayload($ref) +{ + $object = CallAPI($ref); + + $requirement = null; + + if ($object->HierarchicalRequirement) { + $requirement = $object->HierarchicalRequirement; + } elseif ($object->Task) { + $requirement = $object->Task; + } else { + $class = get_class($object); + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + print_r("Sorry {$userlink}, I can't handle a {$class} yet. I'll let @tdm know about it."); + die; + } + + $projecturi = $requirement->Project->_ref; + + $title = $requirement->_refObjectName; + + $ProjectFull = CallAPI($projecturi); + $projectid = $ProjectFull->Project->ObjectID; + $storyid = $requirement->ObjectID; + $description = $requirement->Description; + $owner = $requirement->Owner->_refObjectName; + $projectName = $requirement->Project->_refObjectName; + $itemid = $requirement->FormattedID; + $created = $requirement->_CreatedAt; + $estimate = $requirement->PlanEstimate; + $hasparent = $requirement->HasParent; + $childcount = $requirement->DirectChildrenCount; + $state = $requirement->ScheduleState; + $blocked = $requirement->Blocked; + $blockedreason = $requirement->BlockedReason; + $ready = $requirement->Ready; + + $attachmentcount = $requirement->Attachments->Count; + + $firstattachment = null; + if ($attachmentcount > 0) { + $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } + + $parent = null; + if ($hasparent) { + $parent = $requirement->Parent->_refObjectName; + } + + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); + $short_description = TruncateText($clean_description, 300); + + $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; + $enctitle = urlencode($title); + $linktext = "<{$storyuri}|{$enctitle}>"; + + $dovegray = "#CEC7B8"; + + $fields = array( + MakeField("link", $linktext, false), + MakeField("parent", $parent, false), + + MakeField("id", $itemid, true), + MakeField("owner", $owner, true), + + MakeField("project", $projectName, true), + MakeField("created", $created, true), + + MakeField("estimate", $estimate, true), + MakeField("state", $state, true) + ); + + if ($childcount > 0) { + array_push($fields, MakeField("children", $childcount, true)); + } + + if ($blocked) { + array_push($fields, MakeField("blocked", $blockedreason, true)); + } + + array_push($fields, MakeField("description", $short_description, false)); + + if ($firstattachment != null) { + array_push($fields, $firstattachment); + } + + global $slackCommand; + $userlink = BuildUserLink($slackCommand->UserName); + $user_message = "Ok {$userlink}, here's the story you requested."; + + $obj = new stdClass; + $obj->text = ""; + $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); + // print_r(json_encode($obj));die; + + return $obj; +} + /** * Returns an array of file links listed in a Rally attachment object. * From 99113c0c9092647ec52bc08ee2dc5a1aa4f45dd0 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 22:48:08 -0400 Subject: [PATCH 72/86] Refactor GetRequirementPayload to make it easier on my poor lizard brain --- scripts/include/rallyme.inc.php | 96 +++++++++++++++++---------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index e44f6c0..5cb32d4 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -167,7 +167,7 @@ function ParseDefectPayload($Defect) } /** - * Prepares a table of fields attached to a Rally artifact for display. + * Prepares a table of fields attached to a Rally task for display. * * @param object $Artifact * @@ -181,13 +181,35 @@ function ParseTaskPayload($Artifact) /** * Prepares a table of fields attached to a Rally user story for display. * - * @param object $Artifact + * @param object $Story * * @return string[] */ -function ParseStoryPayload($Artifact) +function ParseStoryPayload($Story) { + global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + + $title = $Story->_refObjectName; + $header = array('title' => $title); + $item_url = $RALLY_BASE_URL . '#/' . basename($Story->Project->_ref) . '/detail/userstory/' . $Story->ObjectID; + switch ($RALLYME_DISPLAY_VERSION) { + + case 2: + break; + + default: + $header['type'] = 'story'; + + $fields = array( + ); + if ($Story->Attachments->Count > 0) { + $fields['attachment'] = GetAttachmentLinks($Story->Attachments->_ref); + } + break; + + } + return array('header' => $header, 'fields' => $fields); } function GetRequirementPayload($ref) @@ -209,71 +231,51 @@ function GetRequirementPayload($ref) } $projecturi = $requirement->Project->_ref; - - $title = $requirement->_refObjectName; - $ProjectFull = CallAPI($projecturi); $projectid = $ProjectFull->Project->ObjectID; $storyid = $requirement->ObjectID; - $description = $requirement->Description; - $owner = $requirement->Owner->_refObjectName; - $projectName = $requirement->Project->_refObjectName; - $itemid = $requirement->FormattedID; - $created = $requirement->_CreatedAt; - $estimate = $requirement->PlanEstimate; - $hasparent = $requirement->HasParent; - $childcount = $requirement->DirectChildrenCount; - $state = $requirement->ScheduleState; - $blocked = $requirement->Blocked; - $blockedreason = $requirement->BlockedReason; - $ready = $requirement->Ready; - - $attachmentcount = $requirement->Attachments->Count; - - $firstattachment = null; - if ($attachmentcount > 0) { - $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); - $firstattachment = MakeField("attachment", $linktxt, false); - } - - $parent = null; - if ($hasparent) { - $parent = $requirement->Parent->_refObjectName; - } - - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; + $title = $requirement->_refObjectName; $enctitle = urlencode($title); $linktext = "<{$storyuri}|{$enctitle}>"; - $dovegray = "#CEC7B8"; + $parent = null; + if ($requirement->HasParent) { + $parent = $requirement->Parent->_refObjectName; + } $fields = array( MakeField("link", $linktext, false), MakeField("parent", $parent, false), - MakeField("id", $itemid, true), - MakeField("owner", $owner, true), + MakeField("id", $requirement->FormattedID, true), + MakeField("owner", $requirement->Owner->_refObjectName, true), - MakeField("project", $projectName, true), - MakeField("created", $created, true), + MakeField("project", $requirement->Project->_refObjectName, true), + MakeField("created", $requirement->_CreatedAt, true), - MakeField("estimate", $estimate, true), - MakeField("state", $state, true) + MakeField("estimate", $requirement->PlanEstimate, true), + MakeField("state", $requirement->ScheduleState, true) ); - if ($childcount > 0) { - array_push($fields, MakeField("children", $childcount, true)); + if ($requirement->DirectChildrenCount > 0) { + array_push($fields, MakeField("children", $requirement->DirectChildrenCount, true)); } - if ($blocked) { - array_push($fields, MakeField("blocked", $blockedreason, true)); + if ($requirement->Blocked) { + array_push($fields, MakeField("blocked", $requirement->BlockedReason, true)); } + $description = $requirement->Description; + $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); + $short_description = TruncateText($clean_description, 300); array_push($fields, MakeField("description", $short_description, false)); + $firstattachment = null; + if ($requirement->Attachments->Count > 0) { + $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); + $firstattachment = MakeField("attachment", $linktxt, false); + } if ($firstattachment != null) { array_push($fields, $firstattachment); } @@ -282,6 +284,8 @@ function GetRequirementPayload($ref) $userlink = BuildUserLink($slackCommand->UserName); $user_message = "Ok {$userlink}, here's the story you requested."; + $dovegray = "#CEC7B8"; + $obj = new stdClass; $obj->text = ""; $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); From f8a5e59d269b36fc1fed40861d04d4c2a501d836 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Tue, 30 Sep 2014 23:58:55 -0400 Subject: [PATCH 73/86] Handle sending user story details via incoming webhook --- scripts/include/rallyme.inc.php | 49 +++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 5cb32d4..d032a36 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -111,7 +111,7 @@ function ParseDefectPayload($Defect) global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; $title = $Defect->_refObjectName; - $header = array('title' => $title); + $header = array('title' => $title, 'type' => 'defect'); $item_url = $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID; switch ($RALLYME_DISPLAY_VERSION) { @@ -141,8 +141,6 @@ function ParseDefectPayload($Defect) break; default: - $header['type'] = 'defect'; - $fields = array( 'link' => array($title => $item_url), 'id' => $Defect->FormattedID, @@ -161,8 +159,8 @@ function ParseDefectPayload($Defect) $fields['attachment'] = GetAttachmentLinks($Defect->Attachments->_ref); } break; - } + return array('header' => $header, 'fields' => $fields); } @@ -190,25 +188,47 @@ function ParseStoryPayload($Story) global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; $title = $Story->_refObjectName; - $header = array('title' => $title); + $header = array('title' => $title, 'type' => 'story'); $item_url = $RALLY_BASE_URL . '#/' . basename($Story->Project->_ref) . '/detail/userstory/' . $Story->ObjectID; switch ($RALLYME_DISPLAY_VERSION) { case 2: - break; + // break; default: - $header['type'] = 'story'; + $parent = NULL; + if ($Story->HasParent) { + /** + * @todo perform lookup of parent's project ID to make this into + * a link; we can't assume it's in the same project + */ + $parent = $Story->Parent->_refObjectName; + } $fields = array( + 'link' => array($title => $item_url), + 'parent' => $parent, + 'id' => $Story->FormattedID, + 'owner' => $Story->Owner->_refObjectName, + 'project' => $Story->Project->_refObjectName, + 'created' => $Story->_CreatedAt, + 'estimate' => $Story->PlanEstimate, + 'state' => $Story->ScheduleState, ); + if ($Story->DirectChildrenCount > 0) { + $fields['children'] = $Story->DirectChildrenCount; + } + if ($Story->Blocked) { + $fields['blocked'] = $Story->BlockedReason; + } + $fields['description'] = $Story->Description; if ($Story->Attachments->Count > 0) { $fields['attachment'] = GetAttachmentLinks($Story->Attachments->_ref); } break; - } + return array('header' => $header, 'fields' => $fields); } @@ -330,7 +350,10 @@ function SendArtifactPayload($payload) global $config; $prextext = ArtifactPretext($payload['header']); - $color = 'bad'; + $color = '#CEC7B8'; //dove gray + if ($payload['header']['type'] == 'defect') { + $color = 'bad'; //purple + } $fields = array(); foreach ($payload['fields'] as $label => $value) { @@ -338,6 +361,11 @@ function SendArtifactPayload($payload) switch ($label) { case 'Parent': + case 'parent': + if (is_string($value)) { + $short = FALSE; + break; + } case 'Attachment': case 'link': case 'attachment': @@ -395,6 +423,7 @@ function ReturnArtifactPayload($payload) case 'Description': $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); $value = '\n> ' . strtr($value, array('\n' => '\n> ')); + break; } if ($label) { @@ -413,8 +442,10 @@ function ArtifactPretext($info) global $RALLYME_DISPLAY_VERSION; switch ($RALLYME_DISPLAY_VERSION) { + case 2: return em('Details for ' . $info['item_id'] . ' ' . l($info['title'], $info['item_url'])); + default: return 'Ok, @' . $_REQUEST['user_name'] . ', here\'s the ' . $info['type'] . ' you requested.'; } From 56ff06f288482d4ffc46a3379657b4e88149a192 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 00:08:16 -0400 Subject: [PATCH 74/86] Remove deprecated functions --- scripts/include/rally.php | 69 ------------------------- scripts/include/rallyme.config.php | 2 - scripts/include/rallyme.inc.php | 82 ------------------------------ 3 files changed, 153 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index 438d5f2..dbe992a 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -4,55 +4,6 @@ $RALLY_API_URL = $RALLY_BASE_URL . 'slm/webservice/v2.0/'; -function HandleStory($id, $channel_name) -{ - $ref = FindRequirement($id); - - $payload = GetRequirementPayload($ref); - - $result = postit($channel_name, $payload->text, $payload->attachments); - - if($result=='Invalid channel specified'){ - die("Sorry, the rallyme command can't post messages to your private chat.\n"); - } - - if($result!="ok"){ - print_r($result."\n"); - print_r(json_encode($payload)); - print_r("\n"); - die("Apparently the Rallyme script is having a problem. Ask about it. :frowning:"); - } - return $result; -} - -function postit($channel_name, $payload, $attachments){ - global $config, $slackCommand; - - return slack_incoming_hook_post_with_attachments( - $config['slack']['hook'], - $config['rally']['botname'], - $slackCommand->ChannelName, - $config['rally']['boticon'], - $payload, - $attachments); -} - - - -function GetRallyAttachmentLink($attachmentRef) -{ - $attachments = CallAPI($attachmentRef); - $firstattachment = $attachments->QueryResult->Results[0]; - - $attachmentname = $firstattachment->_refObjectName; - $encodedattachmentname = urlencode($attachmentname); - $id = $firstattachment->ObjectID; - - $uri = "https://rally1.rallydev.com/slm/attachment/{$id}/{$encodedattachmentname}"; - $linktxt = "<{$uri}|{$attachmentname}>"; - return $linktxt; -} - function MakeField($title, $value, $short=false) { $attachmentfield = array( @@ -73,26 +24,6 @@ function CallAPI($uri) return $object; } -function FindRequirement($id) -{ - $query = GetArtifactQueryUri($id); - - $searchresult = CallAPI($query); -// print_r($searchresult);die; - - $count = GetCount($searchresult); - if($count == 0) - NotFound($id); - - return GetFirstObjectFromSearchResult("HierarchicalRequirement", $searchresult); -} - -function GetArtifactQueryUri($id) -{ - global $config; - return str_replace("[[ID]]", $id, $config['rally']['artifactquery']); -} - function TruncateText($text, $len) { if(strlen($text) <= $len) diff --git a/scripts/include/rallyme.config.php b/scripts/include/rallyme.config.php index 2170aff..3bffea8 100644 --- a/scripts/include/rallyme.config.php +++ b/scripts/include/rallyme.config.php @@ -1,8 +1,6 @@ $header, 'fields' => $fields); } -function GetRequirementPayload($ref) -{ - $object = CallAPI($ref); - - $requirement = null; - - if ($object->HierarchicalRequirement) { - $requirement = $object->HierarchicalRequirement; - } elseif ($object->Task) { - $requirement = $object->Task; - } else { - $class = get_class($object); - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - print_r("Sorry {$userlink}, I can't handle a {$class} yet. I'll let @tdm know about it."); - die; - } - - $projecturi = $requirement->Project->_ref; - $ProjectFull = CallAPI($projecturi); - $projectid = $ProjectFull->Project->ObjectID; - $storyid = $requirement->ObjectID; - $storyuri = "https://rally1.rallydev.com/#/{$projectid}d/detail/userstory/{$storyid}"; - $title = $requirement->_refObjectName; - $enctitle = urlencode($title); - $linktext = "<{$storyuri}|{$enctitle}>"; - - $parent = null; - if ($requirement->HasParent) { - $parent = $requirement->Parent->_refObjectName; - } - - $fields = array( - MakeField("link", $linktext, false), - MakeField("parent", $parent, false), - - MakeField("id", $requirement->FormattedID, true), - MakeField("owner", $requirement->Owner->_refObjectName, true), - - MakeField("project", $requirement->Project->_refObjectName, true), - MakeField("created", $requirement->_CreatedAt, true), - - MakeField("estimate", $requirement->PlanEstimate, true), - MakeField("state", $requirement->ScheduleState, true) - ); - - if ($requirement->DirectChildrenCount > 0) { - array_push($fields, MakeField("children", $requirement->DirectChildrenCount, true)); - } - - if ($requirement->Blocked) { - array_push($fields, MakeField("blocked", $requirement->BlockedReason, true)); - } - - $description = $requirement->Description; - $clean_description = html_entity_decode(strip_tags($description), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); - $short_description = TruncateText($clean_description, 300); - array_push($fields, MakeField("description", $short_description, false)); - - $firstattachment = null; - if ($requirement->Attachments->Count > 0) { - $linktxt = GetRallyAttachmentLink($requirement->Attachments->_ref); - $firstattachment = MakeField("attachment", $linktxt, false); - } - if ($firstattachment != null) { - array_push($fields, $firstattachment); - } - - global $slackCommand; - $userlink = BuildUserLink($slackCommand->UserName); - $user_message = "Ok {$userlink}, here's the story you requested."; - - $dovegray = "#CEC7B8"; - - $obj = new stdClass; - $obj->text = ""; - $obj->attachments = MakeAttachment($user_message, "", $dovegray, $fields, $storyuri); - // print_r(json_encode($obj));die; - - return $obj; -} - /** * Returns an array of file links listed in a Rally attachment object. * From 0dda36d77995a5f51fefe21567a3b08fe63513b6 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 00:44:48 -0400 Subject: [PATCH 75/86] Handle sending user story or task details via incoming webhook --- scripts/include/rallyme.inc.php | 103 +++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 5d36544..4996804 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -110,8 +110,7 @@ function ParseDefectPayload($Defect) { global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; - $title = $Defect->_refObjectName; - $header = array('title' => $title, 'type' => 'defect'); + $header = array('title' => $Defect->_refObjectName, 'type' => 'defect'); $item_url = $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID; switch ($RALLYME_DISPLAY_VERSION) { @@ -142,7 +141,7 @@ function ParseDefectPayload($Defect) default: $fields = array( - 'link' => array($title => $item_url), + 'link' => array($Defect->_refObjectName => $item_url), 'id' => $Defect->FormattedID, 'owner' => $Defect->Owner->_refObjectName, 'project' => $Defect->Project->_refObjectName, @@ -167,13 +166,28 @@ function ParseDefectPayload($Defect) /** * Prepares a table of fields attached to a Rally task for display. * - * @param object $Artifact + * @param object $Task * * @return string[] */ -function ParseTaskPayload($Artifact) +function ParseTaskPayload($Task) { + global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + + $header = array('title' => $Task->_refObjectName, 'type' => 'task'); + $item_url = $RALLY_BASE_URL . '#/' . basename($Task->Project->_ref) . '/detail/task/' . $Task->ObjectID; + + switch ($RALLYME_DISPLAY_VERSION) { + + case 2: + // break; + + default: + $fields = CompileRequirementFields($Task, $item_url); + break; + } + return array('header' => $header, 'fields' => $fields); } /** @@ -187,8 +201,7 @@ function ParseStoryPayload($Story) { global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; - $title = $Story->_refObjectName; - $header = array('title' => $title, 'type' => 'story'); + $header = array('title' => $Story->_refObjectName, 'type' => 'story'); $item_url = $RALLY_BASE_URL . '#/' . basename($Story->Project->_ref) . '/detail/userstory/' . $Story->ObjectID; switch ($RALLYME_DISPLAY_VERSION) { @@ -197,41 +210,59 @@ function ParseStoryPayload($Story) // break; default: - $parent = NULL; - if ($Story->HasParent) { - /** - * @todo perform lookup of parent's project ID to make this into - * a link; we can't assume it's in the same project - */ - $parent = $Story->Parent->_refObjectName; - } - - $fields = array( - 'link' => array($title => $item_url), - 'parent' => $parent, - 'id' => $Story->FormattedID, - 'owner' => $Story->Owner->_refObjectName, - 'project' => $Story->Project->_refObjectName, - 'created' => $Story->_CreatedAt, - 'estimate' => $Story->PlanEstimate, - 'state' => $Story->ScheduleState, - ); - if ($Story->DirectChildrenCount > 0) { - $fields['children'] = $Story->DirectChildrenCount; - } - if ($Story->Blocked) { - $fields['blocked'] = $Story->BlockedReason; - } - $fields['description'] = $Story->Description; - if ($Story->Attachments->Count > 0) { - $fields['attachment'] = GetAttachmentLinks($Story->Attachments->_ref); - } + $fields = CompileRequirementFields($Story, $item_url); break; } return array('header' => $header, 'fields' => $fields); } +/** + * Prepare a table of field values for stories and tasks. + * + * Rally lumps stories and tasks together as types of "Hierarchical Requirements" + * and so the original version of this script rendered the same fields for both. + * + * @param object $Requirement + * @param string $item_url + * + * @return string[] + */ +function CompileRequirementFields($Requirement, $item_url) +{ + $parent = NULL; + if ($Requirement->HasParent) { + /** + * @todo perform lookup of parent's project ID to make this into + * a link; we can't assume it's in the same project + */ + $parent = $Requirement->Parent->_refObjectName; + } + + $fields = array( + 'link' => array($Requirement->_refObjectName => $item_url), + 'parent' => $parent, + 'id' => $Requirement->FormattedID, + 'owner' => $Requirement->Owner->_refObjectName, + 'project' => $Requirement->Project->_refObjectName, + 'created' => $Requirement->_CreatedAt, + 'estimate' => $Requirement->PlanEstimate, + 'state' => $Requirement->ScheduleState, + ); + if ($Requirement->DirectChildrenCount > 0) { + $fields['children'] = $Requirement->DirectChildrenCount; + } + if ($Requirement->Blocked) { + $fields['blocked'] = $Requirement->BlockedReason; + } + $fields['description'] = $Requirement->Description; + if ($Requirement->Attachments->Count > 0) { + $fields['attachment'] = GetAttachmentLinks($Requirement->Attachments->_ref); + } + + return $fields; +} + /** * Returns an array of file links listed in a Rally attachment object. * From 8119d5ceb287da7857661b8f16ecd0fbb52d2088 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 19:36:58 -0400 Subject: [PATCH 76/86] Refactor settings variables and includes --- scripts/include/slack.config.php | 2 +- scripts/rallyme.php | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/include/slack.config.php b/scripts/include/slack.config.php index e14cd95..b77a8e4 100644 --- a/scripts/include/slack.config.php +++ b/scripts/include/slack.config.php @@ -1,7 +1,7 @@ diff --git a/scripts/rallyme.php b/scripts/rallyme.php index 5935b23..847109c 100644 --- a/scripts/rallyme.php +++ b/scripts/rallyme.php @@ -1,10 +1,7 @@ Date: Wed, 1 Oct 2014 19:41:58 -0400 Subject: [PATCH 77/86] Refactor rallyme includes --- scripts/include/rallyme.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 4996804..7ebc894 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -2,9 +2,9 @@ /** * Responds to queries for information about defects, tasks, and user stories. */ -require_once('curl.php'); -require_once('slack.php'); -require_once('rally.php'); +require('curl.php'); +require('slack.php'); +require('rally.php'); set_error_handler('_HandleRallyMeErrors', E_USER_ERROR); From 5448eae9c6cc922d2338a89ea3f0048097104759 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 19:44:11 -0400 Subject: [PATCH 78/86] Specify artifact type in query URL to reduce resultset from Rally - Rally doesn't seem to look at the first two letters of the FormattedID and returns all artifacts with the same number unless the artifact type is specified in the URL :shrug: --- scripts/include/rallyme.inc.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 7ebc894..0120d74 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -28,19 +28,16 @@ function FetchArtifactPayload($command_text) switch (substr($formatted_id, 0, 2)) { case 'DE': //find defect - $query_url .= 'defect'; $artifact_type = 'Defect'; $func = 'ParseDefectPayload'; break; case 'TA': - $query_url .= 'artifact'; $artifact_type = 'Task'; $func = 'ParseTaskPayload'; break; case 'US': - $query_url .= 'artifact'; $artifact_type = 'HierarchicalRequirement'; $func = 'ParseStoryPayload'; break; @@ -48,7 +45,7 @@ function FetchArtifactPayload($command_text) default: trigger_error('Sorry, @user, I don\'t know how to handle "' . $command_text . '". You can look up user stories, defects, and tasks by ID, like "DE1234".', E_USER_ERROR); } - $query_url .= '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; + $query_url .= strtolower($artifact_type) . '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; $Results = CallAPI($query_url); if ($Results->QueryResult->TotalResultCount == 0) { //get count From f5394128e0a3c0079577b5caccddb307c613a685 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 20:09:29 -0400 Subject: [PATCH 79/86] Refactor to abstract notion of artifact-payload header information --- scripts/include/rallyme.inc.php | 73 ++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 0120d74..69bafe0 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -105,17 +105,13 @@ function _HandleRallyMeErrors($errno, $errstr) */ function ParseDefectPayload($Defect) { - global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + global $RALLYME_DISPLAY_VERSION; - $header = array('title' => $Defect->_refObjectName, 'type' => 'defect'); - $item_url = $RALLY_BASE_URL . '#/' . basename($Defect->Project->_ref) . '/detail/defect/' . $Defect->ObjectID; + $header = CompileArtifactHeader($Defect, 'defect'); switch ($RALLYME_DISPLAY_VERSION) { case 2: - $header['item_id'] = $Defect->FormattedID; - $header['item_url'] = $item_url; - $state = $Defect->State; if ($state == 'Closed') { $Date = new DateTime($Defect->ClosedDate); @@ -138,7 +134,7 @@ function ParseDefectPayload($Defect) default: $fields = array( - 'link' => array($Defect->_refObjectName => $item_url), + 'link' => array($header['title'] => $header['url']), 'id' => $Defect->FormattedID, 'owner' => $Defect->Owner->_refObjectName, 'project' => $Defect->Project->_refObjectName, @@ -169,10 +165,9 @@ function ParseDefectPayload($Defect) */ function ParseTaskPayload($Task) { - global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + global $RALLYME_DISPLAY_VERSION; - $header = array('title' => $Task->_refObjectName, 'type' => 'task'); - $item_url = $RALLY_BASE_URL . '#/' . basename($Task->Project->_ref) . '/detail/task/' . $Task->ObjectID; + $header = CompileArtifactHeader($Task, 'task'); switch ($RALLYME_DISPLAY_VERSION) { @@ -180,7 +175,7 @@ function ParseTaskPayload($Task) // break; default: - $fields = CompileRequirementFields($Task, $item_url); + $fields = CompileRequirementFields($Task, $header); break; } @@ -196,10 +191,9 @@ function ParseTaskPayload($Task) */ function ParseStoryPayload($Story) { - global $RALLYME_DISPLAY_VERSION, $RALLY_BASE_URL; + global $RALLYME_DISPLAY_VERSION; - $header = array('title' => $Story->_refObjectName, 'type' => 'story'); - $item_url = $RALLY_BASE_URL . '#/' . basename($Story->Project->_ref) . '/detail/userstory/' . $Story->ObjectID; + $header = CompileArtifactHeader($Story, 'story'); switch ($RALLYME_DISPLAY_VERSION) { @@ -207,13 +201,36 @@ function ParseStoryPayload($Story) // break; default: - $fields = CompileRequirementFields($Story, $item_url); + $fields = CompileRequirementFields($Story, $header); break; } return array('header' => $header, 'fields' => $fields); } +/** + * Prepares an array of fields of meta-information common to all artifacts. + * + * @param object $Artifact + * @param string $type + * + * @return string[] + */ +function CompileArtifactHeader($Artifact, $type) +{ + global $RALLY_BASE_URL; + $path_map = array('defect' => 'defect', 'task' => 'task', 'story' => 'userstory'); //associate human-readable names with Rally URL paths + + $item_url = $RALLY_BASE_URL . '#/' . basename($Artifact->Project->_ref) . '/detail/' . $path_map[$type] . '/' . $Artifact->ObjectID; + + return array( + 'type' => $type, + 'id' => $Artifact->FormattedID, + 'title' => $Artifact->_refObjectName, + 'url' => $item_url + ); +} + /** * Prepare a table of field values for stories and tasks. * @@ -221,11 +238,11 @@ function ParseStoryPayload($Story) * and so the original version of this script rendered the same fields for both. * * @param object $Requirement - * @param string $item_url + * @param string[] $header * * @return string[] */ -function CompileRequirementFields($Requirement, $item_url) +function CompileRequirementFields($Requirement, $header) { $parent = NULL; if ($Requirement->HasParent) { @@ -237,7 +254,7 @@ function CompileRequirementFields($Requirement, $item_url) } $fields = array( - 'link' => array($Requirement->_refObjectName => $item_url), + 'link' => array($header['title'] => $header['url']), 'parent' => $parent, 'id' => $Requirement->FormattedID, 'owner' => $Requirement->Owner->_refObjectName, @@ -322,14 +339,14 @@ function SendArtifactPayload($payload) case 'Description': case 'description': - $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); + $value = TruncateText(SanitizeText($value), 300, $payload['header']['url']); $short = FALSE; break; } $fields[] = MakeField($label, $value, $short); } - $attachment = MakeAttachment($prextext, '', $color, $fields, $payload['header']['item_url']); + $attachment = MakeAttachment($prextext, '', $color, $fields, $payload['header']['url']); return slack_incoming_hook_post_with_attachments( $config['slack']['hook'], @@ -367,7 +384,7 @@ function ReturnArtifactPayload($payload) break; case 'Description': - $value = TruncateText(SanitizeText($value), 300, $payload['header']['item_url']); + $value = TruncateText(SanitizeText($value), 300, $payload['header']['url']); $value = '\n> ' . strtr($value, array('\n' => '\n> ')); break; } @@ -383,16 +400,22 @@ function ReturnArtifactPayload($payload) return PrintJsonResponse($text); } -function ArtifactPretext($info) +/** + * Compiles a short message that Rallybot uses to announce query results. + * + * @param string[] $header + * + * @return string + */ +function ArtifactPretext($header) { global $RALLYME_DISPLAY_VERSION; - switch ($RALLYME_DISPLAY_VERSION) { case 2: - return em('Details for ' . $info['item_id'] . ' ' . l($info['title'], $info['item_url'])); + return em('Details for ' . $header['id'] . ' ' . l($header['title'], $header['url'])); default: - return 'Ok, @' . $_REQUEST['user_name'] . ', here\'s the ' . $info['type'] . ' you requested.'; + return 'Ok, @' . $_REQUEST['user_name'] . ', here\'s the ' . $header['type'] . ' you requested.'; } } From bdc71ebc94243e7b728366c7f6874a5d3daf363b Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 20:17:29 -0400 Subject: [PATCH 80/86] Add troubleshooting message on webhook failure --- scripts/include/slack.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 12bbd2a..6526163 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -124,9 +124,11 @@ function _incoming_hook_post($url, $data) return $result; case 'Invalid channel specified': exit('Unable to post messages to a private chat'); - default: - exit('Unable to send Incoming WebHook message: ' . $reply); } + if (strpos($url, 'REPLACE')) { + $result = 'Please set your Slack subdomain and incoming webhook token'; + } + exit('Unable to send Incoming WebHook message: ' . $result); } function PrintJsonResponse($payload) From c9dd514212912b2e6385e26ac20934c674d86790 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 20:59:31 -0400 Subject: [PATCH 81/86] Update display of stories --- scripts/include/rallyme.inc.php | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 69bafe0..92c59cd 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -105,10 +105,9 @@ function _HandleRallyMeErrors($errno, $errstr) */ function ParseDefectPayload($Defect) { - global $RALLYME_DISPLAY_VERSION; - $header = CompileArtifactHeader($Defect, 'defect'); + global $RALLYME_DISPLAY_VERSION; switch ($RALLYME_DISPLAY_VERSION) { case 2: @@ -165,10 +164,9 @@ function ParseDefectPayload($Defect) */ function ParseTaskPayload($Task) { - global $RALLYME_DISPLAY_VERSION; - $header = CompileArtifactHeader($Task, 'task'); + global $RALLYME_DISPLAY_VERSION; switch ($RALLYME_DISPLAY_VERSION) { case 2: @@ -191,14 +189,31 @@ function ParseTaskPayload($Task) */ function ParseStoryPayload($Story) { - global $RALLYME_DISPLAY_VERSION; - $header = CompileArtifactHeader($Story, 'story'); + global $RALLYME_DISPLAY_VERSION; switch ($RALLYME_DISPLAY_VERSION) { case 2: - // break; + $fields = array( + 'Project' => $Story->Project->_refObjectName, + 'Created' => $Story->_CreatedAt, + 'Owner' => $Story->Owner->_refObjectName, + 'Points' => $Story->PlanEstimate, + 'State' => $Story->ScheduleState, + 'Status' => '' + ); + if ($Story->Blocked) { + $fields['Status'] = 'Blocked'; + $fields['Block Description'] = $Story->BlockedReason; + } elseif ($Story->Ready) { + $fields['Status'] = 'Ready'; + } + $fields['Description'] = $Story->Description; + if ($Story->Attachments->Count > 0) { + $ret['Attachment'] = GetAttachmentLinks($Story->Attachments->_ref); + } + break; default: $fields = CompileRequirementFields($Story, $header); From c1bf27fd3cd4b3ee9c83b2609e0966fe06055ae9 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 21:36:45 -0400 Subject: [PATCH 82/86] Update display of tasks --- scripts/include/rallyme.inc.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index 92c59cd..e5f6cd7 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -170,7 +170,26 @@ function ParseTaskPayload($Task) switch ($RALLYME_DISPLAY_VERSION) { case 2: - // break; + $fields = array( + 'Parent' => $Task->WorkProduct->_refObjectName, + 'Owner' => $Task->Owner->_refObjectName, + 'Created' => $Task->_CreatedAt, + 'To Do' => $Task->ToDo, + 'Actual' => $Task->Actuals, + 'State' => $Task->State, + 'Status' => '' + ); + if ($Task->Blocked) { + $fields['Status'] = 'Blocked'; + $fields['Block Description'] = $Task->BlockedReason; + } elseif ($Task->Ready) { + $fields['Status'] = 'Ready'; + } + $fields['Description'] = $Task->Description; + if ($Task->Attachments->Count > 0) { + $fields['Attachment'] = GetAttachmentLinks($Task->Attachments->_ref); + } + break; default: $fields = CompileRequirementFields($Task, $header); @@ -211,7 +230,7 @@ function ParseStoryPayload($Story) } $fields['Description'] = $Story->Description; if ($Story->Attachments->Count > 0) { - $ret['Attachment'] = GetAttachmentLinks($Story->Attachments->_ref); + $fields['Attachment'] = GetAttachmentLinks($Story->Attachments->_ref); } break; From b1b69d3715fc59ec276a363c0ca03c5b7d2cada5 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Wed, 1 Oct 2014 22:53:37 -0400 Subject: [PATCH 83/86] Filter info when field names added to command eg: "/rallyme US1234 state attachment owner" would only display those fields in the channel --- scripts/include/rallyme.inc.php | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/include/rallyme.inc.php b/scripts/include/rallyme.inc.php index e5f6cd7..7334a6b 100644 --- a/scripts/include/rallyme.inc.php +++ b/scripts/include/rallyme.inc.php @@ -18,16 +18,14 @@ */ function FetchArtifactPayload($command_text) { - global $RALLY_API_URL; - - list($formatted_id) = explode(' ', trim($command_text)); - $formatted_id = strtoupper($formatted_id); + //assume any words following the formatted id are the name of fields we want to see + $field_filter = explode(' ', trim($command_text)); + $formatted_id = strtoupper(array_shift($field_filter)); - //handle item - $query_url = $RALLY_API_URL; + //determine requested artifact type switch (substr($formatted_id, 0, 2)) { - case 'DE': //find defect + case 'DE': $artifact_type = 'Defect'; $func = 'ParseDefectPayload'; break; @@ -45,17 +43,31 @@ function FetchArtifactPayload($command_text) default: trigger_error('Sorry, @user, I don\'t know how to handle "' . $command_text . '". You can look up user stories, defects, and tasks by ID, like "DE1234".', E_USER_ERROR); } - $query_url .= strtolower($artifact_type) . '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; + //compile query string + global $RALLY_API_URL; + $query_url .= $RALLY_API_URL . $artifact_type . '?query=(FormattedID+%3D+' . $formatted_id . ')&fetch=true'; + + //query Rally $Results = CallAPI($query_url); if ($Results->QueryResult->TotalResultCount == 0) { //get count trigger_error('Sorry, @user, I couldn\'t find ' . $formatted_id, E_USER_ERROR); //not found } - //get first object from search result + //generate payload from first query result foreach ($Results->QueryResult->Results as $Result) { if ($Result->_type == $artifact_type) { - return call_user_func($func, $Result); + $payload = call_user_func($func, $Result); + + //filter display of artifact fields + if (!empty($field_filter)) { + if (ctype_upper(key($payload['fields'])[0])) { //match the case of payload field labels + $field_filter = array_map('ucfirst', $field_filter); + } + $payload['fields'] = array_intersect_key($payload['fields'], array_flip($field_filter)); + } + + return $payload; } } trigger_error('Sorry, @user, your search for "' . $formatted_id . '" was ambiguous.', E_USER_ERROR); From 95a14c44ebdb49bf81308166693e52ce01e7a090 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 2 Oct 2014 18:02:06 -0400 Subject: [PATCH 84/86] Move Slack library functions out of rally file --- scripts/include/rally.php | 19 ------------------- scripts/include/slack.php | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/scripts/include/rally.php b/scripts/include/rally.php index dbe992a..bfebcb3 100644 --- a/scripts/include/rally.php +++ b/scripts/include/rally.php @@ -3,17 +3,6 @@ $RALLY_BASE_URL = 'https://rally1.rallydev.com/'; $RALLY_API_URL = $RALLY_BASE_URL . 'slm/webservice/v2.0/'; - -function MakeField($title, $value, $short=false) -{ - $attachmentfield = array( - "title" => $title, - "value" => $value, - "short" => $short); - - return $attachmentfield; -} - function CallAPI($uri) { global $config; @@ -24,12 +13,4 @@ function CallAPI($uri) return $object; } -function TruncateText($text, $len) -{ - if(strlen($text) <= $len) - return $text; - - return substr($text,0,$len)."...[MORE]"; -} - ?> diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 6526163..9c2e7d6 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -67,6 +67,15 @@ function SanitizeText($text) return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); } +function TruncateText($text, $len) +{ + if (strlen($text) <= $len) { + return $text; + } + + return substr($text, 0, $len) . '...[MORE]'; +} + function l($text, $url) { return '<' . $url . '|' . $text . '>'; @@ -175,4 +184,15 @@ function MakeAttachment($pretext, $text, $color, $fields, $fallback){ return $obj; } + +function MakeField($title, $value, $short = false) +{ + $attachmentfield = array( + "title" => $title, + "value" => $value, + "short" => $short + ); + + return $attachmentfield; +} ?> From 6a1ee49e32899bf8f9d57a499db6b22095952849 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 2 Oct 2014 18:17:00 -0400 Subject: [PATCH 85/86] Allow truncated text to display read more link --- scripts/include/slack.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/include/slack.php b/scripts/include/slack.php index 9c2e7d6..71534ed 100644 --- a/scripts/include/slack.php +++ b/scripts/include/slack.php @@ -67,13 +67,14 @@ function SanitizeText($text) return html_entity_decode(strip_tags($text), ENT_HTML401 | ENT_COMPAT, 'UTF-8'); } -function TruncateText($text, $len) +function TruncateText($text, $len, $url = '') { if (strlen($text) <= $len) { return $text; } - - return substr($text, 0, $len) . '...[MORE]'; + $text = preg_replace('/\s+?(\S+)?$/', '', substr($text, 0, $len)); + $more = ($url) ? l('more', $url) : 'more'; + return $text . '... ' . em($more); } function l($text, $url) From 1d257756a00aeef477e3d649163a1f288f357d47 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 2 Oct 2014 19:00:59 -0400 Subject: [PATCH 86/86] Update README.md with rallybot usage instructions --- README.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c01006e..2fb01d1 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,24 @@ A place where I put some integration scripts for the popular Slack messaging pla #Rally Bot -We use this to query our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack. - -E.g.: +We use this to query our Rally instance for defects, tasks and user stories. It can be configured to respond to Slack [slash command](https://slack.zendesk.com/hc/en-us/articles/201259356-Slash-Commands) or to messages posted to a channel that start with a Rally ticket's formatted id, e.g.: +``` /rallyme DE12345 -/rallyme US23456 -/rallyme TA34567 - -Each of these returns a nicely formatted summary of the defect, story or task, including the owner/submitter, creation date, story title, project and description. For documents that have attachments, such as uploaded screenshots, the summary will include a link to the first available attachment. +/rallyme US23456 project +TA34567 owner description +``` +Entering the id of an artifact without any arguments returns a nicely formatted summary of the defect, story or task, including the owner/submitter, creation date, story title, project and description. For documents that have attachments, such as uploaded screenshots, the summary will include a link to the first available attachment. Otherwise, adding one or more field names after the id will filter the summary to just display the requested fields. #Image Bot / Gif Bot This one is simple. It uses the google image search JSON API to query for images that match a keyword. It is configured to use the Safe Search feature, since we use this tool in the workplace. Being able to add some levity to our work chats with amusing images from the internet makes life more fun. - -E.g. +``` /imageme kittens /gifme roflcopter - +``` #XKCD Bot As geeks, we are well acquainted with the internet comic XKCD. This bot allows you to post an XKCD comic to the room, including the ALT text, which will be displayed beneath the image. -E.g. -/xkcd 100 will return the "family circus" episode of XKCD. +E.g. `/xkcd 100` will return the "family circus" episode of XKCD.