Commit b91851a4a2d3ea17ca2be0bf5072200fbeeab5cf

Authored by Michał Młoźniak
1 parent 2d93bb9b
Exists in master and in 1 other branch production

Implement JSON Api v3

This will allow to capture errors send by airbrake-js.
app/controllers/api/v3/notices_controller.rb 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +class Api::V3::NoticesController < ApplicationController
  2 + skip_before_action :verify_authenticity_token
  3 + skip_before_action :authenticate_user!
  4 +
  5 + respond_to :json
  6 +
  7 + def create
  8 + response.headers['Access-Control-Allow-Origin'] = '*'
  9 + response.headers['Access-Control-Allow-Headers'] = 'origin, content-type, accept'
  10 +
  11 + if !request.options?
  12 + report = JsonParser.new(params).report
  13 +
  14 + if report.valid?
  15 + if report.should_keep?
  16 + report.generate_notice!
  17 + render json: {
  18 + notice: {
  19 + id: report.notice.id
  20 + }
  21 + }
  22 + else
  23 + render text: 'Notice for old app version ignored'
  24 + end
  25 + else
  26 + render text: 'Your API key is unknown', :status => 422
  27 + end
  28 + else
  29 + render nothing: true
  30 + end
  31 + rescue JsonParser::ParamsError
  32 + render text: 'Invalid request'
  33 + end
  34 +end
... ...
config/routes.rb
... ... @@ -58,9 +58,10 @@ Rails.application.routes.draw do
58 58 end
59 59 end
60 60 end
  61 +
  62 + match '/v3/projects/:project_id/notices' => 'v3/notices#create', via: [:post, :options]
61 63 end
62 64  
63 65 root :to => 'apps#index'
64   -
65 66 end
66 67  
... ...
lib/json_parser.rb 0 → 100644
... ... @@ -0,0 +1,86 @@
  1 +class JsonParser
  2 + class ParamsError < StandardError; end
  3 +
  4 + attr_reader :params, :error
  5 +
  6 + def initialize(params)
  7 + @params = params || {}
  8 + end
  9 +
  10 + def report
  11 + attributes = {
  12 + error_class: error['type'],
  13 + message: error['message'],
  14 + backtrace: backtrace,
  15 + request: request,
  16 + server_environment: server_environment,
  17 + api_key: params['key'],
  18 + notifier: params['notifier'],
  19 + user_attributes: user_attributes,
  20 + framework: 'Javascript'
  21 + }
  22 +
  23 + ErrorReport.new(attributes)
  24 + end
  25 +
  26 + private
  27 +
  28 + def error
  29 + raise ParamsError unless params.has_key?('errors') && params['errors'].any?
  30 + @error ||= params['errors'].first
  31 + end
  32 +
  33 + def backtrace
  34 + error['backtrace'].map do |backtrace_line|
  35 + {
  36 + method: backtrace_line['function'],
  37 + file: backtrace_line['file'],
  38 + number: backtrace_line['line'],
  39 + column: backtrace_line['column']
  40 + }
  41 + end
  42 + end
  43 +
  44 + def server_environment
  45 + {
  46 + 'environment-name' => context['environment'],
  47 + 'hostname' => hostname,
  48 + 'project-root' => context['rootDirectory'],
  49 + 'app-version' => context['version']
  50 + }
  51 + end
  52 +
  53 + def request
  54 + environment = params['environment'].merge(
  55 + 'HTTP_USER_AGENT' => context['userAgent']
  56 + )
  57 +
  58 + {
  59 + 'cgi-data' => environment,
  60 + 'session' => params['session'],
  61 + 'params' => params['params'],
  62 + 'url' => url,
  63 + 'component' => context['component'],
  64 + 'action' => context['action']
  65 + }
  66 + end
  67 +
  68 + def user_attributes
  69 + hash = context.slice('userId', 'userUsername', 'userName', 'userEmail')
  70 + Hash[hash.map { |key, value| [key.sub(/^user/, ''), value] }]
  71 + end
  72 +
  73 + def url
  74 + context['url']
  75 + end
  76 +
  77 + def hostname
  78 + URI.parse(url).hostname
  79 + rescue URI::InvalidURIError
  80 + ''
  81 + end
  82 +
  83 + def context
  84 + @context = params['context'] || {}
  85 + end
  86 +end
