Skip to content

Commit

Permalink
Merge pull request #2 from blindsidenetworks/rework
Browse files Browse the repository at this point in the history
Restructure and clean, provide tests.
  • Loading branch information
Joshua Arts authored Aug 23, 2018
2 parents 22fb408 + 68e5eae commit 3adf70f
Show file tree
Hide file tree
Showing 19 changed files with 1,297 additions and 282 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@

# rspec failure tracking
.rspec_status

# testing csv file
/spec/testing.csv
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sudo: false
language: ruby
rvm:
- 2.2.4
- 2.5.1
before_install: gem install bundler -v 1.15.4
95 changes: 67 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@ bbbevents is a simple ruby gem that makes it easier to parse data from a recordi

This gem is currently being used on the recording server to parse events and build meeting dashboards.

Currently it can parse data such as...

* Meeting metadata.
* List of attendees.
* Join and leave times.
* Attendee roles.
* Number of chat, talk and emoji status events.
* Uploaded presentation files.
* Chat log.
* Total talk time per user.
* Polls and user votes.

## Installation

Add this line to your application's Gemfile:
Expand All @@ -30,30 +18,81 @@ And then execute:

## Usage

### Recordings
```ruby
require 'bbbevents'

# Parse the recording's events.xml.
rec = BBBEvents.parse('events.xml')
recording = BBBEvents.parse("events.xml")

# Access recording data.
puts rec.metadata
puts rec.start
puts rec.finish
puts rec.attendees
puts rec.moderators
puts rec.viewers
puts rec.chat
puts rec.files
puts rec.polls
puts rec.published_polls
puts rec.unpublished_polls

# Get a hash with all of the parsed data.
puts rec.data
recording.metadata
recording.meeting_id

# Retrieve start, finish time objects or total duration in seconds.
recording.start
recording.finish
recording.duration

# Returns a list of Attendee objects.
recording.attendees
recording.moderators
recording.viewers

# Returns a list of Poll objects.
recording.polls
recording.published_polls
recording.unpublished_polls

# Returns a list of upload files (names only).
recording.files

# Generate a CSV file with the data.
rec.create_csv('data.csv')
recording.create_csv("data.csv")

```

### Attendees
```ruby
# Grab attendee info.
attendee.name
attendee.moderator?

# Fetch initial join, last leave, or total duration.
attendee.duration
attendee.joined
attendee.left

# Fetch all recorded join/leave times.
attendee.joins
attendee.leaves

# View attendee engagement.
attendee.engagement

# => {
# :chats => 11,
# :talks => 7,
# :raisehand => 2,
# :emojis => 5,
# :poll_votes => 2,
# :talk_time => 42
# }
```

### Polls
```ruby
# Determine if poll is published.
poll.published?

# Determine when the poll started.
poll.start

# Returns an Array contain possible options.
poll.options

# Returns a Hash maping user_id's to their poll votes.
poll.votes
```

## License
Expand Down
9 changes: 4 additions & 5 deletions bbbevents.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require "bbbevents/version"
Gem::Specification.new do |spec|
spec.name = "bbbevents"
spec.version = BBBEvents::VERSION
spec.authors = ["Joshua Arts"]
spec.email = ["joshua.rts@blindsidenetworks.com"]
spec.authors = ["Blindside Networks"]
spec.email = ["ffdixon@blindsidenetworks.com"]

