UNPKG

7.84 kBtext/coffeescriptView Raw
1require "sugar"
2fs = require "fs"
3path = require "path"
4Handlebars = require "handlebars"
5Table = require('cli-table')
6argv = require("optimist").argv
7
8Handlebars.registerHelper "eachSummaryFrame", (stack, options) ->
9 NotificationPlugin.getSummaryStacktrace(stack).map((line) ->
10 options.fn line
11 ).join ""
12
13#
14# The base Bugsnag NotificationPlugin class
15# Extend this class to create your own Bugsnag notification plugins:
16#
17# NotificationPlugin = require "../../notification-plugin.js"
18# class MyPlugin extends NotificationPlugin
19# @receiveEvent = (config, event) ->
20# ...
21# module.exports = MyPlugin
22#
23# All notification plugins must override the receiveEvent function to perform
24# the notification. This method is fired when a new event is triggered.
25#
26# See https://github.com/bugsnag/bugsnag-notification-plugins/ for full docs
27#
28
29module.exports = class NotificationPlugin
30
31 # Load templates
32 @markdownTemplate = Handlebars.compile(fs.readFileSync(__dirname + "/templates/error.md.hbs", "utf8"))
33 @htmlTemplate = Handlebars.compile(fs.readFileSync(__dirname + "/templates/error.html.hbs", "utf8"))
34 @textTemplate = Handlebars.compile(fs.readFileSync(__dirname + "/templates/error.text.hbs", "utf8"))
35
36 # Utility methods for http requests
37 @request = require "superagent"
38
39 # Fired when a new event is triggered for notification
40 # Plugins MUST override this method
41 @receiveEvent = (config, event, callback) ->
42 throw new Error("Plugins must override receiveEvent")
43
44 # Utility methods for generating notification content
45 @stacktraceLineString = (stacktraceLine) ->
46 stacktraceLine.file + ":" + stacktraceLine.lineNumber + " - " + stacktraceLine.method
47
48 @basicStacktrace = (stacktrace) ->
49 @getSummaryStacktrace(stacktrace).map((line) ->
50 @stacktraceLineString line
51 , this).join "\n"
52
53 # Returns the first line of a stacktrace (formatted)
54 @firstStacktraceLine = (stacktrace) ->
55 @stacktraceLineString @getSummaryStacktrace(stacktrace)[0]
56
57 # Utility to determine whether a stacktrace line is `inProject`
58 @inProjectStacktraceLine = (line) ->
59 line? and "inProject" of line and line.inProject
60
61 # Utility for getting all the stacktrace lines that are `inProject`
62 @getSummaryStacktrace = (stacktrace) ->
63 filtered = undefined
64
65 # If there are no 'inProject' stacktrace lines
66 filtered = stacktrace.slice(0, 3) unless (filtered = stacktrace.filter(@inProjectStacktraceLine)).length
67 filtered
68
69 @title = (event) ->
70 event.error.exceptionClass + " in " + event.error.context
71
72 @markdownBody = (event) ->
73 @markdownTemplate event
74
75 @htmlBody = (event) ->
76 @htmlTemplate event
77
78 @textBody = (event) ->
79 @textTemplate event
80
81 # Fire a test event to your notification plugin (do not override)
82 @fireTestEvent = (config, callback) ->
83 event =
84 error:
85 exceptionClass: "ExampleException"
86 message: "Something really bad happened"
87 context: "home#example"
88 appVersion: "1.0.0"
89 releaseStage: "production"
90 occurrences: 42
91 firstReceived: new Date()
92 usersAffected: 20
93 url: "http://bugsnag.com/errors/example/events/example"
94 stacktrace: [
95 {
96 file: "app/controllers/home_controller.rb"
97 lineNumber: 123
98 method: "example"
99 inProject: true
100 }
101 {
102 file: "app/controllers/other_controller.rb"
103 lineNumber: 12
104 method: "broken"
105 inProject: true
106 }
107 {
108 file: "gems/junk/junkfile.rb"
109 lineNumber: 999
110 method: "something"
111 inProject: false
112 }
113 {
114 file: "lib/important/magic.rb"
115 lineNumber: 4
116 method: "load_something"
117 inProject: true
118 }
119 ]
120
121 project:
122 name: "Example.com"
123 url: "http://bugsnag.com/projects/example"
124
125 trigger:
126 type: "firstException"
127 message: "New exception"
128
129 if config.spike
130 delete config.spike
131 event.trigger =
132 type: "projectSpiking"
133 message: "Project Spiking"
134 rate: 103
135
136 if config.comment
137 delete config.comment
138 event.trigger =
139 type: "comment"
140 message: "Comment Added"
141 event.comment =
142 message: "I think this should be easy to fix"
143 event.user =
144 name: "John Smith"
145
146 if config.reopened
147 delete config.reopened
148 event.trigger =
149 type: "reopened"
150 message: "Resolved error re-occurred"
151
152 if config.createdIssue
153 info = config.createdIssue.split(",")
154 event.error.createdIssue = {}
155 event.error.createdIssue[info[0]] = info[1]
156 delete config.createdIssue
157
158 @receiveEvent config, event, callback
159 return
160
161 # Configuration validation methods (do not override)
162 @validateConfig = (config, pluginConfigFile) ->
163 pluginConfig = require pluginConfigFile
164 if pluginConfig.fields
165 pluginConfig.fields.each (option) ->
166 configValue = config[option.name]
167
168 # Validate all non-optional config fields are present
169 throw new Error("ConfigurationError: Required configuration option '" + option.name + "' is missing") unless configValue isnt `undefined` or option.optional or (option.type is "boolean" and option.defaultValue isnt `undefined`)
170
171 # Validate fields with allowed values
172 throw new Error("ConfigurationError: Invalid value for '" + option.name + "'") if configValue isnt `undefined` and option.allowedValues and option.allowedValues.none(configValue)
173
174 # Fill in default values
175 config[option.name] = option.defaultValue if not configValue? and option.defaultValue isnt `undefined`
176 return
177
178 return
179
180printHelp = (configFile) ->
181 table = new Table(
182 chars:
183 'top': ''
184 'top-mid': ''
185 'top-left': ''
186 'top-right': ''
187 'bottom': ''
188 'bottom-mid': ''
189 'bottom-left': ''
190 'bottom-right': ''
191 'left': ''
192 'left-mid': ''
193 'mid': ''
194 'mid-mid': ''
195 'right': ''
196 'right-mid': ''
197 'middle': ' '
198 style:
199 'padding-left': 5
200 'padding-right': 0
201 )
202
203 configFile.fields.forEach (field) ->
204 fieldDesc = "--#{field.name}=#{field.type || "string"}"
205 fieldDesc = fieldDesc + " (optional)" if field.optional
206 table.push [fieldDesc, field.description]
207
208 table.push ["",""]
209 table.push ["--reopened (optional)", "Simulate an error reopening"]
210 table.push ["--createdIssue=string,string (optional)", "Simulate created issue metadata key,value"]
211 table.push ["--comment (optional)", "Simulate a comment"]
212 table.push ["--spike (optional)", "Simulate a project spike"]
213 console.log ""
214 console.log "Attempting to test the #{path.basename(path.dirname(module.parent.filename))} integration"
215 console.log table.toString()
216
217# If running plugins from the command line, allow them to fire test events
218if module.parent and module.parent.parent is null
219 pluginConfigFile = require(path.dirname(module.parent.filename) + "/config.json")
220
221 # Parse command line flags
222 flags = Object.keys(argv).exclude("_", "$0")
223 config = {}
224 if flags.indexOf("help") != -1
225 return printHelp(pluginConfigFile)
226 flags.each (flag) ->
227 config[flag] = argv[flag] if argv[flag]? and argv[flag] isnt ""
228
229 # Validate configuration
230 try
231 NotificationPlugin.validateConfig config, path.dirname(module.parent.filename) + "/config.json"
232 catch err
233 console.error err.message
234 printHelp(pluginConfigFile)
235 return
236
237 # Fire a test event
238 plugin = require(module.parent.filename)
239 plugin.fireTestEvent config, (err, data) ->
240 if err
241 console.error "Error firing notification\n", err
242 else
243 console.log "Fired test event successfully\n", data