xteam_schedule 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -13,7 +13,7 @@ You can find a blog post explaining some of the thinking behind its implementati
13
13
  * **Read and write schedules** and interact with in-memory models through the ActiveRecord interface
14
14
  * **Customise everything**; resources, assignments, groups, colours, interface settings..
15
15
  * **Intuitive naming** of models, that correspond to what you see on screen
16
- * **Full test coverage**, giving confidence to highly dynamic businesses everywhere
16
+ * **Full test coverage**, giving confidence to highly dynamic businesses everywhere [![Build Status](https://secure.travis-ci.org/cpatuzzo/xteam_schedule.png?branch=master)](http://travis-ci.org/cpatuzzo/xteam_schedule)
17
17
 
18
18
  ### Disclaimer
19
19
 
@@ -138,6 +138,8 @@ gmail_resource_names = resources.where('email like "%gmail%"').map(&:name)
138
138
  resources.each { |r| r.update_attribute(:displayed_in_planning, true) }
139
139
  ```
140
140
 
141
+ A resource also has remote access attributes, that are explained below.
142
+
141
143
  ## Assignment Groups
142
144
 
143
145
  Assignment groups are almost identical to resource groups. Typical names might be 'Training' and 'Research'.
@@ -334,6 +336,57 @@ finished_holidays = schedule.resources.map(&:holidays).flatten.select { |h|
334
336
  }
335
337
  ```
336
338
 
339
+ ## Remote Access
340
+
341
+ Remote access allows the xTeamView application on Mac OS X and iOS devices to view schedules remotely. A schedule has a remote_access object containing most of the setup, including the 'All' login option for the schedule.
342
+
343
+ ```ruby
344
+ schedule.remote_access.update_attributes!(
345
+ :enabled => true,
346
+ :custom_url => 'http://xteambridge.example.com',
347
+ :custom_enabled => true,
348
+ :global_login => 'username',
349
+ :global_password => 'password',
350
+ :global_login_enabled => true
351
+ )
352
+ ```
353
+
354
+ When remote access is switched on in xTeam, there is a handshake process that takes place which assigns a server_id and name to the schedule. If either of these is missing or they become out of sync, the handshake will fail and you will be notified of an error in xTeam. Therefore, it is recommended that you do not change these attributes. Instead, you should enable remote in xTeam then read these attributes from that file. You can then re-use them as you please.
355
+
356
+ ```ruby
357
+ schedule = XTeamSchedule.new('path/to/file/with/remote/enabled.xtps')
358
+ server_id = schedule.remote_access.server_id
359
+ name = schedule.remote_access.name
360
+
361
+ # An example of re-using the remote configuration for an entirely new schedule
362
+ schedule = XTeamSchedule.new
363
+ schedule.remote_access.update_attributes!(
364
+ :server_id => server_id,
365
+ :name => name
366
+ )
367
+ ```
368
+
369
+ There are three additional attributes on each resource for configuring individual logins. It is worth noting that login details have to be plaintext, unfortunately.
370
+
371
+ ```ruby
372
+ resource_group = schedule.resource_groups.create(:name => 'foo')
373
+ resource = resource_group.resources.create!(
374
+ :name => 'bar',
375
+ :remote_login => 'foo',
376
+ :remote_password => 'bar',
377
+ :remote_login_enabled => true
378
+ )
379
+ ```
380
+
381
+ **Example queries:**
382
+
383
+ ```ruby
384
+ global_login_details = [schedule.remote_access.global_login, schedule.remote_access.global_password]
385
+ resources_without_logins = schedule.resources.reject { |r| r.remote_login }
386
+ usernames = schedule.resources.map(&:remote_login).compact
387
+ raise 'There are duplicated logins' if usernames.count > usernames.uniq.count
388
+ ```
389
+
337
390
  ## Under Development
338
391
 
339
392
  This gem is far from complete. The following is a list of features that are under development:
@@ -341,7 +394,6 @@ This gem is far from complete. The following is a list of features that are unde
341
394
  * Resource images
342
395
  * Sort by
343
396
  * Absences
344
- * Remote access
345
397
  * To assign
346
398
  * Advanced colour controls
347
399
  * Schedule splicing between dates
@@ -49,6 +49,9 @@ class XTeamSchedule::Base < ActiveRecord::Base
49
49
  table.column :mobile, :string
50
50
  table.column :name, :string
51
51
  table.column :phone, :string
52
+ table.column :remote_login, :string
53
+ table.column :remote_password, :string
54
+ table.column :remote_login_enabled, :boolean
52
55
  end
53
56
 
54
57
  create_table :assignment_groups, :force => true do |table|
@@ -78,6 +81,18 @@ class XTeamSchedule::Base < ActiveRecord::Base
78
81
  table.column :end_date, :date
79
82
  table.column :name, :string
80
83
  end
84
+
85
+ create_table :remote_accesses, :force => true do |table|
86
+ table.column :schedule_id, :integer
87
+ table.column :server_id, :integer
88
+ table.column :enabled, :boolean
89
+ table.column :name, :string
90
+ table.column :custom_url, :string
91
+ table.column :custom_enabled, :boolean
92
+ table.column :global_login, :string
93
+ table.column :global_password, :string
94
+ table.column :global_login_enabled, :boolean
95
+ end
81
96
  end
82
97
  end
83
98
  end
@@ -9,7 +9,7 @@ class XTeamSchedule::Composer
9
9
  def initialize(schedule)
10
10
  schedule.save!
11
11
  self.schedule = schedule
12
- self.hash = {}
12
+ self.hash = HashWithoutNilValues.new
13
13
  end
14
14
 
15
15
  def compose
@@ -21,6 +21,7 @@ class XTeamSchedule::Composer
21
21
  compose_interface!
22
22
  compose_weekly_working_schedule!
23
23
  compose_holidays!
24
+ compose_remote_access!
24
25
  compose_schedule!
25
26
  hash
26
27
  end
@@ -31,10 +32,10 @@ private
31
32
  hash['resource groups'] ||= []
32
33
  resource_groups = schedule.resource_groups
33
34
  resource_groups.each do |rg|
34
- hash['resource groups'] << {
35
- 'name' => rg.name,
36
- 'expanded in library' => rg.expanded_in_library
37
- }
35
+ hash['resource groups'] << {}
36
+ current = hash['resource groups'].last
37
+ current['name'] = rg.name
38
+ current['expanded in library'] = rg.expanded_in_library
38
39
  end
39
40
  end
40
41
 
@@ -43,15 +44,15 @@ private
43
44
  resources = schedule.resource_groups.map(&:resources).flatten
44
45
  resources.each do |r|
45
46
  image = Base64.decode64(r.image) if r.image
46
- hash['resources'] << {
47
- 'displayedInPlanning' => r.displayed_in_planning,
48
- 'email' => r.email,
49
- 'image' => (StringIO.new(image) if image),
50
- 'mobile' => r.mobile,
51
- 'name' => r.name,
52
- 'phone' => r.phone,
53
- 'group' => r.resource_group.name
54
- }
47
+ hash['resources'] << {}
48
+ current = hash['resources'].last
49
+ current['displayedInPlanning'] = r.displayed_in_planning
50
+ current['email'] = r.email
51
+ current['image'] = (StringIO.new(image) if image)
52
+ current['mobile'] = r.mobile
53
+ current['name'] = r.name
54
+ current['phone'] = r.phone
55
+ current['group'] = r.resource_group.name
55
56
  end
56
57
  end
57
58
 
@@ -59,10 +60,10 @@ private
59
60
  hash['task categories'] ||= []
60
61
  assignment_groups = schedule.assignment_groups
61
62
  assignment_groups.each do |ag|
62
- hash['task categories'] << {
63
- 'name' => ag.name,
64
- 'expanded in library' => ag.expanded_in_library
65
- }
63
+ hash['task categories'] << {}
64
+ current = hash['task categories'].last
65
+ current['name'] = ag.name
66
+ current['expanded in library'] = ag.expanded_in_library
66
67
  end
67
68
  end
68
69
 
@@ -70,12 +71,12 @@ private
70
71
  hash['tasks'] ||= []
71
72
  assignments = schedule.assignment_groups.map(&:assignments).flatten
72
73
  assignments.each do |a|
73
- hash['tasks'] << {
74
- 'name' => a.name,
75
- 'category' => a.assignment_group.name,
76
- 'kind' => 0,
77
- 'color' => compose_colour(a.colour)
78
- }
74
+ hash['tasks'] << {}
75
+ current = hash['tasks'].last
76
+ current['name'] = a.name
77
+ current['category'] = a.assignment_group.name
78
+ current['kind'] = 0
79
+ current['color'] = compose_colour(a.colour)
79
80
  end
80
81
  end
81
82
 
@@ -85,15 +86,15 @@ private
85
86
  resources.each do |r|
86
87
  working_times_with_parents = r.working_times.select { |wt| wt.resource and wt.assignment }
87
88
  next unless working_times_with_parents.any?
88
- hash['objectsForResources'].merge!(r.name => [])
89
+ hash['objectsForResources'][r.name] = []
89
90
  working_times_with_parents.each do |wt|
90
- hash['objectsForResources'][r.name] << {
91
- 'task' => wt.assignment.name,
92
- 'begin date' => compose_date(wt.begin_date),
93
- 'duration' => wt.duration,
94
- 'notes' => wt.notes,
95
- 'title' => ''
96
- }
91
+ hash['objectsForResources'][r.name] << {}
92
+ current = hash['objectsForResources'][r.name].last
93
+ current['task'] = wt.assignment.name
94
+ current['begin date'] = compose_date(wt.begin_date)
95
+ current['duration'] = wt.duration
96
+ current['notes'] = wt.notes
97
+ current['title'] = ''
97
98
  end
98
99
  end
99
100
  end
@@ -107,7 +108,8 @@ private
107
108
  hash['display resource totals'] = interface.display_total_of_working_hours
108
109
  hash['display task notes'] = interface.display_assignments_notes
109
110
  hash['display absence cells'] = interface.display_absences
110
- hash['interface status'] = { 'latest time navigation mode' => interface.time_granularity }
111
+ hash['interface status'] ||= {}
112
+ hash['interface status']['latest time navigation mode'] = interface.time_granularity
111
113
  end
112
114
 
113
115
  def compose_weekly_working_schedule!
@@ -120,23 +122,23 @@ private
120
122
 
121
123
  working_days.each do |day|
122
124
  day_name = day.name.downcase
123
- hash['settings']['working schedule'][day_name] =
125
+ hash['settings']['working schedule'][day_name] = {}
126
+ current = hash['settings']['working schedule'][day_name]
124
127
  if day.day_begin.present?
125
- { 'worked' => 'yes',
126
- 'begin' => compose_time(day.day_begin),
127
- 'end' => compose_time(day.day_end) }
128
+ current['worked'] = 'yes'
129
+ current['begin'] = compose_time(day.day_begin)
130
+ current['end'] = compose_time(day.day_end)
128
131
  else
129
- { 'worked' => 'no' }
132
+ current['worked'] = 'no'
130
133
  end
131
134
 
132
- hash['settings']['working schedule']["pause_#{day_name}"] =
135
+ hash['settings']['working schedule']["pause_#{day_name}"] = {}
136
+ current = hash['settings']['working schedule']["pause_#{day_name}"]
133
137
  if day.day_begin.present? and day.break_begin.present?
134
- { 'worked' => 'yes',
135
- 'begin' => compose_time(day.break_begin),
136
- 'end' => compose_time(day.break_end),
137
- 'duration' => compose_time(day.break_end) - compose_time(day.break_begin) }
138
- else
139
- {}
138
+ current['worked'] = 'yes'
139
+ current['begin'] = compose_time(day.break_begin)
140
+ current['end'] = compose_time(day.break_end)
141
+ current['duration'] = compose_time(day.break_end) - compose_time(day.break_begin)
140
142
  end
141
143
  end
142
144
  end
@@ -154,11 +156,11 @@ private
154
156
 
155
157
  holidays.each do |h|
156
158
  h.end_date ||= h.begin_date
157
- hash['settings']['days off'] << {
158
- 'begin date' => compose_date(h.begin_date),
159
- 'end date' => compose_date(h.end_date),
160
- 'name' => h.name
161
- }
159
+ hash['settings']['days off'] << {}
160
+ current = hash['settings']['days off'].last
161
+ current['begin date'] = compose_date(h.begin_date)
162
+ current['end date'] = compose_date(h.end_date)
163
+ current['name'] = h.name
162
164
  end
163
165
  end
164
166
 
@@ -174,15 +176,51 @@ private
174
176
  hash['resources'][index]['settings']['use custom days off'] = 1
175
177
  r.holidays.each do |h|
176
178
  h.end_date ||= h.begin_date
177
- hash['resources'][index]['settings']['days off'] << {
178
- 'begin date' => compose_date(h.begin_date),
179
- 'end date' => compose_date(h.end_date),
180
- 'name' => h.name
181
- }
179
+ hash['resources'][index]['settings']['days off'] << {}
180
+ current = hash['resources'][index]['settings']['days off'].last
181
+ current['begin date'] = compose_date(h.begin_date)
182
+ current['end date'] = compose_date(h.end_date)
183
+ current['name'] = h.name
182
184
  end
183
185
  end
184
186
  end
185
187
 
188
+ def compose_remote_access!
189
+ remote_access = schedule.remote_access
190
+ hash['settings'] ||= {}
191
+
192
+ valid_setup = remote_access.server_id && remote_access.name
193
+ enable = valid_setup && remote_access.enabled
194
+
195
+ hash['settings']['remoteId'] = remote_access.server_id if valid_setup
196
+ hash['settings']['remoteEnable'] = enable
197
+ hash['settings']['remoteName'] = remote_access.name if valid_setup
198
+ hash['settings']['remoteCustomServerURL'] = remote_access.custom_url
199
+ hash['settings']['remoteUseCustomServer'] = remote_access.custom_enabled
200
+
201
+ if remote_access.global_login or remote_access.global_password
202
+ hash['settings']['remoteLoginInfo'] ||= {}
203
+ hash['settings']['remoteLoginInfo']['All'] ||= {}
204
+ hash['settings']['remoteLoginInfo']['All']['login'] = remote_access.global_login
205
+ hash['settings']['remoteLoginInfo']['All']['password'] = remote_access.global_password
206
+ hash['settings']['remoteLoginInfo']['All']['enable'] = remote_access.global_login_enabled
207
+ end
208
+
209
+ compose_remote_access_for_resources!
210
+ end
211
+
212
+ def compose_remote_access_for_resources!
213
+ hash['settings'] ||= {}
214
+ hash['settings']['remoteLoginInfo'] ||= {}
215
+
216
+ schedule.resources.each do |r|
217
+ hash['settings']['remoteLoginInfo'][r.name] = {}
218
+ hash['settings']['remoteLoginInfo'][r.name]['login'] = r.remote_login
219
+ hash['settings']['remoteLoginInfo'][r.name]['password'] = r.remote_password
220
+ hash['settings']['remoteLoginInfo'][r.name]['enable'] = r.remote_login_enabled
221
+ end
222
+ end
223
+
186
224
  def compose_schedule!
187
225
  hash['begin date'] = compose_date(schedule.begin_date)
188
226
  hash['end date'] = compose_date(schedule.end_date)
@@ -20,6 +20,7 @@ class XTeamSchedule::Parser
20
20
  parse_interface!
21
21
  parse_weekly_working_schedule!
22
22
  parse_holidays!
23
+ parse_remote_access!
23
24
  parse_schedule!
24
25
  schedule
25
26
  end
@@ -189,6 +190,40 @@ private
189
190
  end
190
191
  end
191
192
 
193
+ def parse_remote_access!
194
+ settings = hash['settings']
195
+ return unless settings.present?
196
+ remote_logins = settings['remoteLoginInfo']
197
+ return unless remote_logins.present?
198
+ global_login = remote_logins['All']
199
+ global_login ||= {}
200
+ schedule.remote_access.update_attributes!(
201
+ :server_id => settings['remoteId'],
202
+ :enabled => settings['remoteEnable'],
203
+ :name => settings['remoteName'],
204
+ :custom_url => settings['remoteCustomServerURL'],
205
+ :custom_enabled => settings['remoteUseCustomServer'],
206
+ :global_login => global_login['login'],
207
+ :global_password => global_login['password'],
208
+ :global_login_enabled => global_login['enable']
209
+ )
210
+ parse_remote_access_for_resources!
211
+ end
212
+
213
+ def parse_remote_access_for_resources!
214
+ settings = hash['settings']
215
+ remote_logins = settings['remoteLoginInfo']
216
+ remote_logins.each do |name, hash|
217
+ resource = schedule.resources.find_by_name(name)
218
+ next unless resource
219
+ resource.update_attributes!(
220
+ :remote_login => hash['login'],
221
+ :remote_password => hash['password'],
222
+ :remote_login_enabled => hash['enable']
223
+ )
224
+ end
225
+ end
226
+
192
227
  def parse_schedule!
193
228
  schedule.update_attributes!(
194
229
  :begin_date => parse_date(hash['begin date']),
@@ -0,0 +1,21 @@
1
+ # Fixes dynamic find by method attempting to use the connection
2
+ # on ActiveRecord::Base in activerecord versions 3.1.0 to 3.2.2
3
+ # when it should be respecting the connection on its subclass
4
+ class QuoteTableNamePatch
5
+ def self.quote_table_name(name)
6
+ name
7
+ end
8
+ end
9
+
10
+ module ActiveRecord
11
+ module Associations
12
+ class AliasTracker
13
+ private
14
+ def connection
15
+ ActiveRecord::Base.connection
16
+ rescue ActiveRecord::ConnectionNotEstablished
17
+ QuoteTableNamePatch
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # hash = HashWithoutNilValues.new
2
+ # hash[:a] = nil
3
+ # hash # => {}
4
+ #
5
+ # hash[:a] = {}
6
+ # hash[:a].class # => HashWithoutNilValues
7
+ #
8
+ # hash[:a] = []
9
+ # hash[:a].class # => ArrayWithoutNilValues
10
+ #
11
+ # hash[:a] << nil
12
+ # hash[:a] # => []
13
+ #
14
+ # hash[:a] << {}
15
+ # hash[:a].first.class # => HashWithoutNilValues
16
+ #
17
+ class HashWithoutNilValues < Hash
18
+ def []=(key, value)
19
+ return if value.nil?
20
+ value = HashWithoutNilValues.new if value == {}
21
+ value = ArrayWithoutNilValues.new if value == []
22
+ super(key, value)
23
+ end
24
+ end
25
+
26
+ class ArrayWithoutNilValues < Array
27
+ def <<(value)
28
+ return if value.nil?
29
+ value = ArrayWithoutNilValues.new if value == []
30
+ value = HashWithoutNilValues.new if value == {}
31
+ super(value)
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ class XTeamSchedule::RemoteAccess < XTeamSchedule::Base
2
+ belongs_to :schedule
3
+
4
+ validate :format_of_name
5
+ NAME_REGEX = /XTEAM-\d{8}-\d{4}/.freeze
6
+
7
+ private
8
+
9
+ def format_of_name
10
+ return unless name
11
+ raise 'invalid' unless name =~ NAME_REGEX
12
+ xteam, date, time = name.split('-')
13
+ day, month, year = date[0..1], date[2..3], date[4..7]
14
+ hour, minute = time[0..1], time[2..3]
15
+ DateTime.new(year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i)
16
+ rescue
17
+ errors.add(:name, 'is not in the form XTEAM-DDMMYYYY-HHMM')
18
+ end
19
+
20
+ end
@@ -8,13 +8,16 @@ class XTeamSchedule::Schedule < XTeamSchedule::Base
8
8
 
9
9
  has_one :interface
10
10
  has_one :weekly_working_schedule
11
+ has_one :remote_access
11
12
 
12
13
  ActiveSupport::Deprecation.silence do
13
14
  after_initialize :set_default_interface
14
15
  after_initialize :set_default_weekly_working_schedule
16
+ after_initialize :set_default_remote_access
15
17
  def after_initialize
16
18
  set_default_interface
17
19
  set_default_weekly_working_schedule
20
+ set_default_remote_access
18
21
  end
19
22
  end
20
23
 
@@ -33,4 +36,8 @@ private
33
36
  self.weekly_working_schedule ||= XTeamSchedule::WeeklyWorkingSchedule.new
34
37
  end
35
38
 
39
+ def set_default_remote_access
40
+ self.remote_access ||= XTeamSchedule::RemoteAccess.new
41
+ end
42
+
36
43
  end
@@ -1,5 +1,12 @@
1
+ begin
2
+ require 'yaml'
3
+ YAML::ENGINE.yamler = 'syck'
4
+ rescue
5
+ end
6
+
1
7
  require 'plist'
2
8
  require 'active_record'
9
+ require 'active_support/multibyte'
3
10
  require 'sqlite3'
4
11
  require 'xteam_schedule/core'
5
12
 
@@ -9,10 +16,13 @@ require 'xteam_schedule/facilitation/parser'
9
16
  require 'xteam_schedule/facilitation/composer'
10
17
  require 'xteam_schedule/facilitation/io'
11
18
  require 'xteam_schedule/facilitation/lmc_patch'
19
+ require 'xteam_schedule/facilitation/qtn_patch'
20
+ require 'xteam_schedule/facilitation/without_nil'
12
21
 
13
22
  require 'xteam_schedule/models/assignment'
14
23
  require 'xteam_schedule/models/assignment_group'
15
24
  require 'xteam_schedule/models/interface'
25
+ require 'xteam_schedule/models/remote_access'
16
26
  require 'xteam_schedule/models/resource'
17
27
  require 'xteam_schedule/models/resource_group'
18
28
  require 'xteam_schedule/models/schedule'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xteam_schedule
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Christopher Patuzzo
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-04-14 00:00:00 Z
18
+ date: 2012-04-15 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: plist
@@ -107,11 +107,14 @@ files:
107
107
  - lib/xteam_schedule/facilitation/io.rb
108
108
  - lib/xteam_schedule/facilitation/lmc_patch.rb
109
109
  - lib/xteam_schedule/facilitation/parser.rb
110
+ - lib/xteam_schedule/facilitation/qtn_patch.rb
110
111
  - lib/xteam_schedule/facilitation/schema.rb
112
+ - lib/xteam_schedule/facilitation/without_nil.rb
111
113
  - lib/xteam_schedule/models/assignment.rb
112
114
  - lib/xteam_schedule/models/assignment_group.rb
113
115
  - lib/xteam_schedule/models/holiday.rb
114
116
  - lib/xteam_schedule/models/interface.rb
117
+ - lib/xteam_schedule/models/remote_access.rb
115
118
  - lib/xteam_schedule/models/resource.rb
116
119
  - lib/xteam_schedule/models/resource_group.rb
117
120
  - lib/xteam_schedule/models/schedule.rb
@@ -148,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
151
  requirements: []
149
152
 
150
153
  rubyforge_project:
151
- rubygems_version: 1.8.15
154
+ rubygems_version: 1.8.22
152
155
  signing_key:
153
156
  specification_version: 3
154
157
  summary: XTeam Schedule