bbbevents 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b7c540fdb454cc4389952b69531500bfaf6d2997b92c1578117904cd92a4558
4
- data.tar.gz: 953c41dd900c59e78630b5067d6d5b2be5599dce4d154f6744ce183b6d24b889
3
+ metadata.gz: ea120800577b78c239ae7c6b0e1007162493fdad13904dcb88e7f9fe8b2762c2
4
+ data.tar.gz: 61671fcd6e70a3b341aee4e07d1b1961f06aeb3149d9f55785f422cb80af5b3b
5
5
  SHA512:
6
- metadata.gz: a8a525a2317ef15232cfc1ec5ee18b0dfccdc2d5fadf6e5579f6aa241fbf8b0bb3b7935acb3cf1bea66f2cb8197eb6620541404ca7b361913e7b1ef7b7360724
7
- data.tar.gz: 6ecbc66f261eaa9bffdd8948c763dfe1f794cbec99254ab2e844915cd57b088cbef929b3fa7a05dda66e26df406f84c1da1ed6451f83efa988c2d540f7487939
6
+ metadata.gz: e8d31d953e2c5b43f92467200f71ca4dbf8c68ae5e7aacfb00ee1320daa527bc9031add9ae732931ab10005437a20fa5d4d28a37339b03a05fcc3946fbfdfc1a
7
+ data.tar.gz: df822b1085cbe78a1e293e8081a53d7836adbc606ede9c5d62dfc2e407b997afd099505ce5bc2d763a082f628fb450fcb9e985b23b99d207f1ebf178babedc27
@@ -0,0 +1,20 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+
4
+ jobs:
5
+ test:
6
+ name: Rake Test
7
+ strategy:
8
+ matrix:
9
+ ruby: ['2.7', '3.0']
10
+ runs-on: ubuntu-20.04
11
+
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+
20
+ - run: bundler exec rake spec
@@ -0,0 +1,32 @@
1
+ name: bbbevents gem release
2
+ on:
3
+ push:
4
+ tags:
5
+ - '*'
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-20.04
10
+ if: startsWith(github.ref, 'refs/tags/')
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 2.7.0
18
+
19
+ - name: Install dependencies
20
+ run: bundle install
21
+
22
+ - name: Build gem
23
+ run: gem build *.gemspec
24
+
25
+ - name: Push gem
26
+ env:
27
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
28
+ run: |
29
+ pwd
30
+ ls -la
31
+ filename=$(ls *.gem | head -n 1)
32
+ gem push "$filename"
data/.gitignore CHANGED
@@ -6,6 +6,8 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /vendor/
10
+ Gemfile.lock
9
11
 
10
12
  # rspec failure tracking
11
13
  .rspec_status
@@ -14,4 +16,9 @@
14
16
  /spec/testing.csv
