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 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) diff --git a/scripts/include/rallycron.inc.php b/scripts/include/rallycron.inc.php new file mode 100644 index 0000000..8f09bed --- /dev/null +++ b/scripts/include/rallycron.inc.php @@ -0,0 +1,95 @@ ++' . $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 SendRallyCommentNotifications($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 = $item['type'] . ' ' . l($item['title'], $item['url']); + + $item['text'] = SanitizeText($item['text']); + $item['text'] = TruncateText($item['text'], 300); + + //display a preview of the comment as a message attachment + $pretext = em('New comment added to ' . $slug); + $text = ''; + $color = '#CEC7B8'; //dove gray + $fields = array(MakeField($item['user'], $item['text'])); + $fallback = $item['user'] . ' commented on ' . $slug; + + $message = MakeAttachment($pretext, $text, $color, $fields, $fallback); + $success = SendIncomingWebHookMessage($SLACK_CHANNEL_FOR_RALLY_PROJECT, '', $message) && $success; + } + + 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 38639a2..1eb0d44 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', '
' => '\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 . '_'; +} + + +//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 +74,19 @@ 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){ $data = array( - "text" => $payload, + "text" => $payload, "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 mylog('sent.txt',$data_string); return curl_post($uri, $data_string); } diff --git a/scripts/rallycron.php b/scripts/rallycron.php new file mode 100644 index 0000000..1dc9d3e --- /dev/null +++ b/scripts/rallycron.php @@ -0,0 +1,9 @@ +