pivotal-tracker-api 0.2.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +8 -2
- data/Gemfile.lock +65 -47
- data/README.md +7 -7
- data/VERSION +1 -1
- data/lib/pivotal-tracker-api.rb +10 -2
- data/lib/pivotal-tracker-api/activity.rb +52 -18
- data/lib/pivotal-tracker-api/analytics.rb +23 -0
- data/lib/pivotal-tracker-api/base.rb +14 -2
- data/lib/pivotal-tracker-api/client.rb +43 -21
- data/lib/pivotal-tracker-api/comment.rb +76 -27
- data/lib/pivotal-tracker-api/core_ext/string.rb +3 -0
- data/lib/pivotal-tracker-api/cycle_time_details.rb +43 -0
- data/lib/pivotal-tracker-api/file_attachment.rb +56 -0
- data/lib/pivotal-tracker-api/iteration.rb +73 -11
- data/lib/pivotal-tracker-api/label.rb +32 -0
- data/lib/pivotal-tracker-api/me.rb +16 -0
- data/lib/pivotal-tracker-api/person.rb +13 -9
- data/lib/pivotal-tracker-api/project.rb +76 -23
- data/lib/pivotal-tracker-api/service.rb +202 -0
- data/lib/pivotal-tracker-api/story.rb +173 -132
- data/lib/pivotal-tracker-api/story_transition.rb +81 -0
- data/lib/pivotal-tracker-api/string_extensions.rb +61 -0
- data/lib/pivotal-tracker-api/task.rb +56 -12
- data/pivotal-tracker-api.gemspec +28 -15
- data/test/helper.rb +1 -0
- data/test/test_activity.rb +79 -0
- data/test/test_analytics.rb +38 -0
- data/test/test_cycle_time_details.rb +69 -0
- data/test/test_iteration.rb +182 -0
- data/test/test_label.rb +29 -0
- data/test/test_me.rb +52 -0
- data/test/test_service.rb +67 -0
- data/test/test_story.rb +557 -0
- data/test/test_story_transition.rb +80 -0
- data/test/test_string_extensions.rb +37 -0
- metadata +29 -27
- data/lib/pivotal-tracker-api/attachment.rb +0 -28
- data/lib/pivotal-tracker-api/pivotal_service.rb +0 -141
- data/test/test_pivotal-tracker-api.rb +0 -7
@@ -1,37 +1,86 @@
|
|
1
|
-
|
1
|
+
# PROPERTIES
|
2
|
+
# id int
|
3
|
+
# — Database id of the comment. This field is read only. This field is always returned.
|
4
|
+
#
|
5
|
+
# story_id int
|
6
|
+
# — The id of the story to which the comment is attached (will be absent if comment attached to an epic. This field is read only.
|
7
|
+
#
|
8
|
+
# epic_id int
|
9
|
+
# — The id of the epic to which the comment is attached (will be absent if comment attached to a story. This field is read only.
|
10
|
+
#
|
11
|
+
# text string[20000]
|
12
|
+
# — Content of the comment. This field is writable only on create.
|
13
|
+
#
|
14
|
+
# person_id int
|
15
|
+
# — The id of the comment creator. This field is writable only on create. In API responses, this attribute may be person_id or person.
|
16
|
+
#
|
17
|
+
# created_at datetime
|
18
|
+
# — Creation time. This field is read only.
|
19
|
+
#
|
20
|
+
# updated_at datetime
|
21
|
+
# — Updated time. (Comments are updated by removing one of their attachments.) This field is read only.
|
22
|
+
#
|
23
|
+
# file_attachment_ids List[int]
|
24
|
+
# — IDs of any file attachments associated with the comment. This field is writable only on create. This field is excluded by default. In API responses, this attribute may be file_attachment_ids or file_attachments.
|
25
|
+
#
|
26
|
+
# google_attachment_ids List[int]
|
27
|
+
# — IDs of any google attachments associated with the comment. This field is writable only on create. This field is excluded by default. In API responses, this attribute may be google_attachment_ids or google_attachments.
|
28
|
+
#
|
29
|
+
# commit_identifier string[255]
|
30
|
+
# — Commit Id on the remote source control system for the comment. Present only on comments that were created by a POST to the source commits API endpoint. This field is writable only on create. (Note that this attribute does not indicate an association to another resource.)
|
31
|
+
#
|
32
|
+
# commit_type string[255]
|
33
|
+
# — String identifying the type of remote source control system if Pivotal Tracker can determine it. Present only on comments that were created by a POST to the source commits API endpoint. This field is writable only on create.
|
34
|
+
#
|
35
|
+
# kind string
|
36
|
+
# — The type of this object: comment. This field is read only.
|
37
|
+
|
38
|
+
module PivotalAPI
|
2
39
|
class Comment < Base
|
3
40
|
|
4
|
-
attr_accessor :project_id, :story_id, :
|
41
|
+
attr_accessor :project_id, :story_id, :epic_id, :id, :text, :person,
|
42
|
+
:created_at, :updated_at, :file_attachments,
|
43
|
+
:google_attachment_ids, :commit_identifier, :commit_type,
|
44
|
+
:kind
|
5
45
|
|
6
46
|
def self.fields
|
7
|
-
["person(#{
|
8
|
-
'created_at', 'story_id', 'file_attachments'
|
47
|
+
["person(#{Person.fields.join(',')})", 'text', 'updated_at', 'id',
|
48
|
+
'created_at', 'story_id', 'file_attachments', 'google_attachment_ids',
|
49
|
+
'commit_identifier', 'commit_type', 'kind']
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_json(json)
|
53
|
+
parse_json_comment(json)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parse_json_comment(comment)
|
57
|
+
person = Person.from_json(comment[:person]) if comment[:person]
|
58
|
+
person = Person.unkown if person.nil?
|
59
|
+
new({
|
60
|
+
id: comment[:id].to_i,
|
61
|
+
text: comment[:text],
|
62
|
+
person: person,
|
63
|
+
created_at: DateTime.parse(comment[:created_at].to_s),
|
64
|
+
updated_at: DateTime.parse(comment[:updated_at].to_s),
|
65
|
+
file_attachments: FileAttachments.from_json(comment[:file_attachments]),
|
66
|
+
commit_identifier: comment[:commit_identifier],
|
67
|
+
google_attachment_ids: comment[:google_attachment_ids],
|
68
|
+
commit_type: comment[:commit_type],
|
69
|
+
kind: comment[:kind]
|
70
|
+
})
|
9
71
|
end
|
10
72
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
}) if person.nil?
|
22
|
-
file_attachments = []
|
23
|
-
file_attachments = Scorer::Attachment.parse_attachments(comment[:file_attachments]) if comment[:file_attachments]
|
24
|
-
comments << new({
|
25
|
-
id: comment[:id].to_i,
|
26
|
-
text: comment[:text],
|
27
|
-
author: person,
|
28
|
-
created_at: DateTime.parse(comment[:created_at].to_s).to_s,
|
29
|
-
updated_at: DateTime.parse(comment[:updated_at].to_s).to_s,
|
30
|
-
file_attachments: file_attachments
|
31
|
-
})
|
32
|
-
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Comments < Comment
|
76
|
+
def self.from_json(json)
|
77
|
+
parse_json_comments(json) if json.is_a?(Array)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.parse_json_comments(json_comments)
|
81
|
+
comments = []
|
82
|
+
json_comments.each { |comment| comments << parse_json_comment(comment) }
|
33
83
|
comments
|
34
84
|
end
|
35
|
-
|
36
85
|
end
|
37
86
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# PROPERTIES
|
2
|
+
# total_cycle_time int
|
3
|
+
# — The total amount of time, in milliseconds, between when the story was first started to when it was last accepted. In the case where a story has not been accepted, it is the time between when the story was first started to the current time. If the story has not been started, this property is not returned. This field is read only.
|
4
|
+
#
|
5
|
+
# started_time int
|
6
|
+
# — The total amount of time, in milliseconds, that the story was in the started state. This field is read only.
|
7
|
+
#
|
8
|
+
# started_count int
|
9
|
+
# — The number of times that the story has been in the started state. This field is read only.
|
10
|
+
#
|
11
|
+
# finished_time int
|
12
|
+
# — The total amount of time, in milliseconds, that the story was in the finished state. This field is read only.
|
13
|
+
#
|
14
|
+
# finished_count int
|
15
|
+
# — The number of times that the story has been in the finished state. This field is read only.
|
16
|
+
#
|
17
|
+
# delivered_time int
|
18
|
+
# — The total amount of time, in milliseconds, that the story was in the delivered state. This field is read only.
|
19
|
+
#
|
20
|
+
# delivered_count int
|
21
|
+
# — The number of times that the story has been in the delivered state. This field is read only.
|
22
|
+
#
|
23
|
+
# rejected_time int
|
24
|
+
# — The total amount of time, in milliseconds, that the story was in the rejected state. This field is read only.
|
25
|
+
#
|
26
|
+
# rejected_count int
|
27
|
+
# — The number of times that the story has been in the rejected state. This field is read only.
|
28
|
+
#
|
29
|
+
# story_id int
|
30
|
+
# — The id of the associated story. This field is read only.
|
31
|
+
#
|
32
|
+
# kind string
|
33
|
+
# — The type of this object: cycle_time_details. This field is read only.
|
34
|
+
|
35
|
+
module PivotalAPI
|
36
|
+
class CycleTimeDetails < Base
|
37
|
+
|
38
|
+
attr_accessor :total_cycle_time, :started_time, :started_count, :finished_time,
|
39
|
+
:finished_count, :delivered_time, :delivered_count, :rejected_time,
|
40
|
+
:rejected_count, :story_id, :kind
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# PROPERTIES
|
2
|
+
# id int
|
3
|
+
# — Database id of the file_attachment. This field is read only. This field is always returned.
|
4
|
+
#
|
5
|
+
# filename string[255]
|
6
|
+
# — The file's name. This field is read only.
|
7
|
+
#
|
8
|
+
# created_at datetime
|
9
|
+
# — Creation time. This field is read only.
|
10
|
+
#
|
11
|
+
# uploader_id int
|
12
|
+
# — The id of the person who uploaded the file. This field is read only. In API responses, this attribute may be uploader_id or uploader.
|
13
|
+
#
|
14
|
+
# thumbnailable boolean
|
15
|
+
# — Flag indicating whether Tracker knows how to make a thumbnail image from the attachment. This field is read only.
|
16
|
+
#
|
17
|
+
# height int
|
18
|
+
# — If the attachment is thumbnailable the height of it in pixels. This field is read only.
|
19
|
+
#
|
20
|
+
# width int
|
21
|
+
# — If the attachment is thumbnailable the width of it in pixels. This field is read only.
|
22
|
+
#
|
23
|
+
# size int
|
24
|
+
# — The size of the attachment in bytes. This field is read only.
|
25
|
+
#
|
26
|
+
# download_url string
|
27
|
+
# — The URL for the original attachment on S3. This field is read only.
|
28
|
+
#
|
29
|
+
# content_type string[255]
|
30
|
+
# — The MIME type of the attachment. This field is read only.
|
31
|
+
#
|
32
|
+
# uploaded boolean
|
33
|
+
# — Flag indicating whether the attachment has been moved to S3. This field is read only.
|
34
|
+
#
|
35
|
+
# big_url string
|
36
|
+
# — URL to larger-size thumbnail version if attachment is an image. This field is read only.
|
37
|
+
#
|
38
|
+
# thumbnail_url string
|
39
|
+
# — URL to small-size thumbnail version if attachment is an image. This field is read only.
|
40
|
+
#
|
41
|
+
# kind string
|
42
|
+
# — The type of this object: file_attachment. This field is read only.
|
43
|
+
|
44
|
+
module PivotalAPI
|
45
|
+
class FileAttachment < Base
|
46
|
+
|
47
|
+
attr_accessor :filename, :id, :created_at, :uploaded_by, :big_url,
|
48
|
+
:width, :height, :download_url, :thumbnail_url, :size,
|
49
|
+
:content_type, :kind, :uploader_id, :thumbnailable,
|
50
|
+
:uploaded
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class FileAttachments < FileAttachment
|
55
|
+
end
|
56
|
+
end
|
@@ -1,12 +1,67 @@
|
|
1
|
-
|
1
|
+
# PROPERTIES
|
2
|
+
# number int
|
3
|
+
# — Iteration number starting from 1 for the first iteration in the project. This field is read only. This field is always returned.
|
4
|
+
#
|
5
|
+
# project_id int
|
6
|
+
# — id of the project. This field is read only.
|
7
|
+
#
|
8
|
+
# length int
|
9
|
+
# — Iteration length in weeks.
|
10
|
+
#
|
11
|
+
# team_strength float
|
12
|
+
# — Iteration team strength, 1.0 is full-strength.
|
13
|
+
#
|
14
|
+
# story_ids List[int]
|
15
|
+
# — Array of stories contained in the iteration. This field is read only. By default this will be included in responses as an array of nested structures, using the key stories. In API responses, this attribute may be story_ids or stories.
|
16
|
+
#
|
17
|
+
# start datetime
|
18
|
+
# — Iteration start time. This field is read only.
|
19
|
+
#
|
20
|
+
# finish datetime
|
21
|
+
# — Iteration finish time. This field is read only.
|
22
|
+
#
|
23
|
+
# velocity float
|
24
|
+
# — The averaged number of points completed over a number of previous iterations, as determined by the project's velocity_averaged_over attribute. This field is read only. This field is excluded by default.
|
25
|
+
#
|
26
|
+
# points int
|
27
|
+
# — The number of points in the iteration. This field is read only. This field is excluded by default.
|
28
|
+
#
|
29
|
+
# effective_points float
|
30
|
+
# — The number of points in the iteration normalized by iteration length and team strength. This field is read only. This field is excluded by default.
|
31
|
+
#
|
32
|
+
# accepted daily_history_container
|
33
|
+
# — The daily summary of stories accepted in this iteration. This field is read only. This field is excluded by default.
|
34
|
+
#
|
35
|
+
# created daily_history_container
|
36
|
+
# — The number of stories created in this iteration by type. This field is read only. This field is excluded by default.
|
37
|
+
#
|
38
|
+
# analytics analytics
|
39
|
+
# — Analytics data for this iteration. This field is read only. This field is excluded by default.
|
40
|
+
#
|
41
|
+
# kind string
|
42
|
+
# — The type of this object: iteration. This field is read only.
|
43
|
+
|
44
|
+
module PivotalAPI
|
2
45
|
class Iteration < Base
|
3
46
|
|
4
|
-
attr_accessor :project_id, :length, :
|
47
|
+
attr_accessor :project_id, :length, :stories, :story_ids, :number, :team_strength,
|
48
|
+
:finish, :kind, :start, :velocity, :points, :effective_points, :analytics
|
49
|
+
|
50
|
+
def self.fields
|
51
|
+
['velocity', 'points', 'effective_points', 'analytics', "stories(#{Story.fields.join(',')})", 'team_strength',
|
52
|
+
'project_id', 'length', 'start', 'finish']
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.from_json(json)
|
56
|
+
parse_json_iteration(json)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
5
60
|
|
6
|
-
def self.parse_json_iteration(json_iteration
|
61
|
+
def self.parse_json_iteration(json_iteration)
|
7
62
|
new({
|
8
63
|
project_id: json_iteration[:project_id].to_i,
|
9
|
-
stories:
|
64
|
+
stories: Stories.from_json(json_iteration[:stories]),
|
10
65
|
story_ids: json_iteration[:story_ids],
|
11
66
|
number: json_iteration[:number],
|
12
67
|
team_strength: json_iteration[:team_strength],
|
@@ -16,14 +71,21 @@ module Scorer
|
|
16
71
|
})
|
17
72
|
end
|
18
73
|
|
74
|
+
end
|
75
|
+
|
76
|
+
class Iterations < Iteration
|
77
|
+
|
78
|
+
def self.from_json(json)
|
79
|
+
parse_json_iterations(json)
|
80
|
+
end
|
81
|
+
|
19
82
|
protected
|
20
|
-
|
21
|
-
def self.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
PivotalService.stories(project, true, story_ids, Scorer::Story.fields, include_done)
|
83
|
+
|
84
|
+
def self.parse_json_iterations(json_iterations)
|
85
|
+
iterations = []
|
86
|
+
json_iterations.each { |iteration| iterations << parse_json_iteration(iteration) }
|
87
|
+
iterations
|
26
88
|
end
|
27
|
-
|
89
|
+
|
28
90
|
end
|
29
91
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# PROPERTIES
|
2
|
+
# id int
|
3
|
+
# — Database id of the label. This field is read only. This field is always returned.
|
4
|
+
#
|
5
|
+
# project_id int
|
6
|
+
# — id of the project. This field is read only.
|
7
|
+
#
|
8
|
+
# name string[255]
|
9
|
+
# Required — The label's name.
|
10
|
+
#
|
11
|
+
# created_at datetime
|
12
|
+
# — Creation time. This field is read only.
|
13
|
+
#
|
14
|
+
# updated_at datetime
|
15
|
+
# — Time of last update. This field is read only.
|
16
|
+
#
|
17
|
+
# counts story_counts
|
18
|
+
# — Summary of numbers of stories and points contained. This field is read only. This field is excluded by default.
|
19
|
+
#
|
20
|
+
# kind string
|
21
|
+
# — The type of this object: label. This field is read only.
|
22
|
+
|
23
|
+
module PivotalAPI
|
24
|
+
class Label < Base
|
25
|
+
|
26
|
+
attr_accessor :id, :project_id, :name, :created_at, :updated_at, :counts, :kind
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class Labels < Label
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module PivotalAPI
|
2
|
+
class Me < Person
|
3
|
+
|
4
|
+
attr_accessor :api_token, :updated_at, :created_at, :has_google_identity,
|
5
|
+
:projects, :receives_in_app_notifications, :time_zone
|
6
|
+
|
7
|
+
def self.retrieve(username, password)
|
8
|
+
Service.me(username, password)
|
9
|
+
end
|
10
|
+
|
11
|
+
def projects
|
12
|
+
Projects.retrieve()
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -1,21 +1,25 @@
|
|
1
|
-
module
|
1
|
+
module PivotalAPI
|
2
2
|
class Person < Base
|
3
3
|
|
4
|
-
attr_accessor :name, :id, :initials, :email, :username
|
4
|
+
attr_accessor :name, :id, :initials, :email, :username, :kind
|
5
5
|
|
6
6
|
def self.fields
|
7
|
-
['name', 'id', 'initials', 'email', 'username']
|
7
|
+
['name', 'id', 'initials', 'email', 'username', 'kind']
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.
|
10
|
+
def self.unknown
|
11
11
|
new({
|
12
|
-
id:
|
13
|
-
name:
|
14
|
-
initials:
|
15
|
-
|
16
|
-
|
12
|
+
id: '0',
|
13
|
+
name: 'Unkown',
|
14
|
+
initials: 'Unkown',
|
15
|
+
username: 'Unkown',
|
16
|
+
email: 'Unkown',
|
17
|
+
kind: 'person'
|
17
18
|
})
|
18
19
|
end
|
19
20
|
|
20
21
|
end
|
22
|
+
|
23
|
+
class People < Person
|
24
|
+
end
|
21
25
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module PivotalAPI
|
2
2
|
class Project < Base
|
3
3
|
|
4
4
|
attr_accessor :updated_at, :bugs_and_chores_are_estimatable, :enable_planned_mode, :public, :story_ids, :name,
|
@@ -11,16 +11,18 @@ module Scorer
|
|
11
11
|
class << self
|
12
12
|
|
13
13
|
def fields
|
14
|
-
['description', 'labels', 'name']
|
14
|
+
['description', 'labels', 'name', 'current_velocity', 'velocity_averaged_over']
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
json_projects.each do |project|
|
20
|
-
projects << parse_json_project(project)
|
21
|
-
end
|
22
|
-
projects
|
17
|
+
def from_json(json)
|
18
|
+
parse_json_project(json)
|
23
19
|
end
|
20
|
+
|
21
|
+
def retrieve(project_id)
|
22
|
+
Service.project(project_id: project_id, fields: Project.fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
24
26
|
|
25
27
|
def parse_json_project(project)
|
26
28
|
new({
|
@@ -28,28 +30,79 @@ module Scorer
|
|
28
30
|
name: project[:name],
|
29
31
|
week_start_day: project[:week_start_day],
|
30
32
|
point_scale: project[:point_scale],
|
31
|
-
labels:
|
33
|
+
labels: PivotalAPI::Label.from_json(project[:labels]),
|
32
34
|
iteration_length: project[:iteration_length],
|
33
35
|
current_velocity: project[:current_velocity]
|
34
36
|
})
|
35
37
|
end
|
36
|
-
|
37
|
-
def parse_labels(labels)
|
38
|
-
parsed_labels = ''
|
39
|
-
labels.each do |label|
|
40
|
-
parsed_labels = parsed_labels + "#{label[:name]},"
|
41
|
-
end
|
42
|
-
parsed_labels
|
43
|
-
end
|
38
|
+
|
44
39
|
end
|
45
|
-
|
46
|
-
def
|
47
|
-
|
40
|
+
|
41
|
+
def activity(opts={})
|
42
|
+
opts[:project_id] = id
|
43
|
+
|
44
|
+
Service.activity(opts)
|
48
45
|
end
|
49
|
-
|
50
|
-
def
|
51
|
-
|
46
|
+
|
47
|
+
def story(opts={})
|
48
|
+
opts[:project_id] = id
|
49
|
+
opts[:parameters] = {} unless opts[:parameters]
|
50
|
+
opts[:parameters][:fields] = Story.fields
|
51
|
+
|
52
|
+
Service.story(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
def stories(opts={})
|
56
|
+
opts[:project_id] = id
|
57
|
+
opts[:parameters] = {} unless opts[:parameters]
|
58
|
+
opts[:parameters][:fields] = Story.fields
|
59
|
+
|
60
|
+
Service.stories(opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
def iterations(opts={})
|
64
|
+
opts[:project_id] = id
|
65
|
+
opts[:parameters] = {} unless opts[:parameters]
|
66
|
+
opts[:fields] = Iteration.fields if opts[:fields].nil?
|
67
|
+
|
68
|
+
Service.iterations(opts)
|
69
|
+
end
|
70
|
+
|
71
|
+
def current_iteration
|
72
|
+
iterations.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def previous_iteration
|
76
|
+
iterations(scope: 'done').first
|
77
|
+
end
|
78
|
+
|
79
|
+
def next_iteration
|
80
|
+
iterations(scope: 'backlog').first
|
52
81
|
end
|
53
82
|
|
54
83
|
end
|
84
|
+
|
85
|
+
class Projects < Project
|
86
|
+
|
87
|
+
class << self
|
88
|
+
|
89
|
+
def retrieve()
|
90
|
+
Service.projects(fields: Project.fields)
|
91
|
+
end
|
92
|
+
|
93
|
+
def from_json(json)
|
94
|
+
parse_json_projects(json)
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def parse_json_projects(json_projects)
|
100
|
+
projects = Array.new
|
101
|
+
json_projects.each { |project| projects << parse_json_project(project) }
|
102
|
+
projects
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
55
108
|
end
|