bloomy 0.10.0 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +7 -4
- data/lib/bloomy/client.rb +10 -8
- data/lib/bloomy/operations/goals.rb +140 -123
- data/lib/bloomy/operations/headlines.rb +122 -104
- data/lib/bloomy/operations/issues.rb +83 -99
- data/lib/bloomy/operations/meetings.rb +156 -154
- data/lib/bloomy/operations/scorecard.rb +91 -93
- data/lib/bloomy/operations/todos.rb +123 -128
- data/lib/bloomy/operations/users.rb +100 -85
- data/lib/bloomy/types/items.rb +119 -0
- data/lib/bloomy/version.rb +1 -1
- metadata +3 -2
@@ -3,112 +3,96 @@
|
|
3
3
|
require "json"
|
4
4
|
require "bloomy/utils/get_user_id"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
def initialize(conn)
|
13
|
-
@conn = conn
|
14
|
-
end
|
15
|
-
|
16
|
-
# Retrieves details of a specific issue
|
17
|
-
#
|
18
|
-
# @param issue_id [Integer] the ID of the issue
|
19
|
-
# @return [Hash] a hash containing issue details
|
20
|
-
# @example
|
21
|
-
# issue.details(123)
|
22
|
-
# #=> { id: 123, title: "Issue Title", notes_url: "http://details.url", ... }
|
23
|
-
def details(issue_id)
|
24
|
-
response = @conn.get("issues/#{issue_id}").body
|
25
|
-
{
|
26
|
-
id: response["Id"],
|
27
|
-
title: response["Name"],
|
28
|
-
notes_url: response["DetailsUrl"],
|
29
|
-
created_at: response["CreateTime"],
|
30
|
-
completed_at: response["CloseTime"],
|
31
|
-
meeting_details: {
|
32
|
-
id: response["OriginId"],
|
33
|
-
title: response["Origin"]
|
34
|
-
},
|
35
|
-
owner_details: {
|
36
|
-
id: response["Owner"]["Id"],
|
37
|
-
name: response["Owner"]["Name"]
|
38
|
-
}
|
39
|
-
}
|
40
|
-
end
|
6
|
+
module Bloomy
|
7
|
+
# Handles CRUD operations for issues in the system.
|
8
|
+
# Provides functionality to create, retrieve, list, and solve issues
|
9
|
+
# associated with meetings and users.
|
10
|
+
class Issue
|
11
|
+
include Bloomy::Utilities::UserIdUtility
|
41
12
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# @example
|
49
|
-
# # Fetch issues for the current user
|
50
|
-
# issue.list
|
51
|
-
#
|
52
|
-
# # Fetch issues for a specific user
|
53
|
-
# issue.list(user_id: 42)
|
54
|
-
#
|
55
|
-
# # Fetch issues for a specific meeting
|
56
|
-
# issue.list(meeting_id: 99)
|
57
|
-
def list(user_id: nil, meeting_id: nil)
|
58
|
-
if user_id && meeting_id
|
59
|
-
raise ArgumentError, "Please provide either `user_id` or `meeting_id`, not both."
|
13
|
+
# Initializes a new Issue instance
|
14
|
+
#
|
15
|
+
# @param conn [Faraday::Connection] Connection object for making API requests
|
16
|
+
# @return [Issue] New instance of Issue
|
17
|
+
def initialize(conn)
|
18
|
+
@conn = conn
|
60
19
|
end
|
61
20
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
21
|
+
# Retrieves detailed information about a specific issue
|
22
|
+
#
|
23
|
+
# @param issue_id [Integer] Unique identifier of the issue
|
24
|
+
# @return [Types::IssueDetails] Detailed information about the issue
|
25
|
+
# @raise [ApiError] When the API request fails or returns invalid data
|
26
|
+
def details(issue_id)
|
27
|
+
response = @conn.get("issues/#{issue_id}").body
|
28
|
+
Types::IssueItem.new(
|
29
|
+
id: response["Id"],
|
30
|
+
title: response["Name"],
|
31
|
+
notes_url: response["DetailsUrl"],
|
32
|
+
created_at: response["CreateTime"],
|
33
|
+
completed_at: response["CloseTime"],
|
34
|
+
meeting_id: response["OriginId"],
|
35
|
+
meeting_title: response["Origin"],
|
36
|
+
user_id: response["Owner"]["Id"],
|
37
|
+
user_name: response["Owner"]["Name"]
|
38
|
+
)
|
67
39
|
end
|
68
40
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
41
|
+
# Lists issues filtered by user or meeting
|
42
|
+
#
|
43
|
+
# @param user_id [Integer, nil] Unique identifier of the user (optional)
|
44
|
+
# @param meeting_id [Integer, nil] Unique identifier of the meeting (optional)
|
45
|
+
# @return [Array<Types::IssueItem>] List of issues matching the filter criteria
|
46
|
+
# @raise [ArgumentError] When both user_id and meeting_id are provided
|
47
|
+
# @raise [ApiError] When the API request fails or returns invalid data
|
48
|
+
def list(user_id: nil, meeting_id: nil)
|
49
|
+
if user_id && meeting_id
|
50
|
+
raise ArgumentError, "Please provide either `user_id` or `meeting_id`, not both."
|
51
|
+
end
|
52
|
+
|
53
|
+
response = meeting_id ? @conn.get("l10/#{meeting_id}/issues").body : @conn.get("issues/users/#{user_id || self.user_id}").body
|
54
|
+
|
55
|
+
response.map do |issue|
|
56
|
+
Types::IssueItem.new(
|
57
|
+
id: issue["Id"],
|
58
|
+
title: issue["Name"],
|
59
|
+
notes_url: issue["DetailsUrl"],
|
60
|
+
created_at: issue["CreateTime"],
|
61
|
+
meeting_id: issue["OriginId"],
|
62
|
+
meeting_title: issue["Origin"]
|
63
|
+
)
|
64
|
+
end
|
78
65
|
end
|
79
|
-
end
|
80
66
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
response.success?
|
91
|
-
end
|
67
|
+
# Marks an issue as completed/solved
|
68
|
+
#
|
69
|
+
# @param issue_id [Integer] Unique identifier of the issue to be solved
|
70
|
+
# @return [Boolean] true if issue was successfully solved, false otherwise
|
71
|
+
# @raise [ApiError] When the API request fails
|
72
|
+
def solve(issue_id)
|
73
|
+
response = @conn.post("issues/#{issue_id}/complete", {complete: true}.to_json)
|
74
|
+
response.success?
|
75
|
+
end
|
92
76
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
77
|
+
# Creates a new issue in the system
|
78
|
+
#
|
79
|
+
# @param meeting_id [Integer] Unique identifier of the associated meeting
|
80
|
+
# @param title [String] Title/name of the issue
|
81
|
+
# @param user_id [Integer] Unique identifier of the issue owner (defaults to current user)
|
82
|
+
# @param notes [String, nil] Additional notes or description for the issue (optional)
|
83
|
+
# @return [Types::IssueItem] Newly created issue details
|
84
|
+
# @raise [ApiError] When the API request fails or returns invalid data
|
85
|
+
# @raise [ArgumentError] When required parameters are missing or invalid
|
86
|
+
def create(meeting_id:, title:, user_id: self.user_id, notes: nil)
|
87
|
+
response = @conn.post("issues/create", {title: title, meetingid: meeting_id, ownerid: user_id, notes: notes}.to_json)
|
88
|
+
Types::IssueItem.new(
|
89
|
+
id: response.body["Id"],
|
90
|
+
meeting_id: response.body["OriginId"],
|
91
|
+
meeting_title: response.body["Origin"],
|
92
|
+
title: response.body["Name"],
|
93
|
+
user_id: response.body["Owner"]["Id"],
|
94
|
+
notes_url: response.body["DetailsUrl"]
|
95
|
+
)
|
96
|
+
end
|
113
97
|
end
|
114
98
|
end
|
@@ -2,171 +2,173 @@
|
|
2
2
|
|
3
3
|
require "bloomy/utils/get_user_id"
|
4
4
|
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
class
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
module Bloomy
|
6
|
+
# Class to handle all the operations related to meeting
|
7
|
+
# @note
|
8
|
+
# This class is already initialized via the client and usable as `client.meeting.method`
|
9
|
+
class Meeting
|
10
|
+
include Bloomy::Utilities::UserIdUtility
|
11
|
+
# Initializes a new Meeting instance
|
12
|
+
#
|
13
|
+
# @param conn [Object] the connection object to interact with the API
|
14
|
+
def initialize(conn)
|
15
|
+
@conn = conn
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
# Lists all meetings for a specific user
|
19
|
+
#
|
20
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
21
|
+
# @return [Array<Hash>] an array of hashes containing meeting details
|
22
|
+
# @example
|
23
|
+
# client.meeting.list
|
24
|
+
# #=> [{ id: 123, name: "Team Meeting" }, ...]
|
25
|
+
def list(user_id = self.user_id)
|
26
|
+
response = @conn.get("L10/#{user_id}/list").body
|
27
|
+
response.map { |meeting| Types::MeetingItem.new(id: meeting["Id"], title: meeting["Name"]) }
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
# Lists all attendees for a specific meeting
|
31
|
+
#
|
32
|
+
# @param meeting_id [Integer] the ID of the meeting
|
33
|
+
# @return [Array<Hash>] an array of hashes containing attendee details
|
34
|
+
# @example
|
35
|
+
# client.meeting.attendees(1)
|
36
|
+
# #=> [{ name: "John Doe", id: 1 }, ...]
|
37
|
+
def attendees(meeting_id)
|
38
|
+
response = @conn.get("L10/#{meeting_id}/attendees").body
|
39
|
+
response.map { |attendee| Types::UserItem.new(id: attendee["Id"], name: attendee["Name"]) }
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
42
|
+
# Lists all issues for a specific meeting
|
43
|
+
#
|
44
|
+
# @param meeting_id [Integer] the ID of the meeting
|
45
|
+
# @param include_closed [Boolean] whether to include closed issues (default: false)
|
46
|
+
# @return [Array<Hash>] an array of hashes containing issue details
|
47
|
+
# @example
|
48
|
+
# client.meeting.issues(1)
|
49
|
+
# #=> [{ id: 1, title: "Issue Title", created_at: "2024-06-10", ... }, ...]
|
50
|
+
def issues(meeting_id, include_closed: false)
|
51
|
+
response = @conn.get("L10/#{meeting_id}/issues?include_resolved=#{include_closed}").body
|
52
|
+
response.map do |issue|
|
53
|
+
Types::IssueItem.new(
|
54
|
+
id: issue["Id"],
|
55
|
+
title: issue["Name"],
|
56
|
+
notes_url: issue["DetailsUrl"],
|
57
|
+
created_at: issue["CreateTime"],
|
58
|
+
completed_at: issue["CloseTime"],
|
59
|
+
user_id: issue.dig("Owner", "Id"),
|
60
|
+
user_name: issue.dig("Owner", "Name"),
|
61
|
+
meeting_id: meeting_id,
|
62
|
+
meeting_title: issue["Origin"]
|
63
|
+
)
|
64
|
+
end
|
63
65
|
end
|
64
|
-
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
67
|
+
# Lists all todos for a specific meeting
|
68
|
+
#
|
69
|
+
# @param meeting_id [Integer] the ID of the meeting
|
70
|
+
# @param include_closed [Boolean] whether to include closed todos (default: false)
|
71
|
+
# @return [Array<Hash>] an array of hashes containing todo details
|
72
|
+
# @example
|
73
|
+
# client.meeting.todos(1)
|
74
|
+
# #=> [{ id: 1, title: "Todo Title", due_date: "2024-06-12", ... }, ...]
|
75
|
+
def todos(meeting_id, include_closed: false)
|
76
|
+
response = @conn.get("L10/#{meeting_id}/todos?INCLUDE_CLOSED=#{include_closed}").body
|
77
|
+
response.map do |todo|
|
78
|
+
Types::TodoItem.new(
|
79
|
+
{
|
80
|
+
id: todo["Id"],
|
81
|
+
title: todo["Name"],
|
82
|
+
due_date: todo["DueDate"],
|
83
|
+
notes_url: todo["DetailsUrl"],
|
84
|
+
status: todo["Complete"] ? "Complete" : "Incomplete",
|
85
|
+
created_at: todo["CreateTime"],
|
86
|
+
completed_at: todo["CompleteTime"],
|
87
|
+
user_id: todo.dig("Owner", "Id"),
|
88
|
+
user_name: todo.dig("Owner", "Name")
|
89
|
+
}
|
90
|
+
)
|
91
|
+
end
|
88
92
|
end
|
89
|
-
end
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
94
|
+
# Lists all metrics for a specific meeting
|
95
|
+
#
|
96
|
+
# @param meeting_id [Integer] the ID of the meeting
|
97
|
+
# @return [Array<Hash>] an array of hashes containing metric details
|
98
|
+
# @example
|
99
|
+
# client.meeting.metrics(1)
|
100
|
+
# #=> [{ id: 1, name: "Sales", target: 100, operator: ">", format: "currency", ... }, ...]
|
101
|
+
def metrics(meeting_id)
|
102
|
+
response = @conn.get("L10/#{meeting_id}/measurables").body
|
103
|
+
return [] if response.nil? || !response.is_a?(Array)
|
104
|
+
|
105
|
+
response.map do |measurable|
|
106
|
+
next unless measurable["Id"] && measurable["Name"]
|
107
|
+
|
108
|
+
Types::MetricItem.new(
|
109
|
+
id: measurable["Id"],
|
110
|
+
title: measurable["Name"].to_s.strip,
|
111
|
+
target: measurable["Target"].to_f,
|
112
|
+
operator: measurable["Direction"].to_s,
|
113
|
+
format: measurable["Modifiers"].to_s,
|
114
|
+
user_id: measurable.dig("Owner", "Id"),
|
115
|
+
user_name: measurable.dig("Owner", "Name"),
|
116
|
+
admin_id: measurable.dig("Admin", "Id"),
|
117
|
+
admin_name: measurable.dig("Admin", "Name")
|
118
|
+
)
|
119
|
+
end.compact
|
116
120
|
end
|
117
|
-
end
|
118
121
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
todos: todos,
|
139
|
-
metrics: measurables
|
140
|
-
}
|
141
|
-
end
|
122
|
+
# Retrieves details of a specific meeting
|
123
|
+
#
|
124
|
+
# @param meeting_id [Integer] the ID of the meeting
|
125
|
+
# @param include_closed [Boolean] whether to include closed issues and todos (default: false)
|
126
|
+
# @return [Hash] a hash containing detailed information about the meeting
|
127
|
+
# @example
|
128
|
+
# client.meeting.details(1)
|
129
|
+
# #=> { id: 1, name: "Team Meeting", attendees: [...], issues: [...], todos: [...], metrics: [...] }
|
130
|
+
def details(meeting_id, include_closed: false)
|
131
|
+
meeting = list.find { |m| m.id == meeting_id }
|
132
|
+
Types::MeetingDetails.new(
|
133
|
+
id: meeting.id,
|
134
|
+
title: meeting.title,
|
135
|
+
attendees: attendees(meeting_id),
|
136
|
+
issues: issues(meeting_id, include_closed: include_closed),
|
137
|
+
todos: todos(meeting_id, include_closed: include_closed),
|
138
|
+
metrics: metrics(meeting_id)
|
139
|
+
)
|
140
|
+
end
|
142
141
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
142
|
+
# Creates a new meeting
|
143
|
+
#
|
144
|
+
# @param title [String] the title of the new meeting
|
145
|
+
# @param add_self [Boolean] whether to add the current user as an attendee (default: true)
|
146
|
+
# @param attendees [Array<Integer>] a list of user IDs to add as attendees
|
147
|
+
# @return [Hash] a hash containing meeting_id, title and attendees array
|
148
|
+
# @example
|
149
|
+
# client.meeting.create("New Meeting", attendees: [2, 3])
|
150
|
+
# #=> { meeting_id: 1, title: "New Meeting", attendees: [2, 3] }
|
151
|
+
def create(title, add_self: true, attendees: [])
|
152
|
+
payload = {title: title, addSelf: add_self}.to_json
|
153
|
+
response = @conn.post("L10/create", payload).body
|
154
|
+
meeting_id = response["meetingId"]
|
155
|
+
meeting_details = {meeting_id: meeting_id, title: title}
|
156
|
+
attendees.each do |attendee|
|
157
|
+
@conn.post("L10/#{meeting_id}/attendees/#{attendee}")
|
158
|
+
end
|
159
|
+
meeting_details.merge(attendees: attendees)
|
159
160
|
end
|
160
|
-
meeting_details.merge(attendees: attendees)
|
161
|
-
end
|
162
161
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
162
|
+
# Deletes a meeting
|
163
|
+
#
|
164
|
+
# @param meeting_id [Integer] the ID of the meeting to delete
|
165
|
+
# @return [Boolean] true if deletion was successful
|
166
|
+
# @example
|
167
|
+
# client.meeting.delete(1)
|
168
|
+
# #=> true
|
169
|
+
def delete(meeting_id)
|
170
|
+
response = @conn.delete("L10/#{meeting_id}")
|
171
|
+
response.success?
|
172
|
+
end
|
171
173
|
end
|
172
174
|
end
|