From 32ea1d194f1d97e68649b1848fdcf7787412c466 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 19 Aug 2014 17:40:36 -0400 Subject: [PATCH] Add support for the Hipchat room notification API --- endpoints/notificationevent.py | 22 ++++++ endpoints/notificationmethod.py | 62 +++++++++++++++++ initdb.py | 1 + static/css/quay.css | 7 ++ .../create-external-notification-dialog.html | 4 +- static/img/hipchat.png | Bin 0 -> 2828 bytes static/js/app.js | 63 +++++++++++++++++- 7 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 static/img/hipchat.png 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 0000000000000000000000000000000000000000..4b0500763a2546cb6a4f41f94d3b4cc0c14f27a3 GIT binary patch literal 2828 zcmV+n3-k1eP) zKOSs*0qtXRxLtxMcqIZ!!E-8}Gw9UqEoz5fqvdi7$Io6lnsfPNeo;|*&2^jI7Ig|A`l5Yhae6# zT|Y3Xnl$czqDOAXG%(h`Ce8&UzxbjUAfNzE<}6!snN!pZzfxEDNm? z3=J8OBzQ&md>{Y|5E>K(N`?%`1d zZoMK>9~$yVa^k?-Vj21dnE}W-SIT!~{=6w8`|`O$a5R)kurwqes%$+6#sBxCSEITb zT3y+?mc`~snzFp~Y$3w*plNtTIGo}Ax-Wfx!c*giurw36 zjA|zg8c;N9?R1#o!j?CCMRjcv*^1VHCzqZ(nhR4f09fr^dWP>?|MDGuqR|{B_w^=f2N{Q4blg*ai%VFWSAbvz!1#gax1f z`mM2(hT*yuGJ#1`eX(rJjI}MonrZDlp7-g-445>aDb&~!hV;uxU#wI%dmo*`-gUDd zd~^gX4iFgzZPvb%A8yHP8qn+_Mn5#a^l~Zm8rWRH(V?Zgmupl(0XaL3{*Ns=f9e7Z zTDa1mO25*cyA`OYSf8PsYuoJB_Ho@ zm}=BooqiCCAkY+??q{bwa8FX7)>3sQF%ib)vnRvl0wGYU(spJ!J)(cAUhCkG`NjDq zP$}RMD5GJ;tVwNW>rCcP8xs~Ei@G|fc<;5E%^5%V$LqDWXP$y6fdSm!XC~Yo7OZVM zRVVZEqr>5mAOkGD?NE+?uwLswKNZy9VR-hbaf8}U()m0wb|4HIh(2)avAm*My9a)) zVsp)vvg_a(ctvA3GVw z{+g2Yw~*L37NQ86h9Kr%sf1svvbq*t5flX?^@%jKm!$KFk2ckRorqj%t%m2|DvLdE{HMGUa4ZM`&&79-fL|*%JhVqt2z(M0?6s?w(*?yX zmZiS6G3LHmpX@jin6cwzX6JVs{o05Z-fJ;lWp$jmR058HPf{4P{bIYJ;a7gxfZp(m zU>JDaoBn;W#mX?F4(6H{URnR>oR2E4PTV9iF#p3HBd4vvRdY;NeT&)c2T!7^7CZ;R zHz+>FsMnxDYjX1KpcxPuyy73bj|hUV>B_XZQ^JS$2gr`ChoeT%d1LMNN{bC0Koq2p zwjYjoaNf#yw!rRT;$ps7_Lmkh?|+jCf5q&R{&au+$-A)(3?DnU;%WsnL9jR$&UL1%Q?Z%9w&(2IkhzcH&)~GE9RvYx{W?HMh+&gd4MuY}K^g+R1 z_;y*J$grj>0AmoA9ZDnR?Rj@ka zlj8EeS&RVYtlPF`<9^gSp;CZlArlY+AF1y|P!w1iG!2@DB*X22l7Bko-VF<$R`4y4 zGoxm&JGAFVgy`V$B8actx2B6p=bvf}rGX7^q`)IVlweTjXXicq>UspQdiLbY87rQ9 zW{k$9hszCz7hVZ81&#s7fTbW1xIJ*XST#R6W%$qAmVEKIX@O2NSEe7@c@)7K5Q19g zvgs2VoDg_UeB|}Com;aI77P$n+9pm({&vlDw1(4FxA%BK)~SNi1tn$X8mHR}89_=# zgh_ix-{@fpy~hvl-_{*==9(?**Kdbj3!a6=k$BH-r#CHZ8W4DVmAg+|Se#RcP(37q z>$X7;Bpq1ys$Sdpr4B@tB*_QJ;91b@jamUj#v^k!>`FfblO75V)egRkzC2@cH4aT=*k_BT{(1CW_-&lJP%>`Tx~hw12+@J1QR9M+Kk?P# zf$=ddB3o~bTq!drrLM4-RKTDEO~dJiMD81rv|`4jp#ypZ#&?jYadXOYi(%4(W?*w5 zSe=#j+P!zh1xB{nNGh+kjeP#S3)y)v>cO)ReQTR1m#}P~+iGUhChpm39rKo7g-m^!9%ROq$p!IJ))jPrj;K@awr*A$4O%N!$VE59v?%i75uA^E2B!cw4 z$G_N$18~ zC)_@`@6VeTwYt|$Xf;q~wq>0tIDGcfnS$anbFJCxl0*R#0DzNenhy?xx+XiE z5CNJ1Eb5$0R5$mLbv)O2n*aT@8n}ARdOE-KTv2Ipd5yWoMhG?M>5=!}*|%xT|3RxG ev@C=EAp8%NkGu3nbEE_S0000