Skip to content

Instantly share code, notes, and snippets.

@JanDeDobbeleer
Last active July 15, 2018 20:54
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JanDeDobbeleer/51b0db9aa37ae0dfb417 to your computer and use it in GitHub Desktop.
Save JanDeDobbeleer/51b0db9aa37ae0dfb417 to your computer and use it in GitHub Desktop.
Dashing - Crashlytics & Xaramin Insights crash free users rate widget + jobs

Description

Display an app's Crashlytics or Xamarin Insights crash free rate. The Crashlytics version also has the current user count, for Xamarin Insights this can only be shown for paying accounts.

Installation

To use this widget, copy reliability.html, reliability.coffee, and reliability.scss into the /widgets/appstore directory. Put the xamarin.rb and crashlytics.rb files in your /jobs folder.

You'll also need a nice little icon for the widget. Use whatever you want, name it crashlytics.png and put it in your /assets/images folder.

To include the widget in a dashboard, add the snippet from dashboard.erb to the dashboard layout file.

Configuration

For the Crashlytics job you will need your credentials, developer token and app id's in order to use the widget. The Xamarin Insights job only uses your credentials and app id's.

You can add the app id's to the configuration like this: [app1 app2 app3]

In case you need a different refresh rate, that can also be adjusted.

require 'net/https'
require 'json'
#--------------------------------------------------------------------------------
# Configuration
#--------------------------------------------------------------------------------
configuration = {
:uri => 'https://fabric.io',
:credentials => {
:username => '',
:password => '',
:developer_token => ''
},
:refresh_rate => '30m',
:app_keys => %w[]
}
#--------------------------------------------------------------------------------
class Crashlytics
def initialize(base_uri, credentials)
@base_uri = base_uri
@credentials = credentials
end
def app_crash_rate(app_key)
begin
uri = URI.parse(@base_uri)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
#fetch the csrf token
request = Net::HTTP::Get.new('/login')
response = http.request(request)
token = response.body.scan(/<meta content=\"(.*)\" name=\"csrf-token\" \/>/)
cookie = response.header["set-cookie"]
#login
request = Net::HTTP::Post.new('/api/v2/session')
set_headers(request, cookie, @credentials[:developer_token], token)
request.set_form_data({"email" => @credentials[:username], "password" => @credentials[:password]})
response = http.request(request)
response_json = JSON.parse(response.body)
cookie = response.header["set-cookie"]
organisation = response_json['current_organization']['id']
#get crash free percentage
response_json = get_json_response(http, rate_endpoint(app_key, organisation), cookie, @credentials[:developer_token], token)
crash_free = response_json['builds']['all'][-1][1]
crash_free_percentage = (crash_free*1000).round.to_f / 10
#get users
response_json = get_json_response(http, users_endpoint(app_key, organisation), cookie, @credentials[:developer_token], token)
current_users = response_json['series'][-1][1]
return {
:number => "#{crash_free_percentage}%",
:state => crash_free_percentage >= 99 ? 'High' : crash_free_percentage < 90 ? 'Low' : 'Medium',
:current => current_users
}
rescue => e
puts "Error getting crash free rate: #{e}"
return {
:number => '?',
:state => 'Failed',
:current => "unknown"
}
end
end
private
def get_json_response(http, endpoint, cookie, developer_token, token)
request = Net::HTTP::Get.new(endpoint)
set_headers(request, cookie, developer_token, token)
response = http.request(request)
JSON.parse(response.body)
end
def rate_endpoint(app_key, organisation_key)
now = Time.now.to_i
from = (Time.now - (24*60*60)).to_i
"/api/v2/organizations/#{organisation_key}/apps/#{app_key}/growth_analytics/crash_free_users_for_top_builds.json?transformation=weighted&limit=3&start=#{from}&end=#{now}"
end
def users_endpoint(app_key, organisation_key)
now = Time.now.to_i
from = (Time.now - (24*60*60)).to_i
"/api/v2/organizations/#{organisation_key}/apps/#{app_key}/growth_analytics/daily_active.json?start=#{from}&end=#{now}&build=all&transformation=seasonal"
end
def set_headers(request, cookie, developer_token, token)
request['Cookie'] = cookie.to_s
request['X-CRASHLYTICS-DEVELOPER-TOKEN'] = developer_token
request['X-CSRF-Token'] = token
request['X-Requested-With'] = 'XMLHttpRequest'
end
end
def get_app_crash_rate(uri, credentials, job)
service = Crashlytics.new(uri, credentials)
service.app_crash_rate(job)
end
configuration[:app_keys].each do |app_key|
SCHEDULER.every configuration[:refresh_rate], :first_in => 0 do |_|
status = get_app_crash_rate(configuration[:uri], configuration[:credentials], app_key)
puts "#{app_key}: #{status}"
send_event(app_key, status)
end
end
<li data-row="3" data-col="1" data-sizex="1" data-sizey="1">
<div data-id="55968703a7ec285da1000014" data-view="Reliability" data-title="Viking Talk Android"></div>
</li>
class Dashing.Reliability extends Dashing.Widget
@accessor 'value', Dashing.AnimatedValue
constructor: ->
super
refreshWidgetState: =>
node = $(@node)
node.removeClass('high low medium')
node.addClass(@get('state').toLowerCase())
ready: ->
@refreshWidgetState()
onData: (data) ->
@refreshWidgetState()
<h1 class="title" data-bind="title"></h1>
<div class="label">Crash free users</div>
<h2 class="state" data-bind="number"></h2>
<p class="more-info">Current active users: <span class="current-users" data-bind="current"></span></p>
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$state-color: #fff;
$background-failed-color: #f44336;
$background-high-color: #00C176;
$background-medium-color: #ff9800;
$background-low-color: #9c4274;
$title-color: rgba(255, 255, 255, 1);
$label-color: rgba(255, 255, 255, 0.7);
$duration-color: rgba(255, 255, 255, 0.7);
$finished-color: rgba(0, 0, 0, 0.3);
.widget-reliability {
background: url("/assets/crashlytics.png") no-repeat center;
background-size: contain;
vertical-align: top;
.title {
color: $title-color;
font-weight: bold;
opacity: .9;
text-align: top;
}
.label {
font-size: 22px;
font-weight: normal;
}
.state {
text-align: center;
display: block;
color: $state-color;
font-size: 76px;
word-wrap: break-word;
font-weight: bold;
}
&.failed {
background-color: $background-failed-color;
}
&.high {
background-color: $background-high-color;
}
&.medium {
background-color: $background-medium-color;
}
&.low {
background-color: $background-low-color;
}
}
require 'net/https'
require 'json'
#--------------------------------------------------------------------------------
# Configuration
#--------------------------------------------------------------------------------
configuration = {
:uri => 'https://insights.xamarin.com',
:credentials => {
:username => '',
:password => ''
},
:refresh_rate => '30m',
:app_keys => %w[]
}
#--------------------------------------------------------------------------------
class Xamarin
def initialize(base_uri, credentials)
@base_uri = base_uri
@credentials = credentials
end
def crash_rate(app_key)
begin
uri = URI.parse(@base_uri)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
#fetch the csrf token
request = Net::HTTP::Post.new('/login')
adjust_headers(request)
request.set_form_data({"username" => @credentials[:username], "password" => @credentials[:password]})
response = http.request(request)
splittedCookie = response.header["set-cookie"].split(';')
cookie = splittedCookie.select { |key| key.lstrip.start_with?("HttpOnly, ") }
if (cookie[0])
xinssid = cookie[0].split(' ')[-1]
request = Net::HTTP::Get.new(crash_rate_endpoint(app_key))
adjust_headers(request)
request['Cookie'] = xinssid
response = http.request(request)
response_json = JSON.parse(response.body)
crash_free = response_json['avgCrashFreeUsers']
return {
:number => "#{crash_free}%",
:state => crash_free >= 99 ? 'High' : crash_free < 90 ? 'Low' : 'Medium',
:current => 'NA'
}
else
return {
:number => '?',
:state => 'Failed',
:current => 'NA'
}
end
rescue => e
puts "Error getting crash free rate: #{e}"
return {
:number => '?',
:state => 'Failed',
:current => 'NA'
}
end
end
private
def crash_rate_endpoint(app_key)
"/api/applications/#{app_key}/statistics/issues"
end
def adjust_headers(request)
request['X-Requested-With'] = 'XMLHttpRequest'
end
end
def xamarin_crash_rate(uri, credentials, job)
service = Xamarin.new(uri, credentials)
service.crash_rate(job)
end
configuration[:app_keys].each do |app_key|
SCHEDULER.every configuration[:refresh_rate], :first_in => 0 do |_|
status = xamarin_crash_rate(configuration[:uri], configuration[:credentials], app_key)
puts "#{app_key} => #{status}\n"
send_event(app_key, status)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment