mavenlink 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/README.md +241 -0
  7. data/Rakefile +28 -0
  8. data/bin/mavenlink-console +18 -0
  9. data/doc/ml_logo_lb-primary.png +0 -0
  10. data/lib/config/specification.yml +1665 -0
  11. data/lib/mavenlink.rb +134 -0
  12. data/lib/mavenlink/account_invitation.rb +4 -0
  13. data/lib/mavenlink/account_membership.rb +4 -0
  14. data/lib/mavenlink/additional_item.rb +4 -0
  15. data/lib/mavenlink/assignment.rb +5 -0
  16. data/lib/mavenlink/attachment.rb +11 -0
  17. data/lib/mavenlink/backup_approver_association.rb +4 -0
  18. data/lib/mavenlink/client.rb +116 -0
  19. data/lib/mavenlink/concerns/custom_fieldable.rb +9 -0
  20. data/lib/mavenlink/concerns/indestructible.rb +11 -0
  21. data/lib/mavenlink/concerns/locked_record.rb +30 -0
  22. data/lib/mavenlink/cost_rate.rb +4 -0
  23. data/lib/mavenlink/custom_field.rb +4 -0
  24. data/lib/mavenlink/custom_field_choice.rb +5 -0
  25. data/lib/mavenlink/custom_field_value.rb +4 -0
  26. data/lib/mavenlink/errors.rb +47 -0
  27. data/lib/mavenlink/expense.rb +4 -0
  28. data/lib/mavenlink/expense_category.rb +4 -0
  29. data/lib/mavenlink/expense_report_submission.rb +7 -0
  30. data/lib/mavenlink/external_payment.rb +4 -0
  31. data/lib/mavenlink/external_reference.rb +24 -0
  32. data/lib/mavenlink/fixed_fee_item.rb +4 -0
  33. data/lib/mavenlink/holiday.rb +4 -0
  34. data/lib/mavenlink/holiday_calendar.rb +4 -0
  35. data/lib/mavenlink/holiday_calendar_association.rb +4 -0
  36. data/lib/mavenlink/holiday_calendar_membership.rb +4 -0
  37. data/lib/mavenlink/invoice.rb +4 -0
  38. data/lib/mavenlink/logger.rb +62 -0
  39. data/lib/mavenlink/model.rb +279 -0
  40. data/lib/mavenlink/organization.rb +4 -0
  41. data/lib/mavenlink/organization_membership.rb +4 -0
  42. data/lib/mavenlink/participation.rb +4 -0
  43. data/lib/mavenlink/post.rb +4 -0
  44. data/lib/mavenlink/project_template.rb +4 -0
  45. data/lib/mavenlink/project_template_assignment.rb +4 -0
  46. data/lib/mavenlink/railtie.rb +7 -0
  47. data/lib/mavenlink/rate_card.rb +4 -0
  48. data/lib/mavenlink/rate_card_role.rb +4 -0
  49. data/lib/mavenlink/rate_card_set.rb +4 -0
  50. data/lib/mavenlink/rate_card_set_version.rb +30 -0
  51. data/lib/mavenlink/rate_card_version.rb +4 -0
  52. data/lib/mavenlink/request.rb +241 -0
  53. data/lib/mavenlink/resolution.rb +4 -0
  54. data/lib/mavenlink/response.rb +22 -0
  55. data/lib/mavenlink/role.rb +5 -0
  56. data/lib/mavenlink/settings.rb +11 -0
  57. data/lib/mavenlink/skill.rb +4 -0
  58. data/lib/mavenlink/skill_category.rb +4 -0
  59. data/lib/mavenlink/skill_membership.rb +4 -0
  60. data/lib/mavenlink/specificators/association.rb +13 -0
  61. data/lib/mavenlink/specificators/attribute.rb +13 -0
  62. data/lib/mavenlink/specificators/base.rb +24 -0
  63. data/lib/mavenlink/specificators/validation.rb +27 -0
  64. data/lib/mavenlink/status_report.rb +4 -0
  65. data/lib/mavenlink/story.rb +8 -0
  66. data/lib/mavenlink/story_allocation_day.rb +5 -0
  67. data/lib/mavenlink/story_dependency.rb +5 -0
  68. data/lib/mavenlink/story_task.rb +5 -0
  69. data/lib/mavenlink/tag.rb +5 -0
  70. data/lib/mavenlink/time_adjustment.rb +4 -0
  71. data/lib/mavenlink/time_entry.rb +4 -0
  72. data/lib/mavenlink/time_off_entry.rb +4 -0
  73. data/lib/mavenlink/timesheet_submission.rb +4 -0
  74. data/lib/mavenlink/user.rb +5 -0
  75. data/lib/mavenlink/vendor.rb +4 -0
  76. data/lib/mavenlink/workspace.rb +21 -0
  77. data/lib/mavenlink/workspace_group.rb +5 -0
  78. data/lib/mavenlink/workspace_invoice_preference.rb +4 -0
  79. data/lib/mavenlink/workweek.rb +4 -0
  80. data/lib/mavenlink/workweek_membership.rb +4 -0
  81. data/mavenlink.gemspec +29 -0
  82. data/spec/lib/mavenlink/account_membership_spec.rb +8 -0
  83. data/spec/lib/mavenlink/assignment_spec.rb +17 -0
  84. data/spec/lib/mavenlink/attachment_spec.rb +30 -0
  85. data/spec/lib/mavenlink/backup_approver_association_spec.rb +9 -0
  86. data/spec/lib/mavenlink/client_spec.rb +187 -0
  87. data/spec/lib/mavenlink/concerns/indestructible_spec.rb +13 -0
  88. data/spec/lib/mavenlink/concerns/locked_record_spec.rb +28 -0
  89. data/spec/lib/mavenlink/cost_rate_spec.rb +9 -0
  90. data/spec/lib/mavenlink/custom_field_value_spec.rb +10 -0
  91. data/spec/lib/mavenlink/expense_report_submission_spec.rb +16 -0
  92. data/spec/lib/mavenlink/expense_spec.rb +23 -0
  93. data/spec/lib/mavenlink/external_references_spec.rb +144 -0
  94. data/spec/lib/mavenlink/holiday_calendar_association_spec.rb +8 -0
  95. data/spec/lib/mavenlink/holiday_calendar_membership_spec.rb +8 -0
  96. data/spec/lib/mavenlink/holiday_spec.rb +7 -0
  97. data/spec/lib/mavenlink/invalid_request_error_spec.rb +9 -0
  98. data/spec/lib/mavenlink/invoice_spec.rb +176 -0
  99. data/spec/lib/mavenlink/model_spec.rb +439 -0
  100. data/spec/lib/mavenlink/post_spec.rb +23 -0
  101. data/spec/lib/mavenlink/rate_card_set_version_spec.rb +119 -0
  102. data/spec/lib/mavenlink/record_invalid_error_spec.rb +16 -0
  103. data/spec/lib/mavenlink/record_not_found_error_spec.rb +9 -0
  104. data/spec/lib/mavenlink/request_spec.rb +381 -0
  105. data/spec/lib/mavenlink/response_spec.rb +50 -0
  106. data/spec/lib/mavenlink/role_spec.rb +9 -0
  107. data/spec/lib/mavenlink/settings_spec.rb +23 -0
  108. data/spec/lib/mavenlink/skill_category_spec.rb +7 -0
  109. data/spec/lib/mavenlink/skill_membership_spec.rb +9 -0
  110. data/spec/lib/mavenlink/skill_spec.rb +8 -0
  111. data/spec/lib/mavenlink/specificators/association_spec.rb +25 -0
  112. data/spec/lib/mavenlink/specificators/attribute_spec.rb +25 -0
  113. data/spec/lib/mavenlink/specificators/validation_spec.rb +39 -0
  114. data/spec/lib/mavenlink/story_allocation_day_spec.rb +64 -0
  115. data/spec/lib/mavenlink/story_dependency_spec.rb +16 -0
  116. data/spec/lib/mavenlink/story_spec.rb +69 -0
  117. data/spec/lib/mavenlink/time_adjustment_spec.rb +13 -0
  118. data/spec/lib/mavenlink/time_entry_spec.rb +43 -0
  119. data/spec/lib/mavenlink/time_off_entry_spec.rb +9 -0
  120. data/spec/lib/mavenlink/user_spec.rb +138 -0
  121. data/spec/lib/mavenlink/workspace_group_spec.rb +25 -0
  122. data/spec/lib/mavenlink/workspace_spec.rb +431 -0
  123. data/spec/lib/mavenlink_spec.rb +43 -0
  124. data/spec/spec_helper.rb +31 -0
  125. data/spec/support/shared_examples.rb +148 -0
  126. metadata +267 -0
@@ -0,0 +1,7 @@
1
+ module Mavenlink
2
+ class Railtie < Rails::Railtie
3
+ initializer 'mavenlink.initialize' do
4
+ Mavenlink.oauth_token = ENV['MAVENLINK_OAUTH_TOKEN']
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class RateCard < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class RateCardRole < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class RateCardSet < Model
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ module Mavenlink
2
+ class RateCardSetVersion < Model
3
+ def publish
4
+ if persisted?
5
+ client.put("rate_card_set_versions/#@id/publish")
6
+ true
7
+ else
8
+ false
9
+ end
10
+ rescue Faraday::Error
11
+ false
12
+ end
13
+
14
+ def clone_version(effective_date = nil)
15
+ return false unless persisted?
16
+
17
+ request.perform do
18
+ client.post(collection_name, {
19
+ clone_id: self.id,
20
+ rate_card_set_version: {
21
+ rate_card_set_id: self.rate_card_set_id,
22
+ effective_date: effective_date
23
+ }
24
+ })
25
+ end.results.first
26
+ rescue Mavenlink::Error
27
+ false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class RateCardVersion < Model
3
+ end
4
+ end
@@ -0,0 +1,241 @@
1
+ module Mavenlink
2
+ class Request
3
+ include Enumerable
4
+
5
+ attr_reader :client, :collection_name
6
+ attr_accessor :scope
7
+
8
+ # @param collection_name [String, Symbol]
9
+ # @param client [Mavenlink::Client]
10
+ def initialize(collection_name, client = Mavenlink.client)
11
+ @collection_name = collection_name
12
+ @client = client
13
+ @scope = ActiveSupport::HashWithIndifferentAccess.new
14
+ end
15
+
16
+ # Returns new chained request
17
+ # @param new_scope [Hash]
18
+ # @return [Mavenlink::Request]
19
+ def chain(new_scope = {})
20
+ self.class.new(collection_name, client).tap do |new_request|
21
+ new_request.scope.merge!(scope)
22
+ new_request.scope.merge!(new_scope)
23
+ end
24
+ end
25
+
26
+ # @param ids [String, Array]
27
+ # @return [Mavenlink::Request]
28
+ def only(*ids)
29
+ if ids.try(:flatten).present?
30
+ chain(only: param_to_request_array(ids))
31
+ else
32
+ without(:only)
33
+ end
34
+ end
35
+
36
+ # @param id [Integer, String]
37
+ # @return [BrainstemAdaptor::Record, nil]
38
+ def find(id)
39
+ only(id).perform.results.first
40
+ end
41
+
42
+ # @param text [String]
43
+ # @return [Mavenlink::Request]
44
+ def search(text)
45
+ chain(search: text)
46
+ end
47
+
48
+ # @param options [Hash]
49
+ def filter(options)
50
+ chain(options)
51
+ end
52
+
53
+ # @param associations [String, Array]
54
+ # @return [Mavenlink::Request]
55
+ def includes(*associations)
56
+ chain(include: param_to_request_array(associations))
57
+ end
58
+ alias_method :include, :includes
59
+
60
+ # @param [Integer, String]
61
+ # @return [Mavenlink::Request]
62
+ def page(number)
63
+ chain(page: number)
64
+ end
65
+
66
+ def current_page
67
+ @scope[:page].try(:to_i) || 1
68
+ end
69
+
70
+ # @return [Integer]
71
+ def total_count
72
+ response.total_count
73
+ end
74
+
75
+ # Kaminari support
76
+ # @return [Integer]
77
+ def total_pages
78
+ (total_count / (@scope[:per_page].try(:to_f) || 200.0)).ceil
79
+ end
80
+
81
+ # Kaminari support
82
+ # @return [Integer]
83
+ def limit_value
84
+ @scope[:limit].try(:to_i) || total_count
85
+ end
86
+
87
+ # @param [Integer, String]
88
+ # @return [Mavenlink::Request]
89
+ def per_page(number)
90
+ chain(per_page: number)
91
+ end
92
+
93
+ # @param [Integer, String]
94
+ # @return [Mavenlink::Request]
95
+ def limit(number)
96
+ chain(limit: number)
97
+ end
98
+
99
+ # @param [Integer, String]
100
+ # @return [Mavenlink::Request]
101
+ def offset(number)
102
+ chain(offset: number)
103
+ end
104
+
105
+ # Removes condition from the scope
106
+ # @param key [String, Symbol]
107
+ # @return [Mavenlink::Request] new request
108
+ def without(key)
109
+ chain.tap { |new_scope| new_scope.scope.delete(key) }
110
+ end
111
+
112
+ # @param field [String, Symbol]
113
+ # @param direction_or_desc [String, nil, true, false]
114
+ # @example
115
+ # workspaces.order(:created_at)
116
+ # workspaces.order(:created_at, :desc)
117
+ # workspaces.order(:created_at, :asc)
118
+ # workspaces.order(:created_at, 'DESC')
119
+ # workspaces.order('created_at:desc')
120
+ # @return [Mavenlink::Request]
121
+ def order(field, direction_or_desc = nil)
122
+ case direction_or_desc
123
+ when :desc, 'DESC', 'desc', true
124
+ field = "#{field}:desc"
125
+ when :asc, nil, 'ASC', 'asc', false
126
+ else
127
+ raise ArgumentError, "Invalid request ordering set '#{direction_or_desc}'"
128
+ end
129
+
130
+ chain(order: field.to_s)
131
+ end
132
+
133
+ # @param attributes [Hash]
134
+ # @return [Mavenlink::Model]
135
+ def build(attributes)
136
+ "Mavenlink::#{collection_name.classify}".constantize.new(attributes, nil, client)
137
+ end
138
+
139
+ # @param attributes [Hash]
140
+ # @return [Mavenlink::Response]
141
+ def create(attributes)
142
+ perform { client.post(collection_name, {collection_name.singularize => attributes}) }
143
+ end
144
+
145
+ # @param attributes [Hash]
146
+ # @return [Mavenlink::Response]
147
+ def update(attributes)
148
+ perform { client.put(resource_path, {collection_name.singularize => attributes}) }
149
+ end
150
+
151
+ # @note Weird non-json response?
152
+ # @return [nil]
153
+ def delete
154
+ client.delete(resource_path)
155
+ end
156
+
157
+ # @return [Mavenlink::Response]
158
+ def perform
159
+ response = block_given? ? yield : client.get(collection_name, scope)
160
+ Mavenlink::Response.new(response, client)
161
+ end
162
+
163
+ # Returns cached response
164
+ # @return [Mavenlink::Response]
165
+ def response
166
+ @response ||= perform
167
+ end
168
+
169
+ def results
170
+ @results ||= response.results
171
+ end
172
+ alias_method :all, :results
173
+
174
+ def reload
175
+ @results = nil
176
+ results
177
+ end
178
+
179
+ def each(&block)
180
+ results.each(&block)
181
+ end
182
+
183
+ # Iterates through each page, similar to ActiveRecord's #find_in_batches.
184
+ # @todo replace with lazy enumerator for ruby 2.0
185
+ # @param batch_size [Integer]
186
+ # @return [Enumerator<Array<Mavenlink::Model>>]
187
+ def each_page(batch_size = 200, &block)
188
+ Enumerator.new do |result|
189
+ i = 0
190
+ records_passed = 0
191
+ request = per_page(batch_size)
192
+ page_records = []
193
+ total_count = Float::INFINITY
194
+
195
+ while (records_passed += page_records.count) < total_count
196
+ response = request.page(i+=1).perform
197
+ total_count = response.total_count
198
+ result << page_records = response.results
199
+ end
200
+ end.each(&block)
201
+ end
202
+
203
+ def scoped
204
+ self
205
+ end
206
+
207
+ def to_hash
208
+ scope
209
+ end
210
+
211
+ # @see https://github.com/rails/rails/blob/682d7c7035fed76c42ba6fefa38973387e80409e/activerecord/lib/active_record/relation.rb#L572
212
+ # @return [String]
213
+ def inspect
214
+ entries = to_a.map { |e| "<#{e.class.name}:#{e['id']}>" }
215
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
216
+ end
217
+
218
+ # Returns "show" path for the resource
219
+ # @raise [ArgumentError] when ID is not included in criteria
220
+ # @return [String]
221
+ def resource_path
222
+ id = @scope[:only] or raise ArgumentError, 'No route matches source path without an ID'
223
+ "#{collection_name}/#{id}"
224
+ end
225
+
226
+ private
227
+
228
+ # Builds comma-separated query param
229
+ # @param param [Array, String]
230
+ def param_to_request_array(param)
231
+ case param
232
+ when Array
233
+ param.join(',').gsub(/\s+/, '')
234
+ when String, Symbol
235
+ param.to_s.gsub(/\s+/, '')
236
+ else
237
+ raise ArgumentError, "Expected Array, got #{param.class.name}"
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class Resolution < Model
3
+ end
4
+ end
@@ -0,0 +1,22 @@
1
+ module Mavenlink
2
+ class Response < BrainstemAdaptor::Response
3
+
4
+ attr_reader :client
5
+
6
+ # @param response_data [String, Hash]
7
+ # @param specification [BrainstemAdaptor::Specification]
8
+ # @param client [Mavenlink::Client]
9
+ def initialize(response_data, client = Mavenlink.client, specification = Mavenlink.specification)
10
+ @client = client
11
+ super(response_data, specification)
12
+ end
13
+
14
+ # Returns collection records
15
+ # Wraps brainstem records into mavenlink models
16
+ # @override
17
+ # @return [Array<MavenLink::Model>]
18
+ def results
19
+ super.map { |record| Mavenlink::Model.models[record.collection_name].wrap(record, client) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ module Mavenlink
2
+ class Role < Model
3
+ validates :name, presence: true
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Mavenlink
2
+ class Settings < ActiveSupport::HashWithIndifferentAccess
3
+ DEFAULTS = {perform_validations: false}.freeze
4
+
5
+ def self.[](key)
6
+ (@instances ||= {})[key] ||= self.new.tap do |defaults|
7
+ defaults.merge!(DEFAULTS)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class Skill < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class SkillCategory < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mavenlink
2
+ class SkillMembership < Model
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module Mavenlink
2
+ module Specificators
3
+ class Association < Base
4
+
5
+ # Defines attributes in model described in specification
6
+ def apply
7
+ (model_class.specification['associations'] || {}).keys.each do |association_name|
8
+ model_class.association association_name
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Mavenlink
2
+ module Specificators
3
+ class Attribute < Base
4
+
5
+ # Defines attributes in model described in specification
6
+ def apply
7
+ (model_class.available_attributes).each do |attr|
8
+ model_class.attribute attr
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Mavenlink
2
+ module Specificators
3
+
4
+ # Specificator is used to inject anything described in specification file into your model.
5
+ class Base
6
+ attr_reader :model_class
7
+
8
+ # @param model_class [Class]
9
+ def initialize(model_class)
10
+ @model_class = model_class
11
+ end
12
+
13
+ # Injects things described in specification file into the model
14
+ def apply
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # @param model_class [Class]
19
+ def self.apply(model_class)
20
+ self.new(model_class).apply
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Mavenlink
2
+ module Specificators
3
+ class Validation < Base
4
+
5
+ # Defines attributes in model described in specification
6
+ def apply
7
+ (model_class.specification['validations'] || {}).each do |fields, options|
8
+ model_class.validates(*fields, to_validation_options(options))
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ # Returns hash for validation options
15
+ # @param options [Hash]
16
+ # @return [Hash]
17
+ def to_validation_options(options)
18
+ options.tap do
19
+ if options.is_a?(Hash)
20
+ options.symbolize_keys!
21
+ options.keys.each { |key| to_validation_options(options[key]) }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end