bbbevents 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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