Skip to content

Instantly share code, notes, and snippets.

@blackjid
Last active August 29, 2015 14:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blackjid/2f0b6010d6ffab479d61 to your computer and use it in GitHub Desktop.
Save blackjid/2f0b6010d6ffab479d61 to your computer and use it in GitHub Desktop.
Google Calendar Attendees widget for Dashing

Description

A Dashing widget for displaying the number of attendees for a specific calendar event on Google Calendar

It's specially designed to be used against a recursive event. For example, we use it at platanus to show how many people and who is going to have lunch in the office.

screen shot

Made by platanus in Chile

Dependencies

google-api-ruby-client rails/activesupport

Add it to dashing's gemfile:

gem 'google-api-client'
gem 'activesupport'

and run bundle install.

Usage

To use this widget, you'll first need to set up a Google API project.

  1. Create and download a new private key for Google API access.

    1. Go to https://code.google.com/apis/console
    2. Click 'Create Project'
    3. Enable 'Analytics API' service and accept both TOS's
    4. Click 'API Access' in the left-hand nav menu
    5. Click 'Create an OAuth 2.0 Client ID'
    6. Enter a product name (e.g. Dashing Widget) - logo and url are optional
    7. Click 'Next'
    8. Under Application Type, select 'Service Account'
    9. Click 'Create Client ID'
    10. Click 'Download private key' NOTE: This will be your only opportunity to download this key.
    11. Note the password for your new private key ('notasecret')
    12. Close the download key dialog
    13. Find the details for the service account you just created and copy it's email address which will look something like this: 210987654321-3rmagherd99kitt3h5@developer.gserviceaccount.com - you'll need it in environmental variables later
  2. Setup your widget

    1. You can install the widget with dashing install 2f0b6010d6ffab479d61, or manually add each file in the corresponding location

    2. Setup your environmental variables

      GOOGLE_SERVICE_ACCOUNT_EMAIL # Email of service account
      GOOGLE_SERVICE_PK_FILE # File containing your private key
      GOOGLE_SERVICE_KEY_SECRET # Password to unlock private key 'notasecret'
      ATTENDEE_CALENDAR_ID # Calendar ID.
      ATTENDEE_EVENT_ID # Event ID
      DEFAULT_GRAVATAR # Url to image to show as default when no gravatar
      NEXT_EVENT_TIME # Time to start showing the next event 'America/Los Angeles'
    3. Add the widget HTML to your dashboard

      <li data-row="1" data-col="4" data-sizex="1" data-sizey="1">
        <div data-id="attendees"
             data-view="Attendees"
             data-today-message="Today lunch"
             data-tomorrow-message="Tomorrow lunch"
             data-no-event-message="No lunch"
             data-updatedAtMessage="Updated"></div>
           <i class="fa fa-cutlery icon-background"></i>
      </li>

Notes

To set your PK12 certificate in heroku you can follow this guide http://ar.zu.my/how-to-store-private-key-files-in-heroku/