15
17
  testdata/*.xml
16
18
 
17
- /bbbevents-*
19
+ /bbbevents-*
20
+ /pec
21
+
22
+ # result of the example script
23
+ /data.csv
24
+ /data.json
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.0
data/README.md CHANGED
@@ -15,7 +15,7 @@ bundle install --path vendor/bundle
15
15
  Copy an `events.xml` file into `testdata/` dir.
16
16
 
17
17
  ```
18
- bundle exec ./example.rb spec/fixtures/files/sample-events.xml
18
+ bundle exec ruby example.rb testdata/events.xml
19
19
  ```
20
20
 
21
21
  ## Installation
@@ -102,10 +102,10 @@ poll.published?
102
102
  # Determine when the poll started.
103
103
  poll.start
104
104
 
105
- # Returns an Array contain possible options.
105
+ # Returns an Array containing possible options.
106
106
  poll.options
107
107
 
108
- # Returns a Hash maping user_id's to their poll votes.
108
+ # Returns a Hash mapping user_id's to their poll votes.
109
109
  poll.votes
110
110
  ```
111
111
 
data/bbbevents.gemspec CHANGED
@@ -21,11 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.15"
25
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "bundler", "~> 2.0"
25
+ spec.add_development_dependency "rake", "~> 13.0"
26
26
  spec.add_development_dependency "rspec", "~> 3.4"
27
27
 
28
28
  # Gem dependecies.
29
- spec.add_dependency 'activesupport', '~> 5.0', '>= 5.0.0.1'
29
+ spec.add_dependency 'activesupport', '>= 5.0.0.1', '< 8'
30
+ spec.add_dependency 'rexml' # Required for activesupport from_xml
30
31
 
31
32
  end
data/example.rb CHANGED
@@ -33,12 +33,10 @@ recording.files
33
33
 
34
34
  # Generate a CSV file with the data.
35
35
  recording.create_csv("data.csv")
36
-
36
+
37
37
  puts "Writing the JSON data"
38
38
 
39
39
  # Write JSON data to file.
40
40
  File.open("data.json", 'w') do |f|
41
41
  f.write(recording.to_json)
42
42
  end
43
-
44
-
@@ -1,6 +1,6 @@
1
1
  module BBBEvents
2
2
  class Attendee
3
- attr_accessor :id, :name, :moderator, :joins, :leaves, :duration, :recent_talking_time, :engagement
3
+ attr_accessor :id, :ext_user_id, :name, :moderator, :joins, :leaves, :duration, :recent_talking_time, :engagement, :sessions
4
4
 
5
5
  MODERATOR_ROLE = "MODERATOR"
6
6
  VIEWER_ROLE = "VIEWER"
@@ -25,6 +25,10 @@ module BBBEvents
25
25
  poll_votes: 0,
26
26
  talk_time: 0,
27
27
  }
28
+
29
+ # A hash of join and lefts arrays for each internal user id
30
+ # { "w_5lmcgjboagjc" => { :joins => [], :lefts => []}}
31
+ @sessions = Hash.new
28
32
  end
29
33
 
30
34
  def moderator?
@@ -59,26 +63,41 @@ module BBBEvents
59
63
  end
60
64
 
61
65
  def to_h
62
- hash = {}
63
- instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
64
- # Convert recent_talking_time to human readable time
65
- if hash["recent_talking_time"] > 0
66
- hash["recent_talking_time"] = Time.at(hash["recent_talking_time"])
67
- else
68
- hash["recent_talking_time"] = ""
69
- end
70
- hash
66
+ {
67
+ id: @id,
68
+ ext_user_id: @ext_user_id,
69
+ name: @name,
70
+ moderator: @moderator,
71
+ joins: @joins,
72
+ leaves: @leaves,
73
+ duration: @duration,
74
+ recent_talking_time: @recent_talking_time > 0 ? Time.at(@recent_talking_time) : '',
75
+ engagement: @engagement,
76
+ sessions: @sessions,
77
+ }
78
+ end
79
+
80
+ def as_json
81
+ {
82
+ id: @id,
83
+ ext_user_id: @ext_user_id,
84
+ name: @name,
85
+ moderator: @moderator,
86
+ joins: @joins.map { |join| BBBEvents.format_datetime(join) },
87
+ leaves: @leaves.map { |leave| BBBEvents.format_datetime(leave) },
88
+ duration: @duration,
89
+ recent_talking_time: @recent_talking_time > 0 ? BBBEvents.format_datetime(Time.at(@recent_talking_time)) : '',
90
+ engagement: @engagement,
91
+ sessions: @sessions.map { |key, session| {
92
+ joins: session[:joins].map { |join| join.merge({ timestamp: BBBEvents.format_datetime(join[:timestamp])}) },
93
+ lefts: session[:lefts].map { |leave| leave.merge({ timestamp: BBBEvents.format_datetime(leave[:timestamp])}) }
94
+ }
95
+ }
96
+ }
71
97
  end
72
98
 
73
99
  def to_json
74
- hash = {}
75
- instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
76
- if hash["recent_talking_time"] > 0
77
- hash["recent_talking_time"] = Time.at(hash["recent_talking_time"])
78
- else
79
- hash["recent_talking_time"] = ""
80
- end
81
- hash.to_json
100
+ JSON.generate(as_json)
82
101
  end
83
102
 
84
103
  private
@@ -5,6 +5,10 @@ module BBBEvents
5
5
  DATE_FORMAT = "%m/%d/%Y %H:%M:%S"
6
6
  UNKNOWN_DATE = "??/??/????"
7
7
 
8
+ def self.format_datetime(time)
9
+ time.strftime('%Y-%m-%dT%H:%M:%S.%L%:z')
10
+ end
11
+
8
12
  def self.parse(events_xml)
9
13
  Recording.new(events_xml)
10
14
  end
@@ -7,9 +7,11 @@ module BBBEvents
7
7
  "public_chat_event",
8
8
  "participant_status_change_event",
9
9
  "participant_talking_event",
10
+ "participant_muted_event",
10
11
  "poll_started_record_event",
11
12
  "user_responded_to_poll_record_event",
12
13
  "add_shape_event",
14
+ "poll_published_record_event",
13
15
  ]
14
16
 
15
17
  EMOJI_WHITELIST = %w(away neutral confused sad happy applause thumbsUp thumbsDown)
@@ -34,13 +36,23 @@ module BBBEvents
34
36
  @attendees[extUserId] = Attendee.new(e) unless @attendees.key?(extUserId)
35
37
  end
36
38
 
39
+ join_ts = Time.at(timestamp_conversion(e["timestamp"]))
40
+
37
41
  # Handle updates for re-joining users
38
42
  att = @attendees[extUserId]
39
- att.joins << Time.at(timestamp_conversion(e["timestamp"]))
43
+ att.joins << join_ts
40
44
  att.name = e['name']
41
45
  if e['role'] == 'MODERATOR'
42
46
  att.moderator = true
43
47
  end
48
+
49
+ join_2 = {:timestamp => join_ts, :userid => intUserId, :ext_userid => extUserId, :event => :join}
50
+
51
+ unless att.sessions.key?(intUserId)
52
+ att.sessions[intUserId] = { :joins => [], :lefts => []}
53
+ end
54
+
55
+ att.sessions[intUserId][:joins] << join_2
44
56
  end
45
57
 
46
58
  # Log a users leave.
@@ -48,8 +60,18 @@ module BBBEvents
48
60
  intUserId = e['userId']
49
61
  # If the attendee exists, set their leave time.
50
62
  if att = @attendees[@externalUserId[intUserId]]
51
- left = Time.at(timestamp_conversion(e["timestamp"]))
52
- att.leaves << left
63
+ left_ts = Time.at(timestamp_conversion(e["timestamp"]))
64
+ att.leaves << left_ts
65
+
66
+ extUserId = 'missing'
67
+ if @externalUserId.key?(intUserId)
68
+ extUserId = @externalUserId[intUserId]
69
+ end
70
+
71
+ left_2 = {:timestamp => left_ts, :userid => intUserId, :ext_userid => extUserId, :event => :left}
72
+ att.sessions[intUserId][:lefts] << left_2
73
+
74
+ record_stop_talking(att, e["timestamp"])
53
75
  end
54
76
  end
55
77
 
@@ -93,7 +115,24 @@ module BBBEvents
93
115
  attendee.engagement[:talks] += 1
94
116
  attendee.recent_talking_time = timestamp_conversion(e["timestamp"])
95
117
  else
96
- attendee.engagement[:talk_time] += timestamp_conversion(e["timestamp"]) - attendee.recent_talking_time
118
+ record_stop_talking(attendee, e["timestamp"])
119
+ end
120
+ end
121
+
122
+ def record_stop_talking(attendee, timestamp_s)
123
+ return if attendee.recent_talking_time == 0
124
+
125
+ attendee.engagement[:talk_time] += timestamp_conversion(timestamp_s) - attendee.recent_talking_time
126
+ attendee.recent_talking_time = 0
127
+ end
128
+
129
+ def participant_muted_event(e)
130
+ intUserId = e["participant"]
131
+
132
+ return unless attendee = @attendees[@externalUserId[intUserId]]
133
+
134
+ if e["muted"] == "true"
135
+ record_stop_talking(attendee, e["timestamp"])
97
136
  end
98
137
  end
99
138
 
@@ -113,6 +152,12 @@ module BBBEvents
113
152
  return unless attendee = @attendees[@externalUserId[intUserId]]
114
153
 
115
154
  if poll = @polls[poll_id]
155
+ if poll.type == 'R-'
156
+ poll.votes[@externalUserId[intUserId]] = e["answer"]
157
+
158
+ # We want to store the responses as options.
159
+ poll.options.insert(e["answerId"].to_i, e["answer"])
160
+ end
116
161
  poll.votes[@externalUserId[intUserId]] = poll.options[e["answerId"].to_i]
117
162
  end
118
163
 
@@ -127,5 +172,13 @@ module BBBEvents
127
172
  end
128
173
  end
129
174
  end
175
+
176
+ def poll_published_record_event(e)
177
+ unless e["pollId"].nil?
178
+ if poll = @polls[e["pollId"]]
179
+ poll.published = true
180
+ end
181
+ end
182
+ end
130
183
  end
131
184
  end
@@ -1,9 +1,11 @@
1
1
  module BBBEvents
2
2
  class Poll
3
- attr_accessor :id, :start, :published, :options, :votes
3
+ attr_accessor :id, :type, :question, :start, :published, :options, :votes
4
4
 
5
5
  def initialize(poll_event)
6
6
  @id = poll_event["pollId"]
7
+ @type = poll_event["type"]
8
+ @question = poll_event["question"].nil? ? "" : "#{poll_event['question']}"
7
9
  @published = false
8
10
  @options = JSON.parse(poll_event["answers"]).map { |opt| opt["key"] }
9
11
  @votes = {}
@@ -18,11 +20,22 @@ module BBBEvents
18
20
  instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
19
21
  hash
20
22
  end
23
+ alias_method :as_json, :to_h
21
24
 
22
25
  def to_json
23
- hash = {}
24
- instance_variables.each { |var| hash[var[1..-1]] = instance_variable_get(var) }
25
- hash.to_json
26
+ JSON.generate(as_json)
27
+ end
28
+
29
+ def as_json
30
+ {
31
+ id: @id,
32
+ type: @type,
33
+ question: @question,
34
+ published: @published,
35
+ options: @options,
36
+ start: BBBEvents.format_datetime(@start),
37
+ votes: @votes
38
+ }
26
39
  end
27
40
  end
28
41
  end
@@ -1,7 +1,9 @@
1
1
  require 'csv'
2
2
  require 'json'
3
+ require 'active_support'
3
4
  require 'active_support/core_ext/hash'
4
5
 
6
+
5
7
  module BBBEvents
6
8
  CSV_HEADER = %w(name moderator chats talks emojis poll_votes raisehand talk_time join left duration)
7
9
  NO_VOTE_SYMBOL = "-"
@@ -15,6 +17,9 @@ module BBBEvents
15
17
  filename = File.basename(events_xml)
16
18
  raise "#{filename} is not a file or does not exist." unless File.file?(events_xml)
17
19
 
20
+ # The Hash.from_xml automatically converts keys with dashes '-' to snake_case
21
+ # (i.e canvas-recording-ready-url becomes canvas_recording_ready_url)
22
+ # see https://www.rubydoc.info/github/datamapper/extlib/Hash.from_xml
18
23
  raw_recording_data = Hash.from_xml(File.read(events_xml))
19
24
 
20
25
  raise "#{filename} is not a valid xml file (unable to parse)." if raw_recording_data.nil?
@@ -22,20 +27,24 @@ module BBBEvents
22
27
 
23
28
  recording_data = raw_recording_data["recording"]
24
29
  events = recording_data["event"]
30
+ events = [] if events.nil?
25
31
  events = [events] unless events.is_a?(Array)
26
32
 
27
33
  @metadata = recording_data["metadata"]
28
34
  @meeting_id = recording_data["metadata"]["meetingId"]
29
-
35
+
30
36
  internal_meeting_id = recording_data["meeting"]["id"]
31
37
 
32
38
  @timestamp = extract_timestamp(internal_meeting_id)
39
+ @start = Time.at(@timestamp / 1000)
33
40
 
34
- @first_event = events.first["timestamp"].to_i
35
- @last_event = events.last["timestamp"].to_i
36
-
37
- @start = Time.at(@timestamp / 1000)
38
- @finish = Time.at(timestamp_conversion(@last_event))
41
+ if events.length > 0
42
+ @first_event = events.first["timestamp"].to_i
43
+ @last_event = events.last["timestamp"].to_i
44
+ @finish = Time.at(timestamp_conversion(@last_event))
45
+ else
46
+ @finish = @start
47
+ end
39
48
  @duration = (@finish - @start).to_i
40
49
 
41
50
  @attendees = {}
@@ -50,7 +59,7 @@ module BBBEvents
50
59
 
51
60
  @attendees.values.each do |att|
52
61
  att.leaves << @finish if att.joins.length > att.leaves.length
53
- att.duration = total_duration(att)
62
+ att.duration = total_duration(@finish, att)
54
63
  end
55
64
  end
56
65
 
@@ -94,15 +103,17 @@ module BBBEvents
94
103
  end
95
104
  end
96
105
 
97
- def to_h
98
- # Transform any CamelCase keys to snake_case.
99
- @metadata.deep_transform_keys! do |key|
100
- k = key.to_s.underscore rescue key
101
- k.to_sym rescue key
102
- end
106
+ # Transform any CamelCase keys to snake_case
107
+ def transform_metadata
108
+ @metadata.deep_transform_keys do |key|
109
+ k = key.to_s.underscore rescue key
110
+ k.to_sym rescue key
111
+ end
112
+ end
103
113
 
114
+ def to_h
104
115
  {
105
- metadata: @metadata,
116
+ metadata: transform_metadata,
106
117
  meeting_id: @meeting_id,
107
118
  duration: @duration,
108
119
  start: @start,
@@ -113,8 +124,230 @@ module BBBEvents
113
124
  }
114
125
  end
115
126
 
127
+ def as_json
128
+ {
129
+ metadata: transform_metadata,
130
+ meeting_id: @meeting_id,
131
+ duration: @duration,
132
+ start: BBBEvents.format_datetime(@start),
133
+ finish: BBBEvents.format_datetime(@finish),
134
+ attendees: attendees.map(&:as_json),
135
+ files: @files,
136
+ polls: polls.map(&:as_json)
137
+ }
138
+ end
139
+
116
140
  def to_json
117
- to_h.to_json
141
+ JSON.generate(as_json)
142
+ end
143
+
144
+ def calculate_user_duration(join_events, left_events)
145
+ joins_leaves_arr = []
146
+ join_events.each { |j| joins_leaves_arr.append({:time => j.to_i, :datetime => j, :event => :join})}
147
+ left_events.each { |j| joins_leaves_arr.append({:time => j.to_i, :datetime => j, :event => :left})}
148
+
149
+ joins_leaves_arr_sorted = joins_leaves_arr.sort_by { |event| event[:time] }
150
+
151
+ partial_duration = 0
152
+ prev_event = nil
153
+
154
+ joins_leaves_arr_sorted.each do |cur_event|
155
+ duration = 0
156
+ if prev_event != nil and cur_event[:event] == :join and prev_event[:event] == :left
157
+ # user left and rejoining, don't update duration
158
+ prev_event = cur_event
159
+ elsif prev_event != nil
160
+ duration = cur_event[:time] - prev_event[:time]
161
+ partial_duration += duration
162
+ prev_event = cur_event
163
+ else
164
+ prev_event = cur_event
165
+ end
166
+ end
167
+
168
+ return partial_duration
169
+ end
170
+
171
+ def calculate_user_duration_based_on_userid(last_event_ts, sessions)
172
+ # combine join and left events into an array
173
+ joins_lefts_arr = build_join_lefts_array(last_event_ts, sessions)
174
+
175
+ # sort the events
176
+ joins_lefts_arr_sorted = joins_lefts_arr.sort_by { |event| event[:timestamp] }
177
+
178
+ combined_tuples = combine_tuples_by_userid(sessions)
179
+ combined_tuples_sorted = fill_missing_left_events(combined_tuples)
180
+
181
+ prepare_joins_lefts_for_overlap_checks(joins_lefts_arr_sorted)
182
+ mark_overlapping_events(combined_tuples_sorted, joins_lefts_arr_sorted)
183
+ removed_overlap_events = remove_overlapping_events(joins_lefts_arr_sorted)
184
+
185
+ duration_tuples = build_join_left_tuples(removed_overlap_events)
186
+
187
+ partial_duration = 0
188
+ duration_tuples.each do |tuple|
189
+ duration = tuple[:left][:timestamp].to_i - tuple[:join][:timestamp].to_i
190
+ partial_duration += duration
191
+ end
192
+
193
+ partial_duration
194
+ end
195
+
196
+ def tuples_by_userid(joins_arr, lefts_arr)
197
+ joins_length = joins_arr.length - 1
198
+ tuples = []
199
+ for i in 0..joins_length
200
+ tuple = {:join => joins_arr[i], :left => nil}
201
+
202
+ if i <= lefts_arr.length - 1
203
+ tuple[:left] = lefts_arr[i]
204
+ end
205
+ tuples.append(tuple)
206
+ end
207
+ tuples
208
+ end
209
+
210
+ def combine_tuples_by_userid(user_sessions)
211
+ combined_tuples = []
212
+
213
+ user_sessions.each do | userid, joins_lefts |
214
+ joins_lefts_arr = []
215
+ joins_lefts[:joins].each { |j| joins_lefts_arr.append(j)}
216
+ joins_lefts[:lefts].each { |j| joins_lefts_arr.append(j)}
217
+
218
+ tuples = tuples_by_userid(joins_lefts[:joins], joins_lefts[:lefts])
219
+
220
+ tuples.each do |tuple|
221
+ combined_tuples.append(tuple)
222
+ end
223
+ end
224
+
225
+ combined_tuples
226
+ end
227
+
228
+ def fill_missing_left_events(combined_tuples)
229
+ joins_lefts_arr_sorted = combined_tuples.sort_by { |event| event[:join][:timestamp]}
230
+
231
+ joins_lefts_arr_sorted_length = joins_lefts_arr_sorted.length - 1
232
+ for i in 0..joins_lefts_arr_sorted_length
233
+ cur_event = joins_lefts_arr_sorted[i]
234
+ if cur_event[:left].nil?
235
+ unless joins_lefts_arr_sorted_length == i
236
+ # Take the next event as the left event for this current event
237
+ next_event = joins_lefts_arr_sorted[i + 1]
238
+ left_event = {:timestamp => next_event[:timestamp], :userid => cur_event[:userid], :event => :left}
239
+
240
+ cur_event[:left] = left_event
241
+ end
242
+ end
243
+ end
244
+
245
+ joins_lefts_arr_sorted
246
+ end
247
+
248
+ def build_join_left_tuples(joins_lefts_arr_sorted)
249
+ jl_tuples = []
250
+ jl_tuple = {:join => nil, :left => nil}
251
+ loop_state = :find_join
252
+
253
+ events_length = joins_lefts_arr_sorted.length - 1
254
+ for i in 0..events_length
255
+
256
+ cur_event = joins_lefts_arr_sorted[i]
257
+
258
+ if loop_state == :find_join and cur_event[:event] == :join
259
+ jl_tuple[:join] = cur_event
260
+ loop_state = :find_left
261
+ end
262
+
263
+ next_event = nil
264
+ if i < events_length
265
+ next_event = joins_lefts_arr_sorted[i + 1]
266
+ end
267
+
268
+ if loop_state == :find_left
269
+ if next_event != nil and next_event[:event] == :left
270
+ # skip the current event to get to the next event
271
+ elsif (cur_event[:event] == :left and next_event != nil and next_event[:event] == :join) or (i == events_length)
272
+ jl_tuple[:left] = cur_event
273
+ jl_tuples.append(jl_tuple)
274
+ jl_tuple = {:join => nil, :left => nil}
275
+ loop_state = :find_join
276
+ end
277
+ end
278
+ end
279
+
280
+ jl_tuples
281
+ end
282
+
283
+ def build_join_lefts_array(last_event_timestamp, user_session)
284
+ joins_leaves_arr = []
285
+ lefts_count = 0
286
+ joins_count = 0
287
+
288
+ user_session.each do | userid, joins_lefts |
289
+ lefts_count += joins_lefts[:lefts].length
290
+ joins_count += joins_lefts[:joins].length
291
+ joins_lefts[:joins].each { |j| joins_leaves_arr.append(j)}
292
+ joins_lefts[:lefts].each { |j| joins_leaves_arr.append(j)}
293
+ end
294
+
295
+ if joins_count > lefts_count
296
+ last_event = joins_leaves_arr[-1]
297
+ joins_leaves_arr.append({:timestamp => last_event_timestamp, :userid => " system ", :ext_userid=> last_event[:ext_userid], :event => :left})
298
+ end
299
+
300
+ joins_leaves_arr
301
+ end
302
+
303
+ def prepare_joins_lefts_for_overlap_checks(joins_leaves_arr_sorted)
304
+ joins_leaves_arr_sorted.each do |event|
305
+ event[:remove] = false
306
+ end
307
+ end
308
+
309
+ def mark_overlapping_events(combined_tuples_sorted, joins_leaves_arr_sorted)
310
+ combined_tuples_sorted.each do |ce|
311
+ joins_leaves_arr_sorted.each do |jl|
312
+ event_ts = jl[:timestamp].to_i
313
+ ce_join = ce[:join][:timestamp].to_i
314
+
315
+ if event_ts > ce_join and not ce[:left].nil? and event_ts < ce[:left][:timestamp].to_i
316
+ jl[:remove] = true
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ def remove_overlapping_events(joins_leaves_arr_sorted)
323
+ keep_events = []
324
+ joins_leaves_arr_sorted.each do |ev|
325
+ if not ev[:remove]
326
+ keep_events.append(ev)
327
+ end
328
+ end
329
+ keep_events
330
+ end
331
+
332
+ def build_tuples_of_kept_events(kept_events)
333
+ odd_events = []
334
+ even_events = []
335
+ for i in 0..kept_events.length - 1
336
+ odd_even = i + 1
337
+ if odd_even.even?
338
+ even_events.append(kept_events[i])
339
+ else
340
+ odd_events.append(kept_events[i])
341
+ end
342
+ end
343
+
344
+ tuples = []
345
+ for i in 0..odd_events.length - 1
346
+ tuple = {:start => odd_events[i], :end => even_events[i]}
347
+ tuples.append(tuple)
348
+ end
349
+
350
+ tuples
118
351
  end
119
352
 
120
353
  private
@@ -138,14 +371,8 @@ module BBBEvents
138
371
  end
139
372
 
140
373
  # Calculates an attendee's duration.
141
- def total_duration(att)
142
- return 0 unless att.joins.length == att.leaves.length
143
- total = 0
144
-
145
- att.joins.length.times do |i|
146
- total += att.leaves[i] - att.joins[i]
147
- end
148
- total
374
+ def total_duration(last_event_ts, att)
375
+ calculate_user_duration_based_on_userid(last_event_ts, att.sessions)
149
376
  end
150
377
  end
151
378
  end
@@ -1,3 +1,3 @@
1
1
  module BBBEvents
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/testdata/.gitkeep ADDED
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bbbevents
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Blindside Networks
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-19 00:00:00.000000000 Z
11
+ date: 2023-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -56,22 +56,36 @@ dependencies:
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '5.0'
62
59
  - - ">="
63
60
  - !ruby/object:Gem::Version
64
61
  version: 5.0.0.1
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '8'
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
- - - "~>"
70
- - !ruby/object:Gem::Version
71
- version: '5.0'
72
69
  - - ">="
73
70
  - !ruby/object:Gem::Version
74
71
  version: 5.0.0.1
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '8'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rexml
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
75
89
  description: Ruby gem for easily parse data from a BigBlueButton recording's events.xml.
76
90
  email:
77
91
  - ffdixon@blindsidenetworks.com
@@ -79,11 +93,12 @@ executables: []
79
93
  extensions: []
80
94
  extra_rdoc_files: []
81
95
  files:
96
+ - ".github/workflows/ci.yml"
97
+ - ".github/workflows/release.yml"
82
98
  - ".gitignore"
83
99
  - ".rspec"
84
- - ".travis.yml"
100
+ - ".ruby-version"
85
101
  - Gemfile
86
- - Gemfile.lock
87
102
  - LICENSE
88
103
  - README.md
89
104
  - Rakefile
@@ -98,6 +113,7 @@ files:
98
113
  - lib/bbbevents/poll.rb
99
114
  - lib/bbbevents/recording.rb
100
115
  - lib/bbbevents/version.rb
116
+ - testdata/.gitkeep
101
117
  homepage: https://www.blindsidenetworks.com
102
118
  licenses:
103
119
  - LGPL-3.0
@@ -117,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
133
  - !ruby/object:Gem::Version
118
134
  version: '0'
119
135
  requirements: []
120
- rubyforge_project:
121
- rubygems_version: 2.7.6
136
+ rubygems_version: 3.1.2
122
137
  signing_key:
123
138
  specification_version: 4
124
139
  summary: Easily parse data from a BigBlueButton recording's events.xml.
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- sudo: false
2
-
3
- language: ruby
4
-
5
- rvm:
6
- - 2.5.1
7
-
8
- before_install: gem install bundler -v 1.17.3
9
-
10
- deploy:
11
- provider: rubygems
12
- api_key: ${RUBYGEMS_API_KEY}
13
- on:
14
- tags: true
data/Gemfile.lock DELETED
@@ -1,48 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- bbbevents (1.2.0)
5
- activesupport
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (5.2.3)
11
- concurrent-ruby (~> 1.0, >= 1.0.2)
12
- i18n (>= 0.7, < 2)
13
- minitest (~> 5.1)
14
- tzinfo (~> 1.1)
15
- concurrent-ruby (1.1.5)
16
- diff-lcs (1.3)
17
- i18n (1.6.0)
18
- concurrent-ruby (~> 1.0)
19
- minitest (5.11.3)
20
- rake (10.5.0)
21
- rspec (3.8.0)
22
- rspec-core (~> 3.8.0)
23
- rspec-expectations (~> 3.8.0)
24
- rspec-mocks (~> 3.8.0)
25
- rspec-core (3.8.2)
26
- rspec-support (~> 3.8.0)
27
- rspec-expectations (3.8.4)
28
- diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.8.0)
30
- rspec-mocks (3.8.1)
31
- diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.8.0)
33
- rspec-support (3.8.2)
34
- thread_safe (0.3.6)
35
- tzinfo (1.2.5)
36
- thread_safe (~> 0.1)
37
-
38
- PLATFORMS
39
- ruby
40
-
41
- DEPENDENCIES
42
- bbbevents!
43
- bundler (>= 1.15)
44
- rake (~> 10.0)
45
- rspec (~> 3.4)
46
-
47
- BUNDLED WITH
48
- 1.17.3