mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	integrations: Add webhook code, API endpoint, and tests for Raygun.
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								static/images/integrations/logos/raygun.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/images/integrations/logos/raygun.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 42 KiB  | 
@@ -343,6 +343,7 @@ WEBHOOK_INTEGRATIONS = [
 | 
			
		||||
    WebhookIntegration('papertrail', ['monitoring']),
 | 
			
		||||
    WebhookIntegration('pingdom', ['monitoring']),
 | 
			
		||||
    WebhookIntegration('pivotal', ['project-management'], display_name='Pivotal Tracker'),
 | 
			
		||||
    WebhookIntegration('raygun', ['monitoring'], display_name="Raygun"),
 | 
			
		||||
    WebhookIntegration('semaphore', ['continuous-integration', 'deployment'], stream_name='builds'),
 | 
			
		||||
    WebhookIntegration('sentry', ['monitoring']),
 | 
			
		||||
    WebhookIntegration('slack', ['communication']),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								zerver/webhooks/raygun/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								zerver/webhooks/raygun/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								zerver/webhooks/raygun/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								zerver/webhooks/raygun/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								zerver/webhooks/raygun/fixtures/hourly_followup_error.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								zerver/webhooks/raygun/fixtures/hourly_followup_error.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
{
 | 
			
		||||
  "event":"error_notification",
 | 
			
		||||
  "eventType":"HourlyFollowUp",
 | 
			
		||||
  "error":{
 | 
			
		||||
    "url":"http://app.raygun.io/error-url",
 | 
			
		||||
    "message":"",
 | 
			
		||||
    "firstOccurredOn":"1970-01-28T01:49:36Z",
 | 
			
		||||
    "lastOccurredOn":"1970-01-28T01:49:36Z",
 | 
			
		||||
    "usersAffected":1,
 | 
			
		||||
    "totalOccurrences":1
 | 
			
		||||
  },
 | 
			
		||||
  "application":{
 | 
			
		||||
    "name":"application name",
 | 
			
		||||
    "url":"http://app.raygun.io/application-url"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "event":"error_activity",
 | 
			
		||||
  "eventType":"UnimplementedFeature"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								zerver/webhooks/raygun/fixtures/no_event_type.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								zerver/webhooks/raygun/fixtures/no_event_type.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "event":"new_event_type",
 | 
			
		||||
  "eventType":"something_new"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "event":"error_notification",
 | 
			
		||||
  "eventType":"UnimplementedFeature"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								zerver/webhooks/raygun/fixtures/reoccurred_error.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								zerver/webhooks/raygun/fixtures/reoccurred_error.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
{
 | 
			
		||||
  "event":"error_notification",
 | 
			
		||||
  "eventType":"ErrorReoccurred",
 | 
			
		||||
  "error":{
 | 
			
		||||
    "url":"http://app.raygun.io/error-url",
 | 
			
		||||
    "message":"",
 | 
			
		||||
    "firstOccurredOn":"1970-01-28T01:49:36Z",
 | 
			
		||||
    "lastOccurredOn":"1970-01-28T01:49:36Z",
 | 
			
		||||
    "usersAffected":1,
 | 
			
		||||
    "totalOccurrences":1,
 | 
			
		||||
    "instance":{
 | 
			
		||||
      "tags":[
 | 
			
		||||
        "test",
 | 
			
		||||
        "error-page",
 | 
			
		||||
        "v1.0.1",
 | 
			
		||||
        "env:staging"
 | 
			
		||||
      ],
 | 
			
		||||
      "affectedUser":{
 | 
			
		||||
        "Identifier":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846",
 | 
			
		||||
        "IsAnonymous":true,
 | 
			
		||||
        "UUID":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846"
 | 
			
		||||
      },
 | 
			
		||||
      "customData":{
 | 
			
		||||
        "pageName":"Error Page",
 | 
			
		||||
        "userLoggedIn":true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "application":{
 | 
			
		||||
    "name":"application name",
 | 
			
		||||
    "url":"http://app.raygun.io/application-url"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										167
									
								
								zerver/webhooks/raygun/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								zerver/webhooks/raygun/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
from typing import Text
 | 
			
		||||
 | 
			
		||||
from zerver.lib.test_classes import WebhookTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RaygunHookTests(WebhookTestCase):
 | 
			
		||||
    STREAM_NAME = 'raygun'
 | 
			
		||||
    URL_TEMPLATE = "/api/v1/external/raygun?&api_key={api_key}"
 | 
			
		||||
    FIXTURE_DIR_NAME = 'raygun'
 | 
			
		||||
 | 
			
		||||
    def test_status_changed_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"[Error](https://app.raygun.com/error-url) " \
 | 
			
		||||
                           u"status changed to: Ignored by Emma Cat\n" \
 | 
			
		||||
                           u"Timestamp: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[Best App](http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('error_status_changed',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_comment_added_to_error_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Anita Peacock left a comment on " \
 | 
			
		||||
                           u"[Error](https://app.raygun.com/error-url): " \
 | 
			
		||||
                           u"Ignoring these errors\n" \
 | 
			
		||||
                           u"Timestamp: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('comment_added_to_error',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_error_assigned_to_user_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Amy Loondon assigned " \
 | 
			
		||||
                           u"[Error](https://app.raygun.com/error-url) " \
 | 
			
		||||
                           u"to Kyle Kenny\n" \
 | 
			
		||||
                           u"Timestamp: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('error_assigned_to_user',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_one_minute_followup_error_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"One minute " \
 | 
			
		||||
                           u"[follow-up error]" \
 | 
			
		||||
                           u"(http://app.raygun.io/error-url)\n" \
 | 
			
		||||
                           u"First occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Last occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"1 users affected with 1 total occurrences\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('one_minute_followup_error',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_hourly_followup_error_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Hourly " \
 | 
			
		||||
                           u"[follow-up error]" \
 | 
			
		||||
                           u"(http://app.raygun.io/error-url)\n" \
 | 
			
		||||
                           u"First occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Last occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"1 users affected with 1 total occurrences\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('hourly_followup_error',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_new_error_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"**New [Error](http://app.raygun.io/error-url) " \
 | 
			
		||||
                           u"occurred!**\n" \
 | 
			
		||||
                           u"First occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Last occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"1 users affected with 1 total occurrences\n" \
 | 
			
		||||
                           u"Tags: test, error-page, v1.0.1, env:staging\n" \
 | 
			
		||||
                           u"Affected user: a9b7d8...33846\n" \
 | 
			
		||||
                           u"pageName: Error Page\n" \
 | 
			
		||||
                           u"userLoggedIn: True\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('new_error',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_reoccurred_error_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"[Error](http://app.raygun.io/error-url) " \
 | 
			
		||||
                           u"reoccurred.\n" \
 | 
			
		||||
                           u"First occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"Last occurred: Wed Jan 28 01:49:36 1970\n" \
 | 
			
		||||
                           u"1 users affected with 1 total occurrences\n" \
 | 
			
		||||
                           u"Tags: test, error-page, v1.0.1, env:staging\n" \
 | 
			
		||||
                           u"Affected user: a9b7d8...33846\n" \
 | 
			
		||||
                           u"pageName: Error Page\n" \
 | 
			
		||||
                           u"userLoggedIn: True\n" \
 | 
			
		||||
                           u"Application details: " \
 | 
			
		||||
                           u"[application name]" \
 | 
			
		||||
                           u"(http://app.raygun.io/application-url)"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('reoccurred_error',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_no_event_type_message(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Unsupported event type: new_event_type"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('no_event_type',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_unimplemented_notification_feature(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Unsupported event_type type: UnimplementedFeature"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('no_notification_eventType_type',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def test_unimplemented_activity_feature(self) -> None:
 | 
			
		||||
        expected_subject = u"test"
 | 
			
		||||
        expected_message = u"Unsupported event_type type: UnimplementedFeature"
 | 
			
		||||
 | 
			
		||||
        self.send_and_test_stream_message('no_activity_eventType_type',
 | 
			
		||||
                                          expected_subject,
 | 
			
		||||
                                          expected_message,
 | 
			
		||||
                                          content_type=
 | 
			
		||||
                                          "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
    def get_body(self, fixture_name: Text) -> Text:
 | 
			
		||||
        return self.fixture_data("raygun", fixture_name, file_type="json")
 | 
			
		||||
							
								
								
									
										306
									
								
								zerver/webhooks/raygun/view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								zerver/webhooks/raygun/view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
			
		||||
from typing import Any, Dict, Optional, Text
 | 
			
		||||
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
 | 
			
		||||
from zerver.decorator import api_key_only_webhook_view
 | 
			
		||||
from zerver.lib.actions import check_send_stream_message
 | 
			
		||||
from zerver.lib.request import REQ, has_request_variables
 | 
			
		||||
from zerver.lib.response import json_success
 | 
			
		||||
from zerver.models import UserProfile
 | 
			
		||||
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@api_key_only_webhook_view('Raygun')
 | 
			
		||||
@has_request_variables
 | 
			
		||||
def api_raygun_webhook(request: HttpRequest, user_profile: UserProfile,
 | 
			
		||||
                       payload: Dict[str, Any] = REQ(argument_type='body'),
 | 
			
		||||
                       stream: Text = REQ(default='raygun'),
 | 
			
		||||
                       topic: Optional[Text] = REQ(default='test')) -> HttpResponse:
 | 
			
		||||
    # The payload contains 'event' key. This 'event' key has a value of either
 | 
			
		||||
    # 'error_notification' or 'error_activity'. 'error_notification' happens
 | 
			
		||||
    # when an error is caught in an application, where as 'error_activity'
 | 
			
		||||
    # happens when an action is being taken for the error itself
 | 
			
		||||
    # (ignored/resolved/assigned/etc.).
 | 
			
		||||
    event = payload['event']
 | 
			
		||||
 | 
			
		||||
    # Because we wanted to create a message for all of the payloads, it is best
 | 
			
		||||
    # to handle them separately. This is because some payload keys don't exist
 | 
			
		||||
    # in the other event.
 | 
			
		||||
 | 
			
		||||
    if event == 'error_notification':
 | 
			
		||||
        message = compose_notification_message(payload)
 | 
			
		||||
    elif event == 'error_activity':
 | 
			
		||||
        message = compose_activity_message(payload)
 | 
			
		||||
    else:
 | 
			
		||||
        message = "Unsupported event type: {}".format(event)
 | 
			
		||||
 | 
			
		||||
    check_send_stream_message(user_profile, request.client, stream, topic,
 | 
			
		||||
                              message)
 | 
			
		||||
 | 
			
		||||
    return json_success()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_user_stats_chunk(error_dict: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Creates a stat chunk about total occurrences and users affected for the
 | 
			
		||||
    error.
 | 
			
		||||
 | 
			
		||||
    Example: usersAffected: 2, totalOccurrences: 10
 | 
			
		||||
    Output: 2 users affected with 10 total occurrences
 | 
			
		||||
 | 
			
		||||
    :param error_dict: The error dictionary containing the error keys and
 | 
			
		||||
    values
 | 
			
		||||
    :returns: A message chunk that will be added to the main message
 | 
			
		||||
    """
 | 
			
		||||
    users_affected = error_dict['usersAffected']
 | 
			
		||||
    total_occurrences = error_dict['totalOccurrences']
 | 
			
		||||
 | 
			
		||||
    # One line is subjectively better than two lines for this.
 | 
			
		||||
    return "{} users affected with {} total occurrences\n".format(
 | 
			
		||||
        users_affected, total_occurrences)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_time_chunk(error_dict: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Creates a time message chunk.
 | 
			
		||||
 | 
			
		||||
    Example: firstOccurredOn: "X", lastOccurredOn: "Y"
 | 
			
		||||
    Output:
 | 
			
		||||
    First occurred: X
 | 
			
		||||
    Last occurred: Y
 | 
			
		||||
 | 
			
		||||
    :param error_dict: The error dictionary containing the error keys and
 | 
			
		||||
    values
 | 
			
		||||
    :returns: A message chunk that will be added to the main message
 | 
			
		||||
    """
 | 
			
		||||
    # Make the timestamp more readable to a human.
 | 
			
		||||
    time_first = parse_time(error_dict['firstOccurredOn'])
 | 
			
		||||
    time_last = parse_time(error_dict['lastOccurredOn'])
 | 
			
		||||
 | 
			
		||||
    # Provide time information about this error,
 | 
			
		||||
    return "First occurred: {}\nLast occurred: {}\n".format(time_first,
 | 
			
		||||
                                                            time_last)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_message_chunk(message: Text) -> Text:
 | 
			
		||||
    """Creates a message chunk if exists.
 | 
			
		||||
 | 
			
		||||
    Example: message: "This is an example message" returns "Message: This is an
 | 
			
		||||
    example message". Whereas message: "" returns "".
 | 
			
		||||
 | 
			
		||||
    :param message: The value of message inside of the error dictionary
 | 
			
		||||
    :returns: A message chunk if there exists an additional message, otherwise
 | 
			
		||||
    returns an empty string.
 | 
			
		||||
    """
 | 
			
		||||
    # "Message" shouldn't be included if there is none supplied.
 | 
			
		||||
    return "Message: {}\n".format(message) if message != "" else ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_app_info_chunk(app_dict: Dict[str, str]) -> Text:
 | 
			
		||||
    """Creates a message chunk that contains the application info and the link
 | 
			
		||||
    to the Raygun dashboard about the application.
 | 
			
		||||
 | 
			
		||||
    :param app_dict: The application dictionary obtained from the payload
 | 
			
		||||
    :returns: A message chunk that will be added to the main message
 | 
			
		||||
    """
 | 
			
		||||
    app_name = app_dict['name']
 | 
			
		||||
    app_url = app_dict['url']
 | 
			
		||||
    return "Application details: [{}]({})\n".format(app_name, app_url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def notification_message_follow_up(payload: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Creates a message for a repeating error follow up
 | 
			
		||||
 | 
			
		||||
    :param payload: Raygun payload
 | 
			
		||||
    :return: Returns the message, somewhat beautifully formatted
 | 
			
		||||
    """
 | 
			
		||||
    message = ""
 | 
			
		||||
 | 
			
		||||
    # Link to Raygun about the follow up
 | 
			
		||||
    followup_link_md = "[follow-up error]({})".format(payload['error']['url'])
 | 
			
		||||
 | 
			
		||||
    followup_type = payload['eventType']
 | 
			
		||||
 | 
			
		||||
    if followup_type == "HourlyFollowUp":
 | 
			
		||||
        prefix = "Hourly"
 | 
			
		||||
    else:
 | 
			
		||||
        # Cut the "MinuteFollowUp" from the possible event types, then add "
 | 
			
		||||
        # minute" after that. So prefix for "OneMinuteFollowUp" is "One
 | 
			
		||||
        # minute", where "FiveMinuteFollowUp" is "Five minute".
 | 
			
		||||
        prefix = followup_type[:len(followup_type) - 14] + " minute"
 | 
			
		||||
 | 
			
		||||
    message += "{} {}\n".format(prefix, followup_link_md)
 | 
			
		||||
 | 
			
		||||
    # Get the message of the error.
 | 
			
		||||
    payload_msg = payload['error']['message']
 | 
			
		||||
 | 
			
		||||
    message += make_message_chunk(payload_msg)
 | 
			
		||||
    message += make_time_chunk(payload['error'])
 | 
			
		||||
    message += make_user_stats_chunk(payload['error'])
 | 
			
		||||
    message += make_app_info_chunk(payload['application'])
 | 
			
		||||
 | 
			
		||||
    return message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def notification_message_error_occurred(payload: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Creates a message for a new error or reoccurred error
 | 
			
		||||
 | 
			
		||||
    :param payload: Raygun payload
 | 
			
		||||
    :return: Returns the message, somewhat beautifully formatted
 | 
			
		||||
    """
 | 
			
		||||
    message = ""
 | 
			
		||||
 | 
			
		||||
    # Provide a clickable link that goes to Raygun about this error.
 | 
			
		||||
    error_link_md = "[Error]({})".format(payload['error']['url'])
 | 
			
		||||
 | 
			
		||||
    # Stylize the message based on the event type of the error.
 | 
			
		||||
    if payload['eventType'] == "NewErrorOccurred":
 | 
			
		||||
        message += "**{}**\n".format("New {} occurred!".format(error_link_md))
 | 
			
		||||
    elif payload['eventType'] == "ErrorReoccurred":
 | 
			
		||||
        message += "{}\n".format("{} reoccurred.".format(error_link_md))
 | 
			
		||||
 | 
			
		||||
    # Get the message of the error. This value can be empty (as in "").
 | 
			
		||||
    payload_msg = payload['error']['message']
 | 
			
		||||
 | 
			
		||||
    message += make_message_chunk(payload_msg)
 | 
			
		||||
    message += make_time_chunk(payload['error'])
 | 
			
		||||
    message += make_user_stats_chunk(payload['error'])
 | 
			
		||||
 | 
			
		||||
    # Only NewErrorOccurred and ErrorReoccurred contain an error instance.
 | 
			
		||||
    error_instance = payload['error']['instance']
 | 
			
		||||
 | 
			
		||||
    # Extract each of the keys and values in error_instance for easier handle
 | 
			
		||||
 | 
			
		||||
    # Contains list of tags for the error. Can be empty (null)
 | 
			
		||||
    tags = error_instance['tags']
 | 
			
		||||
 | 
			
		||||
    # Contains the identity of affected user at the moment this error
 | 
			
		||||
    # happened. This surprisingly can be null. Somehow.
 | 
			
		||||
    affected_user = error_instance['affectedUser']
 | 
			
		||||
 | 
			
		||||
    # Contains custom data for this particular error (if supplied). Can be
 | 
			
		||||
    # null.
 | 
			
		||||
    custom_data = error_instance['customData']
 | 
			
		||||
 | 
			
		||||
    if tags is not None:
 | 
			
		||||
        message += "Tags: {}\n".format(", ".join(tags))
 | 
			
		||||
 | 
			
		||||
    if affected_user is not None:
 | 
			
		||||
        user_uuid = affected_user['UUID']
 | 
			
		||||
        message += "Affected user: {}...{}\n".format(user_uuid[:6],
 | 
			
		||||
                                                     user_uuid[-5:])
 | 
			
		||||
 | 
			
		||||
    if custom_data is not None:
 | 
			
		||||
        # We don't know what the keys and values beforehand, so we are forced
 | 
			
		||||
        # to iterate.
 | 
			
		||||
        for key in sorted(custom_data.keys()):
 | 
			
		||||
            message += "{}: {}\n".format(key, custom_data[key])
 | 
			
		||||
 | 
			
		||||
    message += make_app_info_chunk(payload['application'])
 | 
			
		||||
 | 
			
		||||
    return message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compose_notification_message(payload: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Composes a message that contains information on the error
 | 
			
		||||
 | 
			
		||||
    :param payload: Raygun payload
 | 
			
		||||
    :return: Returns a response message
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Get the event type of the error. This can be "NewErrorOccurred",
 | 
			
		||||
    # "ErrorReoccurred", "OneMinuteFollowUp", "FiveMinuteFollowUp", ...,
 | 
			
		||||
    # "HourlyFollowUp" for notification error.
 | 
			
		||||
    event_type = payload['eventType']
 | 
			
		||||
 | 
			
		||||
    # "NewErrorOccurred" and "ErrorReoccurred" contain error instance
 | 
			
		||||
    # information, meaning that it has payload['error']['instance']. The other
 | 
			
		||||
    # event type (the follow ups) doesn't have this instance.
 | 
			
		||||
 | 
			
		||||
    # We now split this main function again into two functions. One is for
 | 
			
		||||
    # "NewErrorOccurred" and "ErrorReoccurred", and one is for the rest. Both
 | 
			
		||||
    # functions will return a text message that is formatted for the chat.
 | 
			
		||||
    if event_type == "NewErrorOccurred" or event_type == "ErrorReoccurred":
 | 
			
		||||
        return notification_message_error_occurred(payload)
 | 
			
		||||
    elif "FollowUp" in event_type:
 | 
			
		||||
        return notification_message_follow_up(payload)
 | 
			
		||||
    else:
 | 
			
		||||
        return "Unsupported event_type type: {}".format(event_type)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def activity_message(payload: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Creates a message from an activity that is being taken for an error
 | 
			
		||||
 | 
			
		||||
    :param payload: Raygun payload
 | 
			
		||||
    :return: Returns the message, somewhat beautifully formatted
 | 
			
		||||
    """
 | 
			
		||||
    message = ""
 | 
			
		||||
 | 
			
		||||
    error_link_md = "[Error]({})".format(payload['error']['url'])
 | 
			
		||||
 | 
			
		||||
    event_type = payload['eventType']
 | 
			
		||||
 | 
			
		||||
    user = payload['error']['user']
 | 
			
		||||
    if event_type == "StatusChanged":
 | 
			
		||||
        error_status = payload['error']['status']
 | 
			
		||||
        message += "{} status changed to: {} by {}\n".format(error_link_md,
 | 
			
		||||
                                                             error_status,
 | 
			
		||||
                                                             user)
 | 
			
		||||
    elif event_type == "CommentAdded":
 | 
			
		||||
        comment = payload['error']['comment']
 | 
			
		||||
        message += "{} left a comment on {}: {}\n".format(user,
 | 
			
		||||
                                                          error_link_md,
 | 
			
		||||
                                                          comment)
 | 
			
		||||
    elif event_type == "AssignedToUser":
 | 
			
		||||
        assigned_to = payload['error']['assignedTo']
 | 
			
		||||
        message += "{} assigned {} to {}\n".format(user,
 | 
			
		||||
                                                   error_link_md,
 | 
			
		||||
                                                   assigned_to)
 | 
			
		||||
 | 
			
		||||
    message += "Timestamp: {}\n".format(parse_time(payload['error']
 | 
			
		||||
                                                   ['activityDate']))
 | 
			
		||||
 | 
			
		||||
    message += make_app_info_chunk(payload['application'])
 | 
			
		||||
 | 
			
		||||
    return message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compose_activity_message(payload: Dict[str, Any]) -> Text:
 | 
			
		||||
    """Composes a message that contains an activity that is being taken to
 | 
			
		||||
    an error, such as commenting, assigning an error to a user, ignoring the
 | 
			
		||||
    error, etc.
 | 
			
		||||
 | 
			
		||||
    :param payload: Raygun payload
 | 
			
		||||
    :return: Returns a response message
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    event_type = payload['eventType']
 | 
			
		||||
 | 
			
		||||
    # Activity is separated into three main categories: status changes (
 | 
			
		||||
    # ignores, resolved), error is assigned to user, and comment added to
 | 
			
		||||
    # an error,
 | 
			
		||||
 | 
			
		||||
    # But, they all are almost identical and the only differences between them
 | 
			
		||||
    # are the keys at line 9 (check fixtures). So there's no need to split
 | 
			
		||||
    # the function like the notification one.
 | 
			
		||||
    if event_type == "StatusChanged" or event_type == "AssignedToUser" \
 | 
			
		||||
            or event_type == "CommentAdded":
 | 
			
		||||
        return activity_message(payload)
 | 
			
		||||
    else:
 | 
			
		||||
        return "Unsupported event_type type: {}".format(event_type)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_time(timestamp: Text) -> Text:
 | 
			
		||||
    """Parses and returns the timestamp provided
 | 
			
		||||
 | 
			
		||||
    :param timestamp: The timestamp provided by the payload
 | 
			
		||||
    :returns: A string containing the time
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Raygun provides two timestamp format, one with the Z at the end,
 | 
			
		||||
    # and one without the Z.
 | 
			
		||||
 | 
			
		||||
    format = "%Y-%m-%dT%H:%M:%S"
 | 
			
		||||
    format += "Z" if timestamp[-1:] == "Z" else ""
 | 
			
		||||
    parsed_time = time.strftime("%c", time.strptime(timestamp,
 | 
			
		||||
                                                    format))
 | 
			
		||||
    return parsed_time
 | 
			
		||||
		Reference in New Issue
	
	Block a user