class Dashing.Attendees extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
@set('detailMessage', if data.tomorrow_event then @get('tomorrowMessage') else @get('todayMessage') )
<div data-renderif="calendar_event">
<h1 class="title">
<span data-bind="calendar_event.title"></span>
<span class="counter" data-bind="calendar_event.total_attendees"></span>
</h1>
<ul class="clearfix">
<li class="attendee" data-foreach-item="calendar_event.attendees">
<img class="gravatar" data-bind-src="item.gravatar" width="62px" height="62px"/>
<div class="counter" data-bind="'+%{extras}' | interpolate {'extras': 'item.additionalGuests'}" data-insertif="item.additionalGuests"></div>
</li>
</ul>
<p class="more-info">
<span data-bind="detailMessage"></span>
</p>
<p class="updated-at" data-bind="updatedAtMessage"></p>
</div>
<div data-hideif="event">
<h1 class="title" data-bind="noEventMessage"></h1>
</div>
# encoding: UTF-8
require 'google/api_client'
require 'date'
require 'time'
require 'digest/md5'
require 'active_support'
require 'active_support/all'
# Update these to match your own apps credentials
service_account_email = ENV['GOOGLE_SERVICE_ACCOUNT_EMAIL'] # Email of service account
key_file = ENV['GOOGLE_SERVICE_PK_FILE'] # File containing your private key
key_secret = ENV['GOOGLE_SERVICE_KEY_SECRET'] # Password to unlock private key
calendarID = ENV['ATTENDEE_CALENDAR_ID'] # Calendar ID.
eventId = ENV['ATTENDEE_EVENT_ID'] # Event ID
defaultImg = ENV['DEFAULT_GRAVATAR'] # Url to image to show as default when no gravatar
next_event_time = ENV['NEXT_EVENT_TIME'] || '14:00' # Time to start showing the next event
next_event_timezone = ENV['NEXT_EVENT_TIMEZONE'] # Time zone to parse the next event time in
# Get the Google API client
client = Google::APIClient.new(:application_name => 'Google Calendar Attendee Widget',
:application_version => '0.0.1')
# Load your credentials for the service account
if not key_file.nil? and File.exists? key_file
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, key_secret)
else
key = OpenSSL::PKey::RSA.new ENV['GOOGLE_SERVICE_PK'], key_secret
end
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/calendar.readonly',
:issuer => service_account_email,
:signing_key => key)
# Start the scheduler
SCHEDULER.every '60s', :first_in => 4 do |job|
# Request a token for our service account
client.authorization.fetch_access_token!
# Get the calendar API
calendar = client.discovered_api('calendar','v3')
# Start and end dates
Time.zone = next_event_timezone
today = Date.today.in_time_zone
tomorrow = Date.today.next_day.in_time_zone
after_tomorrow = Date.today.next_day(2).in_time_zone
# Check if the current time has passed the defined threshold
show_tomorrow_event = Time.zone.now >= Time.zone.parse(next_event_time)
# Search event dates
startDate = !show_tomorrow_event ? today.to_datetime.rfc3339 : tomorrow.to_datetime.rfc3339
endDate = !show_tomorrow_event ? tomorrow.to_datetime.rfc3339 : after_tomorrow.to_datetime.rfc3339
# Get the events
events = client.execute(:api_method => calendar.events.instances,
:parameters => {
:calendarId => calendarID,
:eventId => eventId,
:maxResults => 1,
:timeMin => startDate,
:timeMax => endDate
}
)
# Set the event if there is one found
if events.data.items.count > 0
# Get the first matching event
calendar_event = events.data.items.first
# Get only the attedees that had accepted
accepted_attendees = calendar_event.attendees.select {|attendee| attendee.responseStatus == 'accepted'}
# Prepare the attendees object
accepted_attendees = accepted_attendees.map do |attendee|
email = attendee.email.downcase
# Force additional guests property
attendee['additionalGuests'] = attendee['additionalGuests'] || 0
# Set the gravatar url
hash = Digest::MD5.hexdigest(email)
attendee[:gravatar] = "http://www.gravatar.com/avatar/#{hash}"
attendee[:gravatar] += "?default=#{defaultImg}" if defaultImg
attendee
end
# Event hash to pass to dashing
event = {
title: calendar_event.summary,
attendees: accepted_attendees,
total_attendees: accepted_attendees.reduce(accepted_attendees.length){|r, v| r + v['additionalGuests']},
}
end
# Update the dashboard
send_event('attendees', {
calendar_event: event,
tomorrow_event: show_tomorrow_event
})
end
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #EEC647;
$value-color: #fff;
$title-color: rgba(255, 255, 255, 0.7);
$label-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-list styles
// ----------------------------------------------------------------------------
.widget-attendees {
background-color: $background-color;
vertical-align: top;
padding: 15px 12px 40px 12px !important;
.title {
color: $title-color;
display: inline-block;
.counter{
display: inline-block;
width: 40px;
height: 40px;
line-height: 40px;
background: #F14D00;
border-radius: 20px;
font-size: 30px;
font-weight: bold;
}
}
.value {
font-weight: 600;
color: $value-color;
}
ol {
list-style-position: inside;
}
.list-nostyle {
list-style: none;
}
.label {
color: $label-color;
}
.updated-at {
color: rgba(0, 0, 0, 0.3);
}
.more-info {
color: $moreinfo-color;
}
.attendee {
float: left;
margin: 4px;
position: relative;
.counter{
position: absolute;
width: 22px;
height: 22px;
z-index: 10;
top: -4px;
right: -4px;
line-height: 22px;
background: #F14D00;
border-radius: 11px;
font-size: 12px;
font-weight: bold;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment