From 88aa7094553a61e8080cfe28f002395b2aceec63 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Thu, 4 Sep 2014 14:43:05 -0400 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] 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: Mon, 8 Sep 2014 02:09:21 -0400 Subject: [PATCH 11/13] 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 c4effb954a64e6512e9ce29d33bd8e806878d536 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 12:52:52 -0400 Subject: [PATCH 12/13] 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 12d12b6a07cdb4629ad8013c7602a28fb0dd8374 Mon Sep 17 00:00:00 2001 From: JP Klein Date: Mon, 22 Sep 2014 15:38:57 -0400 Subject: [PATCH 13/13] 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