web-ex-events 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6313292bccf157b4ad36a2cbf03c81641e4bd024
4
+ data.tar.gz: 256e43c16759d10915bf1b80bf89ab29cb2404b8
5
+ SHA512:
6
+ metadata.gz: d39ebb09c79cb01a8f18a02efcfe5645c67c7fce0ecaf3e549fa858fb078c4228407f010d5b4d4c482ab37a4cd3316414c6d3beff31d518bb9a1c8b182f0f408
7
+ data.tar.gz: 17931565713e2273947abc0298abbe5e123eb83ec66afc78ba0ccf5e5e8f7774b1384cf3f9414a2e01251257b3f1b6d945fdb5a44509bfc58e4b64d9d3d336e5
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in web-ex-events.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Pete Nicholls
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # WebEx::Events
2
+
3
+ A library to read WebEx events through the WebEx API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'web-ex-events'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install web-ex-events
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'web_ex/events'
23
+
24
+ # Set WebEx site name
25
+ ENV['WEB_EX_SITE_NAME'] = "…"
26
+
27
+ # Web Ex Site and Partner ID (available through WebEx support request)
28
+ ENV['WEB_EX_SITE_ID'] = "…"
29
+ ENV['WEB_EX_PARTNER_ID'] = "…"
30
+
31
+ # Your WebEx credentials
32
+ ENV['WEB_EX_USERNAME'] = "…"
33
+ ENV['WEB_EX_PASSWORD'] = "…"
34
+
35
+ next_30_days = 30 * 24 * 60 * 60
36
+ events = WebEx::Events::Event.fetch(next_30_days)
37
+
38
+ # Print event details
39
+ events.each do |event|
40
+ puts event.name
41
+ puts event.start_date
42
+ puts "#{event.duration} mins"
43
+ puts event.description
44
+ end
45
+
46
+ # Print all events as JSON
47
+ puts events.to_json
48
+ ```
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,10 @@
1
+ require 'nokogiri'
2
+ require 'httparty'
3
+ require 'tzinfo'
4
+
5
+ require "web_ex/events/version"
6
+ require "web_ex/events/configuration"
7
+ require "web_ex/events/event"
8
+ require "web_ex/events/event_list"
9
+ require "web_ex/events/event_summary_request"
10
+ require "web_ex/events/time_with_zone"
@@ -0,0 +1,15 @@
1
+ module WebEx
2
+ module Events
3
+ class Configuration
4
+
5
+ %w( site_name site_id username password partner_id ).each do |name|
6
+ define_singleton_method(name) { ENV["WEBEX_#{name.upcase}"] }
7
+ end
8
+
9
+ def self.xml_service_url
10
+ "https://#{site_name}.webex.com/WBXService/XMLService"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ module WebEx
2
+ module Events
3
+ class Event
4
+ attr_accessor :name, :start_date, :end_date, :key, :description, :duration
5
+
6
+ def initialize(attributes = {})
7
+ attributes.each do |k, v|
8
+ send("#{k}=", v)
9
+ end
10
+ end
11
+
12
+ def to_json(*args)
13
+ JSON.dump(
14
+ name: name,
15
+ start_date: start_date,
16
+ end_date: end_date,
17
+ key: key,
18
+ description: description,
19
+ duration: duration
20
+ )
21
+ end
22
+
23
+ def self.from_xml(xml)
24
+ node = Nokogiri::XML(xml)
25
+ element = node.xpath('event')
26
+
27
+ self.new(
28
+ name: element.xpath('sessionName').text,
29
+ start_date: TimeWithZone.new(element.xpath('startDate').text, element.xpath('timeZoneID').text).to_datetime,
30
+ end_date: TimeWithZone.new(element.xpath('endDate').text, element.xpath('timeZoneID').text).to_datetime,
31
+ key: element.xpath('sessionKey').text,
32
+ description: element.xpath('description').text,
33
+ duration: element.xpath('duration').text.to_i
34
+ )
35
+ end
36
+
37
+ def self.fetch(time_frame)
38
+ request = EventSummaryRequest.new(start_date: Time.now, end_date: Time.now + time_frame)
39
+ request.find_events!
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module WebEx
2
+ module Events
3
+ class EventList
4
+ include Enumerable
5
+
6
+ def initialize(*events)
7
+ @events = events.flatten
8
+ end
9
+
10
+ def length
11
+ @events.length
12
+ end
13
+
14
+ def each(&block)
15
+ @events.each(&block)
16
+ end
17
+
18
+ def last
19
+ @events.last
20
+ end
21
+
22
+ def empty?
23
+ @events.empty?
24
+ end
25
+
26
+ def to_json
27
+ JSON.dump(@events)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ module WebEx
2
+ module Events
3
+ class EventSummaryRequest
4
+ WEBEX_EMPTY_RESULTS_EXCEPTION_ID = "000015"
5
+
6
+ attr_accessor :start_date, :end_date
7
+
8
+ def initialize(options = {})
9
+ @start_date, @end_date = options[:start_date], options[:end_date]
10
+ end
11
+
12
+ def find_events!
13
+ events = xml_response.xpath('//event').map do |node|
14
+ Event.from_xml(node.to_xml)
15
+ end
16
+
17
+ EventList.new(events)
18
+ end
19
+
20
+ def payload
21
+ <<-XML
22
+ <?xml version="1.0" encoding="UTF-8"?>
23
+ <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
24
+ <header>
25
+ <securityContext>
26
+ <webExID>#{Configuration.username}</webExID>
27
+ <password>#{Configuration.password}</password>
28
+ <siteID>#{Configuration.site_id}</siteID>
29
+ <partnerID>#{Configuration.partner_id}</partnerID>
30
+ </securityContext>
31
+ </header>
32
+ <body>
33
+ <bodyContent xsi:type="java:com.webex.service.binding.event.LstsummaryEvent">
34
+ <listControl>
35
+ <startFrom/>
36
+ </listControl>
37
+ <order>
38
+ <orderBy>STARTTIME</orderBy>
39
+ </order>
40
+ <dateScope>
41
+ <startDateStart>#{formatted_date start_date}</startDateStart>
42
+ <endDateEnd>#{formatted_date end_date}</endDateEnd>
43
+ <timeZoneID>#{TimeWithZone::UTC_ZONE_ID}</timeZoneID>
44
+ </dateScope>
45
+ </bodyContent>
46
+ </body>
47
+ </message>
48
+ XML
49
+ end
50
+
51
+ private
52
+
53
+ def response
54
+ @response ||= HTTParty.post(Configuration.xml_service_url, body: payload)
55
+ end
56
+
57
+ def xml_response
58
+ @xml_response ||= begin
59
+ xml = Nokogiri::XML(response.body)
60
+ webex_exception = xml.at_xpath('//serv:exceptionID')
61
+
62
+ if webex_exception && webex_exception.content != WEBEX_EMPTY_RESULTS_EXCEPTION_ID
63
+ raise "WebEx Error: #{xml.at_xpath('//serv:reason').content}"
64
+ end
65
+
66
+ xml.remove_namespaces!
67
+ end
68
+ end
69
+
70
+ def formatted_date(date)
71
+ date.utc.strftime('%m/%d/%Y %H:%M:%S') if date
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,83 @@
1
+ module WebEx
2
+ module Events
3
+ class TimeWithZone
4
+ UTC_ZONE_ID = 21
5
+
6
+ TIME_ZONE_MAPPINGS = {
7
+ 0 => "Pacific/Niue",
8
+ 1 => "Pacific/Apia",
9
+ 2 => "Pacific/Honolulu",
10
+ 3 => "America/Juneau ",
11
+ 4 => "America/Los_Angeles",
12
+ 5 => "America/Phoenixr",
13
+ 6 => "America/Denver",
14
+ 7 => "America/Chicago",
15
+ 8 => "America/Tegucigalpa",
16
+ 9 => "America/Regina",
17
+ 10 => "America/Bogota",
18
+ 11 => "America/New_York",
19
+ 12 => "America/Indiana/Indianapolis",
20
+ 13 => "America/Halifax",
21
+ 14 => "America/Caracas",
22
+ 15 => "America/St_Johns",
23
+ 16 => "America/Sao_Paulo",
24
+ 17 => "America/Argentina/Buenos_Aires",
25
+ 18 => "Atlantic/South_Georgia",
26
+ 19 => "Atlantic/Azores",
27
+ 20 => "Africa/Casablanca",
28
+ 21 => "Europe/London",
29
+ 22 => "Europe/Amsterdam",
30
+ 23 => "Europe/Paris",
31
+ 24 => "Europe/Paris",
32
+ 25 => "Europe/Berlin",
33
+ 26 => "Europe/Athens",
34
+ 27 => "Europe/Athens",
35
+ 28 => "Africa/Cairo",
36
+ 29 => "Africa/Johannesburg",
37
+ 30 => "Europe/Helsinki",
38
+ 31 => "Asia/Amman",
39
+ 32 => "Asia/Baghdad",
40
+ 33 => "Europe/Moscow",
41
+ 34 => "Africa/Nairobi",
42
+ 35 => "Asia/Tehran",
43
+ 36 => "Asia/Muscat",
44
+ 37 => "Asia/Baku",
45
+ 38 => "Asia/Kabul",
46
+ 39 => "Asia/Yekaterinburg",
47
+ 40 => "Asia/Karachi",
48
+ 41 => "Asia/Kolkata",
49
+ 42 => "Asia/Colombo",
50
+ 43 => "Asia/Almaty",
51
+ 44 => "Asia/Bangkok",
52
+ 45 => "Asia/Shanghai",
53
+ 46 => "Australia/Perth",
54
+ 47 => "Asia/Singapore",
55
+ 48 => "Asia/Taipei",
56
+ 49 => "Asia/Tokyo",
57
+ 50 => "Asia/Seoul",
58
+ 51 => "Asia/Yakutsk",
59
+ 52 => "Australia/Adelaide",
60
+ 53 => "Australia/Darwin",
61
+ 54 => "Australia/Brisbane",
62
+ 55 => "Australia/Sydney",
63
+ 56 => "Pacific/Guam",
64
+ 57 => "Australia/Hobart",
65
+ 58 => "Asia/Vladivostok",
66
+ 59 => "Asia/Magadan",
67
+ 60 => "Pacific/Auckland",
68
+ 61 => "Pacific/Fiji"
69
+ }
70
+
71
+ def initialize(date_string, timezone_id)
72
+ @date_string = date_string
73
+ @timezone_id = timezone_id.to_i
74
+ end
75
+
76
+ def to_datetime
77
+ month, day, year, hour, minute, second = @date_string.split(/[\/ :]/).map(&:to_i)
78
+ time_zone = TZInfo::Timezone.get(TIME_ZONE_MAPPINGS[@timezone_id])
79
+ time_zone.local_to_utc DateTime.new(year, month, day, hour, minute, second)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module WebEx
2
+ module Events
3
+ VERSION = "1.0.1"
4
+ end
5
+ end
@@ -0,0 +1,200 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>SUCCESS</serv:result>
6
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
7
+ </serv:response>
8
+ </serv:header>
9
+ <serv:body>
10
+ <serv:bodyContent xsi:type="event:lstsummaryEventResponse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
11
+ <event:matchingRecords>
12
+ <serv:total>13</serv:total>
13
+ <serv:returned>13</serv:returned>
14
+ <serv:startFrom>1</serv:startFrom>
15
+ </event:matchingRecords>
16
+ <event:event>
17
+ <event:sessionKey>337167902</event:sessionKey>
18
+ <event:sessionName>Baking Basics</event:sessionName>
19
+ <event:sessionType>220</event:sessionType>
20
+ <event:hostWebExID>bakery</event:hostWebExID>
21
+ <event:startDate>05/01/2013 05:00:00</event:startDate>
22
+ <event:endDate>05/01/2013 06:30:00</event:endDate>
23
+ <event:timeZoneID>4</event:timeZoneID>
24
+ <event:duration>90</event:duration>
25
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
26
+ <event:status>NOT_INPROGRESS</event:status>
27
+ <event:panelists></event:panelists>
28
+ <event:listStatus>UNLISTED</event:listStatus>
29
+ </event:event>
30
+ <event:event>
31
+ <event:sessionKey>241299796</event:sessionKey>
32
+ <event:sessionName>Advanced Baking</event:sessionName>
33
+ <event:sessionType>220</event:sessionType>
34
+ <event:hostWebExID>bakery</event:hostWebExID>
35
+ <event:startDate>05/01/2013 08:00:00</event:startDate>
36
+ <event:endDate>05/01/2013 09:00:00</event:endDate>
37
+ <event:timeZoneID>4</event:timeZoneID>
38
+ <event:duration>60</event:duration>
39
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
40
+ <event:status>NOT_INPROGRESS</event:status>
41
+ <event:panelists>Satoshi Nakamoto</event:panelists>
42
+ <event:listStatus>UNLISTED</event:listStatus>
43
+ </event:event>
44
+ <event:event>
45
+ <event:sessionKey>741475588</event:sessionKey>
46
+ <event:sessionName>Baking Basics</event:sessionName>
47
+ <event:sessionType>220</event:sessionType>
48
+ <event:hostWebExID>bakery</event:hostWebExID>
49
+ <event:startDate>05/01/2013 11:00:00</event:startDate>
50
+ <event:endDate>05/01/2013 12:30:00</event:endDate>
51
+ <event:timeZoneID>4</event:timeZoneID>
52
+ <event:duration>90</event:duration>
53
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
54
+ <event:status>NOT_INPROGRESS</event:status>
55
+ <event:panelists></event:panelists>
56
+ <event:listStatus>UNLISTED</event:listStatus>
57
+ </event:event>
58
+ <event:event>
59
+ <event:sessionKey>785125343</event:sessionKey>
60
+ <event:sessionName>Baking Basics</event:sessionName>
61
+ <event:sessionType>220</event:sessionType>
62
+ <event:hostWebExID>bakery</event:hostWebExID>
63
+ <event:startDate>05/01/2013 13:00:00</event:startDate>
64
+ <event:endDate>05/01/2013 14:30:00</event:endDate>
65
+ <event:timeZoneID>4</event:timeZoneID>
66
+ <event:duration>90</event:duration>
67
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
68
+ <event:status>NOT_INPROGRESS</event:status>
69
+ <event:panelists></event:panelists>
70
+ <event:listStatus>UNLISTED</event:listStatus>
71
+ </event:event>
72
+ <event:event>
73
+ <event:sessionKey>516377344</event:sessionKey>
74
+ <event:sessionName>Baking Basics</event:sessionName>
75
+ <event:sessionType>220</event:sessionType>
76
+ <event:hostWebExID>bakery</event:hostWebExID>
77
+ <event:startDate>05/02/2013 05:00:00</event:startDate>
78
+ <event:endDate>05/02/2013 06:30:00</event:endDate>
79
+ <event:timeZoneID>4</event:timeZoneID>
80
+ <event:duration>90</event:duration>
81
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
82
+ <event:status>NOT_INPROGRESS</event:status>
83
+ <event:panelists></event:panelists>
84
+ <event:listStatus>UNLISTED</event:listStatus>
85
+ </event:event>
86
+ <event:event>
87
+ <event:sessionKey>675735386</event:sessionKey>
88
+ <event:sessionName>Advanced Baking</event:sessionName>
89
+ <event:sessionType>220</event:sessionType>
90
+ <event:hostWebExID>bakery</event:hostWebExID>
91
+ <event:startDate>05/02/2013 08:00:00</event:startDate>
92
+ <event:endDate>05/02/2013 09:00:00</event:endDate>
93
+ <event:timeZoneID>4</event:timeZoneID>
94
+ <event:duration>60</event:duration>
95
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
96
+ <event:status>NOT_INPROGRESS</event:status>
97
+ <event:panelists>Satoshi Nakamoto</event:panelists>
98
+ <event:listStatus>UNLISTED</event:listStatus>
99
+ </event:event>
100
+ <event:event>
101
+ <event:sessionKey>448544768</event:sessionKey>
102
+ <event:sessionName>Baking Basics</event:sessionName>
103
+ <event:sessionType>220</event:sessionType>
104
+ <event:hostWebExID>bakery</event:hostWebExID>
105
+ <event:startDate>05/02/2013 11:00:00</event:startDate>
106
+ <event:endDate>05/02/2013 12:30:00</event:endDate>
107
+ <event:timeZoneID>4</event:timeZoneID>
108
+ <event:duration>90</event:duration>
109
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
110
+ <event:status>NOT_INPROGRESS</event:status>
111
+ <event:panelists></event:panelists>
112
+ <event:listStatus>UNLISTED</event:listStatus>
113
+ </event:event>
114
+ <event:event>
115
+ <event:sessionKey>548465686</event:sessionKey>
116
+ <event:sessionName>Baking Basics</event:sessionName>
117
+ <event:sessionType>220</event:sessionType>
118
+ <event:hostWebExID>bakery</event:hostWebExID>
119
+ <event:startDate>05/02/2013 13:00:00</event:startDate>
120
+ <event:endDate>05/02/2013 14:30:00</event:endDate>
121
+ <event:timeZoneID>4</event:timeZoneID>
122
+ <event:duration>90</event:duration>
123
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
124
+ <event:status>NOT_INPROGRESS</event:status>
125
+ <event:panelists></event:panelists>
126
+ <event:listStatus>UNLISTED</event:listStatus>
127
+ </event:event>
128
+ <event:event>
129
+ <event:sessionKey>265268976</event:sessionKey>
130
+ <event:sessionName>Advanced Baking</event:sessionName>
131
+ <event:sessionType>220</event:sessionType>
132
+ <event:hostWebExID>bakery</event:hostWebExID>
133
+ <event:startDate>05/03/2013 08:00:00</event:startDate>
134
+ <event:endDate>05/03/2013 09:00:00</event:endDate>
135
+ <event:timeZoneID>4</event:timeZoneID>
136
+ <event:duration>60</event:duration>
137
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
138
+ <event:status>NOT_INPROGRESS</event:status>
139
+ <event:panelists>Satoshi Nakamoto</event:panelists>
140
+ <event:listStatus>UNLISTED</event:listStatus>
141
+ </event:event>
142
+ <event:event>
143
+ <event:sessionKey>223574221</event:sessionKey>
144
+ <event:sessionName>Baking Basics</event:sessionName>
145
+ <event:sessionType>220</event:sessionType>
146
+ <event:hostWebExID>bakery</event:hostWebExID>
147
+ <event:startDate>05/07/2013 05:00:00</event:startDate>
148
+ <event:endDate>05/07/2013 06:30:00</event:endDate>
149
+ <event:timeZoneID>4</event:timeZoneID>
150
+ <event:duration>90</event:duration>
151
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
152
+ <event:status>NOT_INPROGRESS</event:status>
153
+ <event:panelists></event:panelists>
154
+ <event:listStatus>UNLISTED</event:listStatus>
155
+ </event:event>
156
+ <event:event>
157
+ <event:sessionKey>495477388</event:sessionKey>
158
+ <event:sessionName>Advanced Baking</event:sessionName>
159
+ <event:sessionType>220</event:sessionType>
160
+ <event:hostWebExID>bakery</event:hostWebExID>
161
+ <event:startDate>05/07/2013 11:00:00</event:startDate>
162
+ <event:endDate>05/07/2013 12:00:00</event:endDate>
163
+ <event:timeZoneID>4</event:timeZoneID>
164
+ <event:duration>60</event:duration>
165
+ <event:description>Learn advanced Japanese baking techniques in this hour-long webinar.</event:description>
166
+ <event:status>NOT_INPROGRESS</event:status>
167
+ <event:panelists></event:panelists>
168
+ <event:listStatus>PUBLIC</event:listStatus>
169
+ </event:event>
170
+ <event:event>
171
+ <event:sessionKey>252653476</event:sessionKey>
172
+ <event:sessionName>Baking Basics</event:sessionName>
173
+ <event:sessionType>220</event:sessionType>
174
+ <event:hostWebExID>bakery</event:hostWebExID>
175
+ <event:startDate>05/07/2013 11:00:00</event:startDate>
176
+ <event:endDate>05/07/2013 12:30:00</event:endDate>
177
+ <event:timeZoneID>4</event:timeZoneID>
178
+ <event:duration>90</event:duration>
179
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
180
+ <event:status>NOT_INPROGRESS</event:status>
181
+ <event:panelists></event:panelists>
182
+ <event:listStatus>UNLISTED</event:listStatus>
183
+ </event:event>
184
+ <event:event>
185
+ <event:sessionKey>356727241</event:sessionKey>
186
+ <event:sessionName>Baking Finale</event:sessionName>
187
+ <event:sessionType>220</event:sessionType>
188
+ <event:hostWebExID>bakery</event:hostWebExID>
189
+ <event:startDate>05/07/2013 13:00:00</event:startDate>
190
+ <event:endDate>05/07/2013 14:30:00</event:endDate>
191
+ <event:timeZoneID>4</event:timeZoneID>
192
+ <event:duration>90</event:duration>
193
+ <event:description>A 90 minute wrap-up event!</event:description>
194
+ <event:status>NOT_INPROGRESS</event:status>
195
+ <event:panelists></event:panelists>
196
+ <event:listStatus>UNLISTED</event:listStatus>
197
+ </event:event>
198
+ </serv:bodyContent>
199
+ </serv:body>
200
+ </serv:message>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>FAILURE</serv:result>
6
+ <serv:reason>Corresponding User not found</serv:reason>
7
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
8
+ <serv:exceptionID>030001</serv:exceptionID>
9
+ </serv:response>
10
+ </serv:header>
11
+ <serv:body>
12
+ <serv:bodyContent/>
13
+ </serv:body>
14
+ </serv:message>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>FAILURE</serv:result>
6
+ <serv:reason>Sorry, no record found</serv:reason>
7
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
8
+ <serv:exceptionID>000015</serv:exceptionID>
9
+ </serv:response>
10
+ </serv:header>
11
+ <serv:body>
12
+ <serv:bodyContent/>
13
+ </serv:body>
14
+ </serv:message>
@@ -0,0 +1,6 @@
1
+ require 'bundler/setup'
2
+ require 'web_ex/events'
3
+ require 'json'
4
+ require 'timecop'
5
+
6
+ include WebEx::Events
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Configuration do
4
+ context "with all environment variables present" do
5
+ before :each do
6
+ ENV.stub(:[]).with("WEBEX_SITE_NAME").and_return("bakeryevents")
7
+ ENV.stub(:[]).with("WEBEX_USERNAME").and_return("bakery")
8
+ ENV.stub(:[]).with("WEBEX_PASSWORD").and_return("secret")
9
+ ENV.stub(:[]).with("WEBEX_SITE_ID").and_return("358562")
10
+ ENV.stub(:[]).with("WEBEX_PARTNER_ID").and_return("123ab")
11
+ end
12
+
13
+ it { Configuration.site_name.should == "bakeryevents" }
14
+ it { Configuration.username.should == "bakery" }
15
+ it { Configuration.password.should == "secret" }
16
+ it { Configuration.site_id.should == "358562" }
17
+ it { Configuration.partner_id.should == "123ab" }
18
+ it { Configuration.xml_service_url.should == "https://bakeryevents.webex.com/WBXService/XMLService" }
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventList do
4
+ let(:event_a) { stub }
5
+ let(:event_b) { stub }
6
+ let(:event_c) { stub }
7
+
8
+ describe "#each" do
9
+ let(:event_list) { EventList.new(event_a, event_b, event_c) }
10
+
11
+ it "iterates over each event" do
12
+ captured_events = []
13
+ event_list.each { |event| captured_events << event }
14
+ captured_events.should == [event_a, event_b, event_c]
15
+ end
16
+ end
17
+
18
+ describe "#to_json" do
19
+ let(:event_list) { EventList.new(event_a, event_b) }
20
+
21
+ before :each do
22
+ event_a.stub(to_json: '{ "event": "a" }')
23
+ event_b.stub(to_json: '{ "event": "b" }')
24
+ end
25
+
26
+ it "returns a JSON representation of all events" do
27
+ JSON.parse(event_list.to_json).should == [{ 'event' => 'a' }, { 'event' => 'b' }]
28
+ end
29
+ end
30
+
31
+ describe "#length" do
32
+ it "returns the number of events" do
33
+ EventList.new(event_a, event_b).length.should == 2
34
+ EventList.new(event_a, event_b, event_c).length.should == 3
35
+ end
36
+ end
37
+
38
+ context "with three events" do
39
+ let(:event_list) { EventList.new(event_a, event_b, event_c) }
40
+
41
+ describe "#first and #last" do
42
+ it { event_list.first.should == event_a }
43
+ it { event_list.last.should == event_c }
44
+ end
45
+ end
46
+
47
+ context "with no events" do
48
+ subject { EventList.new }
49
+ it { should be_empty }
50
+ end
51
+
52
+ context "with at least one event" do
53
+ subject { EventList.new(event_a) }
54
+ it { should_not be_empty }
55
+ end
56
+
57
+ end
@@ -0,0 +1,114 @@
1
+ require "spec_helper"
2
+
3
+ describe Event do
4
+ let(:valid_attributes) {
5
+ {
6
+ name: "Cupcake Giveaway",
7
+ start_date: DateTime.now,
8
+ end_date: DateTime.now + 3600,
9
+ key: "12345",
10
+ description: "Free cupcakes!",
11
+ duration: 60
12
+ }
13
+ }
14
+
15
+ let(:event) { Event.new(valid_attributes) }
16
+
17
+ it "can has accessors for valid attributes" do
18
+ valid_attributes.each do |name, value|
19
+ event.send(name).should == value
20
+ end
21
+ end
22
+
23
+ describe "#to_json" do
24
+ let(:json) { JSON.parse(event.to_json) }
25
+
26
+ it "contains all the valid attributes" do
27
+ json["name"].should == "Cupcake Giveaway"
28
+ json["start_date"].inspect.should == JSON.dump(DateTime.now)
29
+ json["end_date"].inspect.should == JSON.dump(DateTime.now + 3600)
30
+ json["key"].should == "12345"
31
+ json["duration"].should == 60
32
+ json["description"].should == "Free cupcakes!"
33
+ end
34
+ end
35
+
36
+ describe ".from_xml" do
37
+ let(:xml) {
38
+ <<-XML
39
+ <event>
40
+ <sessionKey>662050914</sessionKey>
41
+ <sessionName>NE Utilities - Transition Webinar Navtrak to Fleet</sessionName>
42
+ <sessionType>220</sessionType>
43
+ <hostWebExID>bakery</hostWebExID>
44
+ <startDate>04/30/2013 05:00:00</startDate>
45
+ <endDate>04/30/2013 06:30:00</endDate>
46
+ <timeZoneID>4</timeZoneID>
47
+ <duration>90</duration>
48
+ <description>During this webinar you will be trained on the bakery Fleet application.</description>
49
+ <status>NOT_INPROGRESS</status>
50
+ <panelists></panelists>
51
+ <listStatus>UNLISTED</listStatus>
52
+ </event>
53
+ XML
54
+ }
55
+
56
+ let(:parsed_event) { Event.from_xml(xml) }
57
+
58
+ it "can parse an event" do
59
+ parsed_event.name.should == "NE Utilities - Transition Webinar Navtrak to Fleet"
60
+ parsed_event.key.should == "662050914"
61
+ parsed_event.duration.should == 90
62
+ parsed_event.description.should == "During this webinar you will be trained on the bakery Fleet application."
63
+ end
64
+
65
+ context "with a timezone not in daylight savings" do
66
+ let(:xml) {
67
+ <<-XML
68
+ <event>
69
+ <startDate>02/15/2013 05:00:00</startDate>
70
+ <endDate>02/15/2013 06:30:00</endDate>
71
+ <timeZoneID>4</timeZoneID>
72
+ </event>
73
+ XML
74
+ }
75
+
76
+ it "parses the date correctly" do
77
+ parsed_event.start_date.should == DateTime.new(2013, 2, 15, 5, 0, 0, "-08:00")
78
+ parsed_event.end_date.should == DateTime.new(2013, 2, 15, 6, 30, 0, "-08:00")
79
+ end
80
+ end
81
+
82
+ context "with a timezone inside daylight savings" do
83
+ let(:xml) {
84
+ <<-XML
85
+ <event>
86
+ <startDate>04/30/2013 05:00:00</startDate>
87
+ <endDate>04/30/2013 06:30:00</endDate>
88
+ <timeZoneID>4</timeZoneID>
89
+ </event>
90
+ XML
91
+ }
92
+
93
+ it "corrects for daylight savings" do
94
+ parsed_event.start_date.should == DateTime.new(2013, 4, 30, 5, 0, 0, "-07:00")
95
+ parsed_event.end_date.should == DateTime.new(2013, 4, 30, 6, 30, 0, "-07:00")
96
+ end
97
+ end
98
+ end
99
+
100
+ describe ".fetch" do
101
+ it "creates a new event summary request using the time frame" do
102
+ Timecop.freeze do
103
+ request = stub(find_events!: "events")
104
+ ten_days = 10 * 24 * 60 * 60
105
+
106
+ EventSummaryRequest.should_receive(:new)
107
+ .with(start_date: Time.now, end_date: Time.now + ten_days)
108
+ .and_return(request)
109
+
110
+ Event.fetch(ten_days).should == "events"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require 'fakeweb'
3
+
4
+ describe EventSummaryRequest do
5
+ def fixture_path(name)
6
+ File.expand_path("../../../fixtures/#{name}", __FILE__)
7
+ end
8
+
9
+ context "with a start date of a day ago and end date in a weeks' time" do
10
+ let(:one_second) { 1 }
11
+ let(:one_day_ago) { Time.now - (24 * 60 * 60) }
12
+ let(:seven_days_from_now) { Time.now + (7 * 24 * 60 * 60) }
13
+
14
+ let(:req) { EventSummaryRequest.new(start_date: one_day_ago, end_date: seven_days_from_now) }
15
+
16
+ describe "#start_date" do
17
+ subject { req.start_date }
18
+ it { should be_within(one_second).of(one_day_ago) }
19
+ end
20
+
21
+ describe "#end_date" do
22
+ subject { req.end_date }
23
+ it { should be_within(one_second).of(seven_days_from_now) }
24
+ end
25
+ end
26
+
27
+ describe "#payload" do
28
+ let(:req) { EventSummaryRequest.new }
29
+ let(:xml) { Nokogiri::XML(req.payload) }
30
+
31
+ describe "<?xml ?> processing instruction" do
32
+ subject { xml.at_xpath('/processing-instruction("xml")').content }
33
+ it { should include("UTF-8") }
34
+ end
35
+
36
+ describe "namespaces" do
37
+ let(:namespaces) { xml.collect_namespaces }
38
+ it { namespaces.should have_key('xmlns:xsi') }
39
+ end
40
+
41
+ describe "security context" do
42
+ before :each do
43
+ Configuration.stub(
44
+ site_name: "wafflehut",
45
+ site_id: "12345",
46
+ username: "bobwaffles",
47
+ password: "maplesyrup",
48
+ partner_id: "p4rtn3r"
49
+ )
50
+ end
51
+
52
+ let(:security_context) { xml.at_xpath('/message/header/securityContext') }
53
+ it { security_context.at_xpath('./webExID').content.should == "bobwaffles" }
54
+ it { security_context.at_xpath('./password').content.should == "maplesyrup" }
55
+ it { security_context.at_xpath('./siteID').content.should == "12345" }
56
+ it { security_context.at_xpath('./partnerID').content.should == "p4rtn3r" }
57
+ end
58
+
59
+ describe "body content" do
60
+ let(:body_content) { xml.at_xpath('/message/body/bodyContent') }
61
+ it { body_content['xsi:type'].should include('LstsummaryEvent') }
62
+ it { body_content.at_xpath('./listControl/startFrom').should_not be_nil }
63
+ it { body_content.at_xpath('./order').should_not be_nil }
64
+ it { body_content.at_xpath('./dateScope').should_not be_nil }
65
+ end
66
+
67
+ context "with start and end dates in a time zone" do
68
+ before :each do
69
+ midday_pancake_tuesday = Time.new(2014, 3, 4, 12, 0, 0, "+02:00")
70
+ three_thirty_pm_pancake_tuesday = Time.new(2014, 3, 4, 15, 30, 0, "+02:00")
71
+ req.stub(start_date: midday_pancake_tuesday, end_date: three_thirty_pm_pancake_tuesday)
72
+ end
73
+
74
+ describe "date scope" do
75
+ let(:date_scope) { xml.at_xpath('/message/body/bodyContent/dateScope') }
76
+
77
+ it "formats dates appropriately and in UTC time" do
78
+ date_scope.at_xpath('./startDateStart').content.should == "03/04/2014 10:00:00"
79
+ date_scope.at_xpath('./endDateEnd').content.should == "03/04/2014 13:30:00"
80
+ end
81
+
82
+ it "uses the UTC time zone ID" do
83
+ date_scope.at_xpath('./timeZoneID').content.should == "21"
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#find_events!" do
90
+ let(:req) { EventSummaryRequest.new }
91
+
92
+ before :each do
93
+ Configuration.stub(xml_service_url: "https://webex-api.example.com/service")
94
+ FakeWeb.register_uri(:post, "https://webex-api.example.com/service", body: response_body)
95
+ req.stub(payload: "XML")
96
+ end
97
+
98
+ context "when the server responds with events" do
99
+ let(:response_body) { fixture_path("responses/webex_events.xml") }
100
+
101
+ it "creates a new event using from each event node" do
102
+ Event.should_receive(:from_xml).exactly(13).times
103
+ req.find_events!
104
+ end
105
+
106
+ it "returns the events in an event list" do
107
+ events = req.find_events!
108
+ events.should be_a(EventList)
109
+ events.length.should == 13
110
+ events.first.name.should == "Baking Basics"
111
+ events.last.name.should == "Baking Finale"
112
+ end
113
+ end
114
+
115
+ context "when the server responds without events" do
116
+ let(:response_body) { fixture_path("responses/webex_events_no_results.xml") }
117
+
118
+ it "returns an empty list" do
119
+ events = req.find_events!
120
+ events.should be_a(EventList)
121
+ events.should be_empty
122
+ end
123
+ end
124
+
125
+ context "when the server responds with an authentication error" do
126
+ let(:response_body) { fixture_path("responses/webex_events_auth_error.xml") }
127
+
128
+ it "raises an error with the server's reason for the error" do
129
+ -> {
130
+ req.find_events!
131
+ }.should raise_error { |error|
132
+ error.message.should include('Corresponding User not found')
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'web_ex/events/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "web-ex-events"
8
+ spec.version = WebEx::Events::VERSION
9
+ spec.authors = ["Pete Nicholls"]
10
+ spec.email = ["pete@metanation.com"]
11
+ spec.description = "Allows fetching and parsing WebEx events through the WebEx API. Events can be queried and converted into JSON."
12
+ spec.summary = "A library to read WebEx events through the WebEx API."
13
+ spec.homepage = "https://github.com/Aupajo/web-ex-events"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "timecop"
25
+ spec.add_development_dependency "fakeweb"
26
+
27
+ spec.add_dependency "tzinfo"
28
+ spec.add_dependency "httparty"
29
+ spec.add_dependency "nokogiri"
30
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web-ex-events
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pete Nicholls
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tzinfo
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: httparty
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: nokogiri
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Allows fetching and parsing WebEx events through the WebEx API. Events
126
+ can be queried and converted into JSON.
127
+ email:
128
+ - pete@metanation.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - lib/web_ex/events.rb
139
+ - lib/web_ex/events/configuration.rb
140
+ - lib/web_ex/events/event.rb
141
+ - lib/web_ex/events/event_list.rb
142
+ - lib/web_ex/events/event_summary_request.rb
143
+ - lib/web_ex/events/time_with_zone.rb
144
+ - lib/web_ex/events/version.rb
145
+ - spec/fixtures/responses/webex_events.xml
146
+ - spec/fixtures/responses/webex_events_auth_error.xml
147
+ - spec/fixtures/responses/webex_events_no_results.xml
148
+ - spec/spec_helper.rb
149
+ - spec/web_ex/events/configuration_spec.rb
150
+ - spec/web_ex/events/event_list_spec.rb
151
+ - spec/web_ex/events/event_spec.rb
152
+ - spec/web_ex/events/event_summary_request_spec.rb
153
+ - web-ex-events.gemspec
154
+ homepage: https://github.com/Aupajo/web-ex-events
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.0.3
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: A library to read WebEx events through the WebEx API.
178
+ test_files:
179
+ - spec/fixtures/responses/webex_events.xml
180
+ - spec/fixtures/responses/webex_events_auth_error.xml
181
+ - spec/fixtures/responses/webex_events_no_results.xml
182
+ - spec/spec_helper.rb
183
+ - spec/web_ex/events/configuration_spec.rb
184
+ - spec/web_ex/events/event_list_spec.rb
185
+ - spec/web_ex/events/event_spec.rb
186
+ - spec/web_ex/events/event_summary_request_spec.rb