Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post new-comment notifications to Slack #7

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions scripts/config/rallycron.conf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
$CRON_INTERVAL = 61; //seconds between cron runs; pad for script run time and latency
$RALLY_PROJECT_ID = REPLACE_ME;
$SLACK_CHANNEL_FOR_RALLY_PROJECT = 'REPLACE ME'; //do not include hash symbol
21 changes: 11 additions & 10 deletions scripts/include/rally.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?
//rally commands

$RALLY_URL = 'https://rally1.rallydev.com/';
$RALLY_TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.u\Z';

function HandleItem($slackCommand, $rallyFormattedId)
{
Expand Down Expand Up @@ -53,11 +54,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));
Expand All @@ -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);
}

Expand Down Expand Up @@ -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.";

Expand Down Expand Up @@ -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)
Expand Down
95 changes: 95 additions & 0 deletions scripts/include/rallycron.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
require('curl.php');
require('slack.config.php');
require('slack.php');
require('rallyme.config.php');
require('rally.php');

function FetchLatestRallyComments($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 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;
}

34 changes: 28 additions & 6 deletions scripts/include/slack.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,31 @@ function BuildSlashCommand($request)
}


//text-formatting functions

function SanitizeText($text)
{
$text = strtr($text, array('<br />' => '\n', '<div>' => '\n', '<p>' => '\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
);
Expand All @@ -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);
}
Expand Down
9 changes: 9 additions & 0 deletions scripts/rallycron.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
require('config/rallycron.conf.php');
require('include/rallycron.inc.php');

date_default_timezone_set('UTC');
$since = date($RALLY_TIMESTAMP_FORMAT, time() - $CRON_INTERVAL);

$items = FetchLatestRallyComments($since);
$result = SendRallyCommentNotifications($items);