diff --git a/endpoints/notificationevent.py b/endpoints/notificationevent.py index e393dc134..f3f4d6a77 100644 --- a/endpoints/notificationevent.py +++ b/endpoints/notificationevent.py @@ -15,6 +15,13 @@ class NotificationEvent(object): def __init__(self): pass + def get_level(self, event_data, notification_data): + """ + Returns a 'level' representing the severity of the event. + Valid values are: 'info', 'warning', 'error', 'primary' + """ + raise NotImplementedError + def get_summary(self, event_data, notification_data): """ Returns a human readable one-line summary for the given notification data. @@ -55,6 +62,9 @@ class RepoPushEvent(NotificationEvent): def event_name(cls): return 'repo_push' + def get_level(self, event_data, notification_data): + return 'info' + def get_summary(self, event_data, notification_data): return 'Repository %s updated' % (event_data['repository']) @@ -87,6 +97,9 @@ class BuildQueueEvent(NotificationEvent): @classmethod def event_name(cls): return 'build_queued' + + def get_level(self, event_data, notification_data): + return 'info' def get_sample_data(self, repository): build_uuid = 'fake-build-id' @@ -127,6 +140,9 @@ class BuildStartEvent(NotificationEvent): def event_name(cls): return 'build_start' + def get_level(self, event_data, notification_data): + return 'info' + def get_sample_data(self, repository): build_uuid = 'fake-build-id' @@ -155,6 +171,9 @@ class BuildSuccessEvent(NotificationEvent): def event_name(cls): return 'build_success' + def get_level(self, event_data, notification_data): + return 'primary' + def get_sample_data(self, repository): build_uuid = 'fake-build-id' @@ -183,6 +202,9 @@ class BuildFailureEvent(NotificationEvent): def event_name(cls): return 'build_failure' + def get_level(self, event_data, notification_data): + return 'error' + def get_sample_data(self, repository): build_uuid = 'fake-build-id' diff --git a/endpoints/notificationmethod.py b/endpoints/notificationmethod.py index 56adcc0a5..a6d958037 100644 --- a/endpoints/notificationmethod.py +++ b/endpoints/notificationmethod.py @@ -240,3 +240,65 @@ class FlowdockMethod(NotificationMethod): return False return True + + +class HipchatMethod(NotificationMethod): + """ Method for sending notifications to Hipchat via the API: + https://www.hipchat.com/docs/apiv2/method/send_room_notification + """ + @classmethod + def method_name(cls): + return 'hipchat' + + def validate(self, repository, config_data): + if not config_data.get('notification_token', ''): + raise CannotValidateNotificationMethodException('Missing Hipchat Room Notification Token') + + if not config_data.get('room_id', ''): + raise CannotValidateNotificationMethodException('Missing Hipchat Room ID') + + def perform(self, notification, event_handler, notification_data): + config_data = json.loads(notification.config_json) + + token = config_data.get('notification_token', '') + room_id = config_data.get('room_id', '') + + if not token or not room_id: + return False + + owner = model.get_user(notification.repository.namespace) + if not owner: + # Something went wrong. + return False + + url = 'https://api.hipchat.com/v2/room/%s/notification?auth_token=%s' % (room_id, token) + + level = event_handler.get_level(notification_data['event_data'], notification_data) + color = { + 'info': 'gray', + 'warning': 'yellow', + 'error': 'red', + 'primary': 'purple' + }.get(level, 'gray') + + headers = {'Content-type': 'application/json'} + payload = { + 'color': color, + 'message': event_handler.get_message(notification_data['event_data'], notification_data), + 'notify': level == 'error', + 'message_format': 'html', + } + + try: + resp = requests.post(url, data=json.dumps(payload), headers=headers) + if resp.status_code/100 != 2: + logger.error('%s response for hipchat to url: %s' % (resp.status_code, + url)) + logger.error(resp.content) + return False + + except requests.exceptions.RequestException as ex: + logger.exception('Hipchat method was unable to be sent: %s' % ex.message) + return False + + return True diff --git a/initdb.py b/initdb.py index cb56d987e..6b68cee85 100644 --- a/initdb.py +++ b/initdb.py @@ -252,6 +252,7 @@ def initialize_database(): ExternalNotificationMethod.create(name='webhook') ExternalNotificationMethod.create(name='flowdock') + ExternalNotificationMethod.create(name='hipchat') NotificationKind.create(name='repo_push') NotificationKind.create(name='build_queued') diff --git a/static/css/quay.css b/static/css/quay.css index a5cdf019b..bd8f571e8 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -4566,6 +4566,13 @@ i.flowdock-icon { height: 16px; } +i.hipchat-icon { + background-image: url(/static/img/hipchat.png); + background-size: 16px; + width: 16px; + height: 16px; +} + .external-notification-view-element { margin: 10px; padding: 6px; diff --git a/static/directives/create-external-notification-dialog.html b/static/directives/create-external-notification-dialog.html index ba78a4ac9..bf0c5da03 100644 --- a/static/directives/create-external-notification-dialog.html +++ b/static/directives/create-external-notification-dialog.html @@ -88,8 +88,8 @@ allowed-entities="['user', 'team', 'org']" ng-switch-when="entity"> -
- See: {{ field.help_url }} +
+ See: {{ getHelpUrl(field, currentConfig) }}
diff --git a/static/img/hipchat.png b/static/img/hipchat.png new file mode 100644 index 000000000..4b0500763 Binary files /dev/null and b/static/img/hipchat.png differ diff --git a/static/js/app.js b/static/js/app.js index 8676d7d99..3394543e7 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -513,6 +513,41 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading $provide.factory('StringBuilderService', ['$sce', 'UtilService', function($sce, UtilService) { var stringBuilderService = {}; + stringBuilderService.buildUrl = function(value_or_func, metadata) { + var url = value_or_func; + if (typeof url != 'string') { + url = url(metadata); + } + + // Find the variables to be replaced. + var varNames = []; + for (var i = 0; i < url.length; ++i) { + var c = url[i]; + if (c == '{') { + for (var j = i + 1; j < url.length; ++j) { + var d = url[j]; + if (d == '}') { + varNames.push(url.substring(i + 1, j)); + i = j; + break; + } + } + } + } + + // Replace all variables found. + for (var i = 0; i < varNames.length; ++i) { + var varName = varNames[i]; + if (!metadata[varName]) { + return null; + } + + url = url.replace('{' + varName + '}', metadata[varName]); + } + + return url; + }; + stringBuilderService.buildString = function(value_or_func, metadata) { var fieldIcons = { 'username': 'user', @@ -1089,6 +1124,23 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading 'help_url': 'https://www.flowdock.com/account/tokens' } ] + }, + { + 'id': 'hipchat', + 'title': 'HipChat Room Notification', + 'icon': 'hipchat-icon', + 'fields': [ + { + 'name': 'room_id', + 'type': 'string', + 'title': 'Room ID #' + }, + { + 'name': 'notification_token', + 'type': 'string', + 'title': 'Notification Token' + } + ] } ]; @@ -4830,7 +4882,7 @@ quayApp.directive('createExternalNotificationDialog', function () { 'counter': '=counter', 'notificationCreated': '¬ificationCreated' }, - controller: function($scope, $element, ExternalNotificationData, ApiService, $timeout) { + controller: function($scope, $element, ExternalNotificationData, ApiService, $timeout, StringBuilderService) { $scope.currentEvent = null; $scope.currentMethod = null; $scope.status = ''; @@ -4930,6 +4982,15 @@ quayApp.directive('createExternalNotificationDialog', function () { }, 1000); }; + $scope.getHelpUrl = function(field, config) { + var helpUrl = field['help_url']; + if (!helpUrl) { + return null; + } + + return StringBuilderService.buildUrl(helpUrl, config); + }; + $scope.$watch('counter', function(counter) { if (counter) { $scope.clearCounter++;