spec.summary = %q{Easily parse data from a BigBlueButton recording's events.xml.}
spec.description = %q{Easily parse data from a BigBlueButton recording's events.xml.}
Expand All @@ -23,9 +23,8 @@ Gem::Specification.new do |spec|

spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "rspec", "~> 3.4"

# Gem dependecies.
spec.add_dependency "nokogiri"
spec.add_dependency "nori"
spec.add_dependency "activesupport"
end
72 changes: 72 additions & 0 deletions lib/bbbevents/attendee.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module BBBEvents
class Attendee
attr_accessor :id, :name, :moderator, :joins, :leaves, :duration, :recent_talking_time, :engagement

MODERATOR_ROLE = "MODERATOR"
VIEWER_ROLE = "VIEWER"

def initialize(join_event)
@id = join_event["userId"]
@name = join_event["name"]
@moderator = (join_event["role"] == MODERATOR_ROLE)

@joins = []
@leaves = []
@duration = 0

@recent_talking_time = 0

@engagement = {
chats: 0,
talks: 0,
raisehand: 0,
emojis: 0,
poll_votes: 0,
talk_time: 0,
}
end

def moderator?
moderator
end

# Grab the initial join.
def joined
@joins.first
end

# Grab the last leave.
def left
@leaves.last
end

def csv_row
e = @engagement
[
@name,
@moderator,
e[:chats],
e[:talks],
e[:emojis],
e[:poll_votes],
e[:raisehand],
seconds_to_time(@engagement[:talk_time]),
joined.strftime(DATE_FORMAT),
left.strftime(DATE_FORMAT),
seconds_to_time(@duration),
].map(&:to_s)
end

def to_json
hash = {}
instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
hash.to_json
end

private

def seconds_to_time(seconds)
[seconds / 3600, seconds / 60 % 60, seconds % 60].map { |t| t.floor.to_s.rjust(2, "0") }.join(':')
end
end
end
18 changes: 10 additions & 8 deletions lib/bbbevents/base.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
require 'bbbevents/version'

module BBBEvents

class << self
def parse(obj)
raise 'BBBEvents: Invalid file.' unless File::file?(obj)
RecordingData.new(obj)
end
TIME_FORMAT = "%H:%M:%S"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S"
UNKNOWN_DATE = "??/??/????"

def self.parse(events_xml)
Recording.new(events_xml)
end

end

require 'bbbevents/recording_data'
require 'bbbevents/attendee'
require 'bbbevents/events'
require 'bbbevents/poll'
require 'bbbevents/recording'
104 changes: 104 additions & 0 deletions lib/bbbevents/events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
module BBBEvents
module Events
RECORDABLE_EVENTS = [
"participant_join_event",
"participant_left_event",
"conversion_completed_event",
"public_chat_event",
"participant_status_change_event",
"participant_talking_event",
"poll_started_record_event",
"user_responded_to_poll_record_event",
"add_shape_event",
]

EMOJI_WHITELIST = %w(away neutral confused sad happy applause thumbsUp thumbsDown)
RAISEHAND = "raiseHand"
POLL_PUBLISHED_STATUS = "poll_result"

private

# Log a users join.
def participant_join_event(e)
id = e["userId"]

@attendees[id] = Attendee.new(e) unless @attendees.key?(id)
@attendees[id].joins << Time.at(timestamp_conversion(e["timestamp"]))
end

# Log a users leave.
def participant_left_event(e)
return unless attendee = @attendees[e["userId"]]

left = Time.at(timestamp_conversion(e["timestamp"]))
attendee.leaves << left
end

# Log the uploaded file name.
def conversion_completed_event(e)
@files << e["originalFilename"]
end

# Log a users public chat message
def public_chat_event(e)
return unless attendee = @attendees[e["senderId"]]

attendee.engagement[:chats] += 1 if attendee
end

# Log user status changes.
def participant_status_change_event(e)
return unless attendee = @attendees[e["userId"]]
status = e["value"]

if attendee
if status == RAISEHAND
attendee.engagement[:raisehand] += 1
elsif EMOJI_WHITELIST.include?(status)
attendee.engagement[:emojis] += 1
end
end
end

# Log number of speaking events and total talk time.
def participant_talking_event(e)
return unless attendee = @attendees[e["participant"]]

if e["talking"] == "true"
attendee.engagement[:talks] += 1
attendee.recent_talking_time = timestamp_conversion(e["timestamp"])
else
attendee.engagement[:talk_time] += timestamp_conversion(e["timestamp"]) - attendee.recent_talking_time
end
end

# Log all polls with metadata, options and votes.
def poll_started_record_event(e)
id = e["pollId"]

@polls[id] = Poll.new(e)
@polls[id].start = timestamp_conversion(e["timestamp"])
end

# Log user responses to polls.
def user_responded_to_poll_record_event(e)
user_id = e["userId"]
return unless attendee = @attendees[user_id]

if poll = @polls[e["pollId"]]
poll.votes[user_id] = poll.options[e["answerId"].to_i]
end

attendee.engagement[:poll_votes] += 1
end

# Log if the poll was published.
def add_shape_event(e)
if e["type"] == POLL_PUBLISHED_STATUS
if poll = @polls[e["id"]]
poll.published = true
end
end
end
end
end
22 changes: 22 additions & 0 deletions lib/bbbevents/poll.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module BBBEvents
class Poll
attr_accessor :id, :start, :published, :options, :votes

def initialize(poll_event)
@id = poll_event["pollId"]
@published = false
@options = JSON.parse(poll_event["answers"]).map { |opt| opt["key"] }
@votes = {}
end

def published?
@published
end

def to_json
hash = {}
instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
hash.to_json
end
end
end
Loading

0 comments on commit 3adf70f

Please sign in to comment.