0 87 \ No newline at end of file
... ...
spec/lib/json_parser_spec.rb 0 → 100644
... ... @@ -0,0 +1,69 @@
  1 +describe JsonParser do
  2 + let(:app) { Fabricate(:app) }
  3 +
  4 + it 'raises error when errors attribute is missing' do
  5 + expect {
  6 + JsonParser.new({}).report
  7 + }.to raise_error(JsonParser::ParamsError)
  8 +
  9 + expect {
  10 + JsonParser.new({'errors' => []}).report
  11 + }.to raise_error(JsonParser::ParamsError)
  12 + end
  13 +
  14 + it 'parses JSON payload and returns ErrorReport' do
  15 + params = JSON.parse(<<-EOL)
  16 + {
  17 + "notifier":{"name":"airbrake-js-v8","version":"0.3.10","url":"https://github.com/airbrake/airbrake-js"},
  18 + "errors":[
  19 + {
  20 + "type":"Error",
  21 + "message":"Error: TestError",
  22 + "backtrace":[
  23 + {"function":"d","file":"http://localhost:3000/assets/application.js","line":11234,"column":24},
  24 + {"function":"c","file":"http://localhost:3000/assets/application.js","line":11233,"column":18},
  25 + {"function":"b","file":"http://localhost:3000/assets/application.js","line":11232,"column":18},
  26 + {"function":"a","file":"http://localhost:3000/assets/application.js","line":11231,"column":18},
  27 + {"function":"HTMLDocument.<anonymous>","file":"http://localhost:3000/assets/application.js","line":11236,"column":3},
  28 + {"function":"fire","file":"http://localhost:3000/assets/application.js","line":1018,"column":34},
  29 + {"function":"Object.self.fireWith [as resolveWith]","file":"http://localhost:3000/assets/application.js","line":1128,"column":13},
  30 + {"function":"Function.jQuery.extend.ready","file":"http://localhost:3000/assets/application.js","line":417,"column":15},
  31 + {"function":"HTMLDocument.DOMContentLoaded","file":"http://localhost:3000/assets/application.js","line":93,"column":14}
  32 + ]
  33 + }
  34 + ],
  35 + "context":{
  36 + "language":"JavaScript",
  37 + "sourceMapEnabled":true,
  38 + "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36",
  39 + "url":"http://localhost:3000/kontakt",
  40 + "userId":1,"userUsername":"john",
  41 + "userName":"John Doe",
  42 + "userUsername": "john",
  43 + "userEmail":"john.doe@example.org",
  44 + "version":"1.0",
  45 + "component":"ContactsController",
  46 + "action":"show"
  47 + },
  48 + "params":{"returnTo":"dashboard"},
  49 + "environment":{"navigator_vendor":"Google Inc."},
  50 + "session":{"isAdmin":true},
  51 + "key":"#{app.api_key}"
  52 + }
  53 + EOL
  54 +
  55 + report = JsonParser.new(params).report
  56 + notice = report.generate_notice!
  57 +
  58 + expect(report.error_class).to eq('Error')
  59 + expect(report.message).to eq('Error: TestError')
  60 + expect(report.backtrace.lines.size).to eq(9)
  61 + expect(notice.user_attributes).to include({'Id' => 1, 'Name' => 'John Doe', 'Email' => 'john.doe@example.org', 'Username' => 'john'})
  62 + expect(notice.session).to include('isAdmin' => true)
  63 + expect(notice.params).to include('returnTo' => 'dashboard')
  64 + expect(notice.env_vars).to include(
  65 + 'navigator_vendor' => 'Google Inc.',
  66 + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36'
  67 + )
  68 + end
  69 +end
0 70 \ No newline at end of file
... ...