bloomy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.pre-commit-config.yaml +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +27 -0
- data/LICENSE +201 -0
- data/README.md +127 -0
- data/Rakefile +8 -0
- data/bloomy.gemspec +41 -0
- data/exe/bloomy +4 -0
- data/lib/bloomy/client.rb +43 -0
- data/lib/bloomy/configuration.rb +88 -0
- data/lib/bloomy/operations/issues.rb +90 -0
- data/lib/bloomy/operations/measurables.rb +77 -0
- data/lib/bloomy/operations/meetings.rb +170 -0
- data/lib/bloomy/operations/rocks.rb +109 -0
- data/lib/bloomy/operations/todos.rb +70 -0
- data/lib/bloomy/operations/users.rb +92 -0
- data/lib/bloomy/version.rb +5 -0
- data/lib/bloomy.rb +9 -0
- data/sig/bloomy.rbs +4 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 783fa82ea83467cc8e6d012305831a5bfb73f43897c50b8f80a96276778004f5
|
4
|
+
data.tar.gz: 0e2f5b69910a85d9866d2715921ee5b27f569c466aaac16f5a4343db3bc71ca7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 42f086d17f1b29d61060629a6630a7f8380cf54cb4d993425dd44f0d859765bfbf400d3f6a42df1ec5d6f910c8a45904fd91875005aa76aff9498e50112a6f1b
|
7
|
+
data.tar.gz: b9ac85ecf2dc0114b1cdf92d71def98927233b103b260970f52797a728b0dd8077db04eabc68aba72c0c77df9091c327d8a2151efe2f95767139dd440975a4d1
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# See https://pre-commit.com for more information
|
2
|
+
# See https://pre-commit.com/hooks.html for more hooks
|
3
|
+
repos:
|
4
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
5
|
+
rev: v3.2.0
|
6
|
+
hooks:
|
7
|
+
- id: trailing-whitespace
|
8
|
+
- id: end-of-file-fixer
|
9
|
+
- id: check-yaml
|
10
|
+
- id: check-added-large-files
|
11
|
+
- repo: local
|
12
|
+
hooks:
|
13
|
+
- id: standardrb
|
14
|
+
name: standardrb
|
15
|
+
description: Enforce the community Ruby Style Guide with standardrb
|
16
|
+
entry: standardrb
|
17
|
+
language: ruby
|
18
|
+
types: ["ruby"]
|
19
|
+
args: ["--fix"]
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
SuggestExtensions: false
|
4
|
+
|
5
|
+
Gemspec/RequiredRubyVersion:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Metrics/AbcSize:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Metrics/BlockLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics/CyclomaticComplexity:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Metrics/MethodLength:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Metrics/PerceivedComplexity:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Naming/AccessorMethodName:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Gemspec/DevelopmentDependencies:
|
27
|
+
Enabled: false
|
data/LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
Apache License
|
2
|
+
Version 2.0, January 2004
|
3
|
+
http://www.apache.org/licenses/
|
4
|
+
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6
|
+
|
7
|
+
1. Definitions.
|
8
|
+
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
11
|
+
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13
|
+
the copyright owner that is granting the License.
|
14
|
+
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
16
|
+
other entities that control, are controlled by, or are under common
|
17
|
+
control with that entity. For the purposes of this definition,
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
19
|
+
direction or management of such entity, whether by contract or
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22
|
+
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24
|
+
exercising permissions granted by this License.
|
25
|
+
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
27
|
+
including but not limited to software source code, documentation
|
28
|
+
source, and configuration files.
|
29
|
+
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
31
|
+
transformation or translation of a Source form, including but
|
32
|
+
not limited to compiled object code, generated documentation,
|
33
|
+
and conversions to other media types.
|
34
|
+
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
36
|
+
Object form, made available under the License, as indicated by a
|
37
|
+
copyright notice that is included in or attached to the work
|
38
|
+
(an example is provided in the Appendix below).
|
39
|
+
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46
|
+
the Work and Derivative Works thereof.
|
47
|
+
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
49
|
+
the original version of the Work and any modifications or additions
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
61
|
+
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
64
|
+
subsequently incorporated within the Work.
|
65
|
+
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
72
|
+
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78
|
+
where such license applies only to those patent claims licensable
|
79
|
+
by such Contributor that are necessarily infringed by their
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
82
|
+
institute patent litigation against any entity (including a
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
85
|
+
or contributory patent infringement, then any patent licenses
|
86
|
+
granted to You under this License for that Work shall terminate
|
87
|
+
as of the date such litigation is filed.
|
88
|
+
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
91
|
+
modifications, and in Source or Object form, provided that You
|
92
|
+
meet the following conditions:
|
93
|
+
|
94
|
+
(a) You must give any other recipients of the Work or
|
95
|
+
Derivative Works a copy of this License; and
|
96
|
+
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
98
|
+
stating that You changed the files; and
|
99
|
+
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
102
|
+
attribution notices from the Source form of the Work,
|
103
|
+
excluding those notices that do not pertain to any part of
|
104
|
+
the Derivative Works; and
|
105
|
+
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
108
|
+
include a readable copy of the attribution notices contained
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
111
|
+
of the following places: within a NOTICE text file distributed
|
112
|
+
as part of the Derivative Works; within the Source form or
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
114
|
+
within a display generated by the Derivative Works, if and
|
115
|
+
wherever such third-party notices normally appear. The contents
|
116
|
+
of the NOTICE file are for informational purposes only and
|
117
|
+
do not modify the License. You may add Your own attribution
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
120
|
+
that such additional attribution notices cannot be construed
|
121
|
+
as modifying the License.
|
122
|
+
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
124
|
+
may provide additional or different license terms and conditions
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
128
|
+
the conditions stated in this License.
|
129
|
+
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
133
|
+
this License, without any additional terms or conditions.
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135
|
+
the terms of any separate license agreement you may have executed
|
136
|
+
with Licensor regarding such Contributions.
|
137
|
+
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
140
|
+
except as required for reasonable and customary use in describing the
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
142
|
+
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
152
|
+
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
158
|
+
incidental, or consequential damages of any character arising as a
|
159
|
+
result of this License or out of the use or inability to use the
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
162
|
+
other commercial damages or losses), even if such Contributor
|
163
|
+
has been advised of the possibility of such damages.
|
164
|
+
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168
|
+
or other liability obligations and/or rights consistent with this
|
169
|
+
License. However, in accepting such obligations, You may act only
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
174
|
+
of your accepting any such warranty or additional liability.
|
175
|
+
|
176
|
+
END OF TERMS AND CONDITIONS
|
177
|
+
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
179
|
+
|
180
|
+
To apply the Apache License to your work, attach the following
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182
|
+
replaced with your own identifying information. (Don't include
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
184
|
+
comment syntax for the file format. We also recommend that a
|
185
|
+
file or class name and description of purpose be included on the
|
186
|
+
same "printed page" as the copyright notice for easier
|
187
|
+
identification within third-party archives.
|
188
|
+
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
190
|
+
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192
|
+
you may not use this file except in compliance with the License.
|
193
|
+
You may obtain a copy of the License at
|
194
|
+
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
196
|
+
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
|
+
See the License for the specific language governing permissions and
|
201
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Bloomy
|
2
|
+
|
3
|
+
The Bloomy is a Ruby library for interacting with the Bloom Growth API. It provides convenient methods for getting user details, todos, rocks, meetings, measurables, and issues.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'bloomy'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```sh
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```sh
|
22
|
+
gem install bloomy
|
23
|
+
```
|
24
|
+
|
25
|
+
## Configuration
|
26
|
+
|
27
|
+
### Initialize the Configuration
|
28
|
+
|
29
|
+
First, you need to initialize the configuration and set up the API key.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'bloomy'
|
33
|
+
|
34
|
+
config = Bloomy::Configuration.new
|
35
|
+
```
|
36
|
+
|
37
|
+
### Configure the API Key
|
38
|
+
|
39
|
+
You can configure the API key using your username and password. Optionally, you can store the API key locally for future use.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
config.configure_api_key("your_username", "your_password", store_key: true)
|
43
|
+
```
|
44
|
+
|
45
|
+
Alternatively you can set an `API_KEY` environment variable and it will be loaded automatically for you once you initialize a client.
|
46
|
+
|
47
|
+
## Client Initialization
|
48
|
+
|
49
|
+
Once the configuration is set up, you can initialize the client. The client provides access to various features such as managing users, todos, rocks, meetings, measurables, and issues.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
client = Bloomy::Client.new
|
53
|
+
```
|
54
|
+
|
55
|
+
## Using Client Features
|
56
|
+
|
57
|
+
### User Management
|
58
|
+
|
59
|
+
To interact with user-related features:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# Fetch current user details
|
63
|
+
current_user_details = client.user.details
|
64
|
+
|
65
|
+
# Search for users
|
66
|
+
search_results = client.user.search("John Doe")
|
67
|
+
```
|
68
|
+
|
69
|
+
### Meeting Management
|
70
|
+
|
71
|
+
To interact with meeting-related features:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# List all meetings for the current user
|
75
|
+
meetings = client.meeting.list
|
76
|
+
|
77
|
+
# Get details of a specific meeting
|
78
|
+
meeting_details = client.meeting.details(meeting_id)
|
79
|
+
```
|
80
|
+
|
81
|
+
### Todo Management
|
82
|
+
|
83
|
+
To interact with todo-related features:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
# List all todos for the current user
|
87
|
+
todos = client.todo.list
|
88
|
+
|
89
|
+
# Create a new todo
|
90
|
+
new_todo = client.todo.create(title: "New Task", meeting_id: 1, due_date: "2024-06-15")
|
91
|
+
```
|
92
|
+
|
93
|
+
### Rock Management
|
94
|
+
|
95
|
+
To interact with rock-related features:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# List all rocks for the current user
|
99
|
+
rocks = client.rock.list
|
100
|
+
|
101
|
+
# Create a new rock
|
102
|
+
new_rock = client.rock.create(title: "New Rock", meeting_id: 1)
|
103
|
+
```
|
104
|
+
|
105
|
+
### Measurable Management
|
106
|
+
|
107
|
+
To interact with measurable-related features:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# Get current week details
|
111
|
+
current_week = client.measurable.current_week
|
112
|
+
|
113
|
+
# Get the scorecard for the user
|
114
|
+
scorecard = client.measurable.scorecard
|
115
|
+
```
|
116
|
+
|
117
|
+
### Issue Management
|
118
|
+
|
119
|
+
To interact with issue-related features:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
# Get details of a specific issue
|
123
|
+
issue_details = client.issue.details(issue_id)
|
124
|
+
|
125
|
+
# Create a new issue
|
126
|
+
new_issue = client.issue.create("New Issue", meeting_id)
|
127
|
+
```
|
data/Rakefile
ADDED
data/bloomy.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/bloomy/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "bloomy"
|
7
|
+
spec.version = Bloomy::VERSION
|
8
|
+
spec.authors = ["Franccesco Orozco"]
|
9
|
+
spec.email = ["franccesco@thatai.dev"]
|
10
|
+
|
11
|
+
spec.summary = "Manage your Bloom Growth account from the command line."
|
12
|
+
spec.homepage = "https://github.com/franccesco/bloomy"
|
13
|
+
spec.required_ruby_version = ">= 2.6.0"
|
14
|
+
spec.licenses = ["MIT"]
|
15
|
+
|
16
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) ||
|
26
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Uncomment to register a new dependency of your gem
|
34
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
35
|
+
spec.add_dependency "faraday", "~> 2.9"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
37
|
+
|
38
|
+
# For more information and examples about making a new gem, check out our
|
39
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
40
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
41
|
+
end
|
data/exe/bloomy
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require_relative "operations/users"
|
5
|
+
require_relative "operations/todos"
|
6
|
+
require_relative "operations/rocks"
|
7
|
+
require_relative "operations/meetings"
|
8
|
+
require_relative "operations/measurables"
|
9
|
+
require_relative "operations/issues"
|
10
|
+
|
11
|
+
module Bloomy
|
12
|
+
# The Client class is the main entry point for interacting with the Bloomy API.
|
13
|
+
# It provides methods for managing users, todos, rocks, meetings, measurables, and issues.
|
14
|
+
class Client
|
15
|
+
attr_reader :configuration, :user, :todo, :rock, :meeting, :measurable, :issue
|
16
|
+
|
17
|
+
# Initializes a new Client instance
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# client = Bloomy::Client.new
|
21
|
+
# client.meetings.list
|
22
|
+
# client.user.details
|
23
|
+
# client.meeting.delete(id)
|
24
|
+
def initialize
|
25
|
+
@configuration = Configuration.new
|
26
|
+
@base_url = "https://app.bloomgrowth.com/api/v1"
|
27
|
+
@conn = Faraday.new(url: @base_url) do |faraday|
|
28
|
+
faraday.response :json
|
29
|
+
faraday.adapter Faraday.default_adapter
|
30
|
+
faraday.headers["Accept"] = "*/*"
|
31
|
+
faraday.headers["Content-Type"] = "application/json"
|
32
|
+
faraday.headers["Authorization"] = "Bearer #{configuration.api_key}"
|
33
|
+
end
|
34
|
+
@user = User.new(@conn)
|
35
|
+
@user_id = @user.default_user_id
|
36
|
+
@todo = Todo.new(@conn, @user_id)
|
37
|
+
@rock = Rock.new(@conn, @user_id)
|
38
|
+
@meeting = Meeting.new(@conn, @user_id)
|
39
|
+
@measurable = Measurable.new(@conn, @user_id)
|
40
|
+
@issue = Issue.new(@conn, @user_id)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "faraday"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module Bloomy
|
8
|
+
# The Configuration class is responsible for managing the authentication
|
9
|
+
class Configuration
|
10
|
+
attr_accessor :api_key
|
11
|
+
|
12
|
+
# Initializes a new Configuration instance
|
13
|
+
#
|
14
|
+
# @param [String, nil] optional_parameter Pass an optional API key
|
15
|
+
# @example
|
16
|
+
# config = Bloomy::Configuration.new(api_key)
|
17
|
+
def initialize(api_key = nil)
|
18
|
+
@api_key = api_key || ENV["API_KEY"] || load_api_key
|
19
|
+
end
|
20
|
+
|
21
|
+
# Configures the API key using the provided username and password
|
22
|
+
#
|
23
|
+
# @param username [String] the username for authentication
|
24
|
+
# @param password [String] the password for authentication
|
25
|
+
# @param store_key [Boolean] whether to store the API key (default: false)
|
26
|
+
# @return [void]
|
27
|
+
# @note This method only fetches and stores the API key if it is currently nil.
|
28
|
+
# It saves the key under '~/.bloomy/config.yaml' if 'store_key: true' is passed.
|
29
|
+
# @example
|
30
|
+
# config.configure_api_key("user", "pass", store_key: true)
|
31
|
+
# config.api_key
|
32
|
+
# # => 'xxxx...'
|
33
|
+
def configure_api_key(username, password, store_key: false)
|
34
|
+
@api_key = fetch_api_key(username, password)
|
35
|
+
store_api_key if store_key
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Fetches the API key using the provided username and password
|
41
|
+
#
|
42
|
+
# @param username [String] the username for authentication
|
43
|
+
# @param password [String] the password for authentication
|
44
|
+
# @return [String] the fetched API key
|
45
|
+
def fetch_api_key(username, password)
|
46
|
+
conn = Faraday.new(url: "https://app.bloomgrowth.com")
|
47
|
+
response = conn.post("/Token", "grant_type=password&userName=#{username}&password=#{password}",
|
48
|
+
{"Content-Type" => "application/x-www-form-urlencoded"})
|
49
|
+
JSON.parse(response.body)["access_token"]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Stores the API key in a local configuration file
|
53
|
+
#
|
54
|
+
# @return [void]
|
55
|
+
# @raise [RuntimeError] if the API key is nil
|
56
|
+
# @example
|
57
|
+
# config.store_api_key
|
58
|
+
def store_api_key
|
59
|
+
raise "API key is nil" if @api_key.nil?
|
60
|
+
|
61
|
+
FileUtils.mkdir_p(config_dir)
|
62
|
+
File.write(config_file, {version: 1, api_key: @api_key}.to_yaml)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Loads the API key from a local configuration file
|
66
|
+
#
|
67
|
+
# @return [String, nil] the loaded API key or nil if the file does not exist
|
68
|
+
def load_api_key
|
69
|
+
return nil unless File.exist?(config_file)
|
70
|
+
|
71
|
+
YAML.load_file(config_file)[:api_key]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the directory path for the configuration file
|
75
|
+
#
|
76
|
+
# @return [String] the directory path for the configuration file
|
77
|
+
def config_dir
|
78
|
+
File.expand_path("~/.bloomy")
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the file path for the configuration file
|
82
|
+
#
|
83
|
+
# @return [String] the file path for the configuration file
|
84
|
+
def config_file
|
85
|
+
File.join(config_dir, "config.yaml")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# Class to handle all the operations related to issues
|
6
|
+
class Issue
|
7
|
+
# Initializes a new Issue instance
|
8
|
+
#
|
9
|
+
# @param conn [Object] the connection object to interact with the API
|
10
|
+
# @param user_id [Integer] the ID of the user
|
11
|
+
def initialize(conn, user_id)
|
12
|
+
@conn = conn
|
13
|
+
@user_id = user_id
|
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
|
+
name: response["Origin"]
|
34
|
+
},
|
35
|
+
owner_details: {
|
36
|
+
id: response["Owner"]["Id"],
|
37
|
+
name: response["Owner"]["Name"]
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Lists issues for a specific user
|
43
|
+
#
|
44
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
45
|
+
# @return [Array<Hash>] an array of hashes containing issues details
|
46
|
+
# @example
|
47
|
+
# issue.list
|
48
|
+
# #=> [{ id: 123, title: "Issue Title", notes_url: "http://details.url", ... }, ...]
|
49
|
+
def list(user_id: @user_id)
|
50
|
+
response = @conn.get("issues/users/#{user_id}").body
|
51
|
+
response.map do |issue|
|
52
|
+
{
|
53
|
+
id: issue["Id"],
|
54
|
+
title: issue["Name"],
|
55
|
+
notes_url: issue["DetailsUrl"],
|
56
|
+
created_at: issue["CreateTime"],
|
57
|
+
meeting_id: issue["OriginId"],
|
58
|
+
meeting_name: issue["Origin"]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Marks an issue as complete
|
64
|
+
#
|
65
|
+
# @param issue_id [Integer] the ID of the issue
|
66
|
+
# @return [Boolean] true if the operation was successful, false otherwise
|
67
|
+
# @example
|
68
|
+
# issue.complete(123)
|
69
|
+
# #=> true
|
70
|
+
def complete(issue_id)
|
71
|
+
response = @conn.post("issues/#{issue_id}/complete", {complete: true}.to_json).status
|
72
|
+
response == 200
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates a new issue
|
76
|
+
#
|
77
|
+
# @param issue_title [String] the title of the new issue
|
78
|
+
# @param meeting_id [Integer] the ID of the meeting associated with the issue
|
79
|
+
# @return [Hash] a hash containing the new issue's ID and title
|
80
|
+
# @example
|
81
|
+
# issue.create("New Issue", 456)
|
82
|
+
# #=> { id: 789, title: "New Issue" }
|
83
|
+
def create(issue_title, meeting_id)
|
84
|
+
response = @conn.post("issues/create", {title: issue_title, meetingid: meeting_id}.to_json)
|
85
|
+
{
|
86
|
+
id: response.body["Id"],
|
87
|
+
title: response.body["Name"]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# Class to handle all the operations related to measurables
|
6
|
+
# @note
|
7
|
+
# This class is already initialized via the client and usable as `client.measurable.method`
|
8
|
+
class Measurable
|
9
|
+
# Initializes a new Measurable instance
|
10
|
+
#
|
11
|
+
# @param conn [Object] the connection object to interact with the API
|
12
|
+
# @param user_id [Integer] the ID of the user
|
13
|
+
def initialize(conn, user_id)
|
14
|
+
@conn = conn
|
15
|
+
@user_id = user_id
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieves the current week details
|
19
|
+
#
|
20
|
+
# @return [Hash] a hash containing current week details
|
21
|
+
# @example
|
22
|
+
# client.measurable.current_week
|
23
|
+
# #=> { id: 123, week_number: 24, week_start: "2024-06-10", week_end: "2024-06-16" }
|
24
|
+
def current_week
|
25
|
+
response = @conn.get("weeks/current").body
|
26
|
+
{
|
27
|
+
id: response["Id"],
|
28
|
+
week_number: response["ForWeekNumber"],
|
29
|
+
week_start: response["LocalDate"]["Date"],
|
30
|
+
week_end: response["ForWeek"]
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retrieves the scorecard for the user
|
35
|
+
#
|
36
|
+
# @param current_week_only [Boolean] whether to include only the current week's scores (default: true)
|
37
|
+
# @param show_empty [Boolean] whether to include scores with nil values (default: true)
|
38
|
+
# @return [Array<Hash>] an array of hashes containing scorecard details
|
39
|
+
# @example
|
40
|
+
# client.measurable.scorecard
|
41
|
+
# #=> [{ id: 123, title: "Sales", target: 100, value: 80, updated_at: "2024-06-12", week_number: 24 }, ...]
|
42
|
+
def scorecard(current_week_only: true, show_empty: true)
|
43
|
+
response = @conn.get("scorecard/user/mine").body
|
44
|
+
scorecards = response["Scores"].map do |scorecard|
|
45
|
+
{
|
46
|
+
id: scorecard["Id"],
|
47
|
+
title: scorecard["MeasurableName"],
|
48
|
+
target: scorecard["Target"],
|
49
|
+
value: scorecard["Measured"],
|
50
|
+
updated_at: scorecard["DateEntered"],
|
51
|
+
week_number: scorecard["ForWeek"]
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
if current_week_only
|
56
|
+
week_id = current_week[:week_number]
|
57
|
+
scorecards.select do |scorecard|
|
58
|
+
scorecard[:week_number] == week_id && (show_empty || scorecard[:value].nil?)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
scorecards.select { |scorecard| show_empty || scorecard[:value].nil? }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Updates a scorecard with a new measured value
|
66
|
+
#
|
67
|
+
# @param scorecard_id [Integer] the ID of the scorecard to update
|
68
|
+
# @param measured [Numeric] the new measured value
|
69
|
+
# @return [Boolean] true if the operation was successful, false otherwise
|
70
|
+
# @example
|
71
|
+
# client.measurable.update(1, 85)
|
72
|
+
# #=> true
|
73
|
+
def update(scorecard_id, measured)
|
74
|
+
response = @conn.put("scores/#{scorecard_id}", {value: measured}.to_json).status
|
75
|
+
response == 200
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class to handle all the operations related to meeting
|
4
|
+
# @note
|
5
|
+
# This class is already initialized via the client and usable as `client.measurable.method`
|
6
|
+
class Meeting
|
7
|
+
# Initializes a new Meeting instance
|
8
|
+
#
|
9
|
+
# @param conn [Object] the connection object to interact with the API
|
10
|
+
# @param user_id [Integer] the ID of the user
|
11
|
+
def initialize(conn, user_id)
|
12
|
+
@conn = conn
|
13
|
+
@user_id = user_id
|
14
|
+
end
|
15
|
+
|
16
|
+
# Lists all meetings for a specific user
|
17
|
+
#
|
18
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
19
|
+
# @return [Array<Hash>] an array of hashes containing meeting details
|
20
|
+
# @example
|
21
|
+
# client.meeting.list
|
22
|
+
# #=> [{ id: 123, name: "Team Meeting" }, ...]
|
23
|
+
def list(user_id: @user_id)
|
24
|
+
response = @conn.get("L10/#{user_id}/list").body
|
25
|
+
response.map { |meeting| {id: meeting["Id"], name: meeting["Name"]} }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Lists all attendees for a specific meeting
|
29
|
+
#
|
30
|
+
# @param meeting_id [Integer] the ID of the meeting
|
31
|
+
# @return [Array<Hash>] an array of hashes containing attendee details
|
32
|
+
# @example
|
33
|
+
# client.meeting.attendees(1)
|
34
|
+
# #=> [{ name: "John Doe", id: 1 }, ...]
|
35
|
+
def attendees(meeting_id)
|
36
|
+
response = @conn.get("L10/#{meeting_id}/attendees").body
|
37
|
+
response.map { |attendee| {name: attendee["Name"], id: attendee["Id"]} }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Lists all issues for a specific meeting
|
41
|
+
#
|
42
|
+
# @param meeting_id [Integer] the ID of the meeting
|
43
|
+
# @param include_closed [Boolean] whether to include closed issues (default: false)
|
44
|
+
# @return [Array<Hash>] an array of hashes containing issue details
|
45
|
+
# @example
|
46
|
+
# client.meeting.issues(1)
|
47
|
+
# #=> [{ id: 1, title: "Issue Title", created_at: "2024-06-10", ... }, ...]
|
48
|
+
def issues(meeting_id, include_closed: false)
|
49
|
+
response = @conn.get("L10/#{meeting_id}/issues?include_resolved=#{include_closed}").body
|
50
|
+
response.map do |issue|
|
51
|
+
{
|
52
|
+
id: issue["Id"],
|
53
|
+
title: issue["Name"],
|
54
|
+
created_at: issue["CreateTime"],
|
55
|
+
closed_at: issue["CloseTime"],
|
56
|
+
details_url: issue["DetailsUrl"],
|
57
|
+
owner: {
|
58
|
+
id: issue["Owner"]["Id"],
|
59
|
+
name: issue["Owner"]["Name"]
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Lists all todos for a specific meeting
|
66
|
+
#
|
67
|
+
# @param meeting_id [Integer] the ID of the meeting
|
68
|
+
# @param include_closed [Boolean] whether to include closed todos (default: false)
|
69
|
+
# @return [Array<Hash>] an array of hashes containing todo details
|
70
|
+
# @example
|
71
|
+
# client.meeting.todos(1)
|
72
|
+
# #=> [{ id: 1, title: "Todo Title", due_date: "2024-06-12", ... }, ...]
|
73
|
+
def todos(meeting_id, include_closed: false)
|
74
|
+
response = @conn.get("L10/#{meeting_id}/todos?INCLUDE_CLOSED=#{include_closed}").body
|
75
|
+
response.map do |todo|
|
76
|
+
{
|
77
|
+
id: todo["Id"],
|
78
|
+
title: todo["Name"],
|
79
|
+
due_date: todo["DueDate"],
|
80
|
+
details_url: todo["DetailsUrl"],
|
81
|
+
completed_at: todo["CompleteTime"],
|
82
|
+
owner: {
|
83
|
+
id: todo["Owner"]["Id"],
|
84
|
+
name: todo["Owner"]["Name"]
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Lists all metrics for a specific meeting
|
91
|
+
#
|
92
|
+
# @param meeting_id [Integer] the ID of the meeting
|
93
|
+
# @return [Array<Hash>] an array of hashes containing metric details
|
94
|
+
# @example
|
95
|
+
# client.meeting.metrics(1)
|
96
|
+
# #=> [{ id: 1, name: "Sales", target: 100, operator: ">", format: "currency", ... }, ...]
|
97
|
+
def metrics(meeting_id)
|
98
|
+
response = @conn.get("L10/#{meeting_id}/measurables").body
|
99
|
+
response.map do |measurable|
|
100
|
+
{
|
101
|
+
id: measurable["Id"],
|
102
|
+
name: measurable["Name"].strip,
|
103
|
+
target: measurable["Target"],
|
104
|
+
operator: measurable["Direction"],
|
105
|
+
format: measurable["Modifiers"],
|
106
|
+
owner: {
|
107
|
+
id: measurable["Owner"]["Id"],
|
108
|
+
name: measurable["Owner"]["Name"]
|
109
|
+
},
|
110
|
+
admin: {
|
111
|
+
id: measurable["Admin"]["Id"],
|
112
|
+
name: measurable["Admin"]["Name"]
|
113
|
+
}
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Retrieves details of a specific meeting
|
119
|
+
#
|
120
|
+
# @param meeting_id [Integer] the ID of the meeting
|
121
|
+
# @param include_closed [Boolean] whether to include closed issues and todos (default: false)
|
122
|
+
# @return [Hash] a hash containing detailed information about the meeting
|
123
|
+
# @example
|
124
|
+
# client.meeting.details(1)
|
125
|
+
# #=> { id: 1, name: "Team Meeting", attendees: [...], issues: [...], todos: [...], metrics: [...] }
|
126
|
+
def details(meeting_id, include_closed: false)
|
127
|
+
meeting = list.find { |m| m[:id] == meeting_id }
|
128
|
+
attendees = attendees(meeting_id)
|
129
|
+
issues = issues(meeting_id, include_closed: include_closed)
|
130
|
+
todos = todos(meeting_id, include_closed: include_closed)
|
131
|
+
measurables = metrics(meeting_id)
|
132
|
+
{
|
133
|
+
id: meeting[:id],
|
134
|
+
name: meeting[:name],
|
135
|
+
attendees: attendees,
|
136
|
+
issues: issues,
|
137
|
+
todos: todos,
|
138
|
+
metrics: measurables
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
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 the new meeting's ID and title, and the list of attendees
|
148
|
+
# @example
|
149
|
+
# client.meeting.create(title: "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)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Deletes a meeting
|
163
|
+
#
|
164
|
+
# @param meeting_id [Integer] the ID of the meeting to delete
|
165
|
+
# @example
|
166
|
+
# client.meeting.delete(1)
|
167
|
+
def delete(meeting_id)
|
168
|
+
@conn.delete("L10/#{meeting_id}")
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class to handle all the operations related to rocks
|
4
|
+
class Rock
|
5
|
+
# Initializes a new Rock instance
|
6
|
+
#
|
7
|
+
# @param conn [Object] the connection object to interact with the API
|
8
|
+
# @param user_id [Integer] the ID of the user
|
9
|
+
def initialize(conn, user_id)
|
10
|
+
@conn = conn
|
11
|
+
@user_id = user_id
|
12
|
+
end
|
13
|
+
|
14
|
+
# Lists all rocks for a specific user
|
15
|
+
#
|
16
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
17
|
+
# @param archived [Boolean] whether to include archived rocks (default: false)
|
18
|
+
# @return [Array<Hash>] an array of hashes containing rock details or a hash with active and archived rocks
|
19
|
+
# @example
|
20
|
+
# client.rock.list
|
21
|
+
# #=> [{ id: 1, title: "Complete project", created_at: "2024-06-10", ... }, ...]
|
22
|
+
def list(user_id: @user_id, archived: false)
|
23
|
+
active_rocks = @conn.get("rocks/user/#{user_id}?include_origin=true").body.map do |rock|
|
24
|
+
{
|
25
|
+
id: rock["Id"],
|
26
|
+
title: rock["Name"],
|
27
|
+
created_at: rock["CreateTime"],
|
28
|
+
due_date: rock["DueDate"],
|
29
|
+
status: rock["Complete"] ? "Completed" : "Incomplete",
|
30
|
+
meeting_id: rock["Origins"].empty? ? nil : rock["Origins"][0]["Id"],
|
31
|
+
meeting_name: rock["Origins"].empty? ? nil : rock["Origins"][0]["Name"]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
archived ? {active: active_rocks, archived: get_archived_rocks(user_id: @user_id)} : active_rocks
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a new rock
|
39
|
+
#
|
40
|
+
# @param title [String] the title of the new rock
|
41
|
+
# @param meeting_id [Integer] the ID of the meeting associated with the rock
|
42
|
+
# @param user_id [Integer] the ID of the user responsible for the rock (default: initialized user ID)
|
43
|
+
# @return [Hash] a hash containing the new rock's details
|
44
|
+
# @example
|
45
|
+
# client.rock.create(title: "New Rock", meeting_id: 1)
|
46
|
+
# #=> { rock_id: 1, title: "New Rock", meeting_id: 1, ... }
|
47
|
+
def create(title:, meeting_id:, user_id: @user_id)
|
48
|
+
payload = {title: title, accountableUserId: user_id}.to_json
|
49
|
+
response = @conn.post("/api/v1/L10/#{meeting_id}/rocks", payload).body
|
50
|
+
{
|
51
|
+
rock_id: response["Id"],
|
52
|
+
title: title,
|
53
|
+
meeting_id: meeting_id,
|
54
|
+
meeting_name: response["Origins"][0]["Name"],
|
55
|
+
user_id: user_id,
|
56
|
+
user_name: response["Owner"]["Name"],
|
57
|
+
created_at: DateTime.parse(response["CreateTime"])
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Deletes a rock
|
62
|
+
#
|
63
|
+
# @param rock_id [Integer] the ID of the rock to delete
|
64
|
+
# @return [Hash] a hash containing the status of the delete operation
|
65
|
+
# @example
|
66
|
+
# client.rock.delete(1)
|
67
|
+
# #=> { status: 200 }
|
68
|
+
def delete(rock_id)
|
69
|
+
response = @conn.delete("/api/v1/rocks/#{rock_id}")
|
70
|
+
{status: response.status}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Updates a rock
|
74
|
+
#
|
75
|
+
# @param rock_id [Integer] the ID of the rock to update
|
76
|
+
# @param title [String] the new title of the rock
|
77
|
+
# @param accountable_user [Integer] the ID of the user responsible for the rock (default: initialized user ID)
|
78
|
+
# @return [Hash] a hash containing the status of the update operation
|
79
|
+
# @example
|
80
|
+
# client.rock.update(rock_id: 1, title: "Updated Rock")
|
81
|
+
# #=> { status: 200 }
|
82
|
+
def update(rock_id:, title:, accountable_user: @user_id)
|
83
|
+
payload = {title: title, accountableUserId: accountable_user}.to_json
|
84
|
+
response = @conn.put("/api/v1/rocks/#{rock_id}", payload)
|
85
|
+
{status: response.status}
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Retrieves all archived rocks for a specific user (private method)
|
91
|
+
#
|
92
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
93
|
+
# @return [Array<Hash>] an array of hashes containing archived rock details
|
94
|
+
# @example
|
95
|
+
# rock.send(:get_archived_rocks)
|
96
|
+
# #=> [{ id: 1, title: "Archived Rock", created_at: "2024-06-10", ... }, ...]
|
97
|
+
def get_archived_rocks(user_id: @user_id)
|
98
|
+
response = @conn.get("archivedrocks/user/#{user_id}").body
|
99
|
+
response.map do |rock|
|
100
|
+
{
|
101
|
+
id: rock["Id"],
|
102
|
+
title: rock["Name"],
|
103
|
+
created_at: rock["CreateTime"],
|
104
|
+
due_date: rock["DueDate"],
|
105
|
+
status: rock["Complete"] ? "Complete" : "Incomplete"
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class to handle all the operations related to todos
|
4
|
+
class Todo
|
5
|
+
# Initializes a new Todo instance
|
6
|
+
#
|
7
|
+
# @param conn [Object] the connection object to interact with the API
|
8
|
+
# @param user_id [Integer] the ID of the user
|
9
|
+
def initialize(conn, user_id)
|
10
|
+
@conn = conn
|
11
|
+
@user_id = user_id
|
12
|
+
end
|
13
|
+
|
14
|
+
# Lists all todos for a specific user
|
15
|
+
#
|
16
|
+
# @param user_id [Integer] the ID of the user (default is the initialized user ID)
|
17
|
+
# @return [Array<Hash>] an array of hashes containing todo details
|
18
|
+
# @example
|
19
|
+
# client.todo.list
|
20
|
+
# #=> [{ id: 1, title: "Finish report", due_date: "2024-06-10", ... }, ...]
|
21
|
+
def list(user_id: @user_id)
|
22
|
+
response = @conn.get("todo/user/#{user_id}").body
|
23
|
+
response.map do |todo|
|
24
|
+
{
|
25
|
+
id: todo["Id"],
|
26
|
+
title: todo["Name"],
|
27
|
+
due_date: todo["DueDate"],
|
28
|
+
created_at: todo["CreateTime"],
|
29
|
+
completed_at: todo["CompleteTime"],
|
30
|
+
status: todo["Complete"] ? "Complete" : "Incomplete"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a new todo
|
36
|
+
#
|
37
|
+
# @param title [String] the title of the new todo
|
38
|
+
# @param meeting_id [Integer] the ID of the meeting associated with the todo
|
39
|
+
# @param due_date [String, nil] the due date of the todo (optional)
|
40
|
+
# @param user_id [Integer] the ID of the user responsible for the todo (default: initialized user ID)
|
41
|
+
# @return [Hash] a hash containing the new todo's details
|
42
|
+
# @example
|
43
|
+
# client.todo.create(title: "New Todo", meeting_id: 1, due_date: "2024-06-15")
|
44
|
+
# #=> { id: 1, title: "New Todo", meeting_name: "Team Meeting", ... }
|
45
|
+
def create(title:, meeting_id:, due_date: nil, user_id: @user_id)
|
46
|
+
payload = {title: title, accountableUserId: user_id}
|
47
|
+
payload[:dueDate] = due_date if due_date
|
48
|
+
response = @conn.post("/api/v1/L10/#{meeting_id}/todos", payload.to_json).body
|
49
|
+
|
50
|
+
{
|
51
|
+
id: response["Id"],
|
52
|
+
title: response["Name"],
|
53
|
+
meeting_name: response["Origin"],
|
54
|
+
meeting_id: response["OriginId"],
|
55
|
+
due_date: response["DueDate"]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Marks a todo as complete
|
60
|
+
#
|
61
|
+
# @param todo_id [Integer] the ID of the todo to complete
|
62
|
+
# @return [Hash] a hash containing the status of the complete operation
|
63
|
+
# @example
|
64
|
+
# todo.complete(1)
|
65
|
+
# #=> { status: 200 }
|
66
|
+
def complete(todo_id)
|
67
|
+
response = @conn.post("/api/v1/todo/#{todo_id}/complete?status=true")
|
68
|
+
{status: response.status}
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class to handle all the operations related to users
|
4
|
+
class User
|
5
|
+
attr_reader :default_user_id
|
6
|
+
|
7
|
+
# Initializes a new User instance
|
8
|
+
#
|
9
|
+
# @param conn [Object] the connection object to interact with the API
|
10
|
+
def initialize(conn)
|
11
|
+
@conn = conn
|
12
|
+
@default_user_id = current_user_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# Retrieves the current user's ID
|
16
|
+
#
|
17
|
+
# @return [Integer] the ID of the current user
|
18
|
+
# @example
|
19
|
+
# client.user.current_user_id
|
20
|
+
# #=> 1
|
21
|
+
def current_user_id
|
22
|
+
response = @conn.get("users/mine").body
|
23
|
+
response["Id"]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Retrieves details of a specific user
|
27
|
+
#
|
28
|
+
# @param user_id [Integer] the ID of the user (default: the current user ID)
|
29
|
+
# @param direct_reports [Boolean] whether to include direct reports (default: false)
|
30
|
+
# @param positions [Boolean] whether to include positions (default: false)
|
31
|
+
# @param all [Boolean] whether to include both direct reports and positions (default: false)
|
32
|
+
# @return [Hash] a hash containing user details
|
33
|
+
# @example
|
34
|
+
# client.user.details
|
35
|
+
# #=> {name: "John Doe", id: 1, image_url: "http://example.com/image.jpg", ...}
|
36
|
+
def details(user_id: @default_user_id, direct_reports: false, positions: false, all: false)
|
37
|
+
response = @conn.get("users/#{user_id}").body
|
38
|
+
user_details = {name: response["Name"], id: response["Id"], image_url: response["ImageUrl"]}
|
39
|
+
|
40
|
+
user_details[:direct_reports] = direct_reports(user_id: user_id) if direct_reports || all
|
41
|
+
user_details[:positions] = positions(user_id: user_id) if positions || all
|
42
|
+
|
43
|
+
user_details
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieves direct reports of a specific user
|
47
|
+
#
|
48
|
+
# @param user_id [Integer] the ID of the user (default: the current user ID)
|
49
|
+
# @return [Array<Hash>] an array of hashes containing direct report details
|
50
|
+
# @example
|
51
|
+
# client.user.direct_reports
|
52
|
+
# #=> [{name: "Jane Smith", id: 2, image_url: "http://example.com/image.jpg"}, ...]
|
53
|
+
def direct_reports(user_id: @default_user_id)
|
54
|
+
direct_reports_response = @conn.get("users/#{user_id}/directreports").body
|
55
|
+
direct_reports_response.map { |report| {name: report["Name"], id: report["Id"], image_url: report["ImageUrl"]} }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Retrieves positions of a specific user
|
59
|
+
#
|
60
|
+
# @param user_id [Integer] the ID of the user (default: the current user ID)
|
61
|
+
# @return [Array<Hash>] an array of hashes containing position details
|
62
|
+
# @example
|
63
|
+
# user.positions
|
64
|
+
# #=> [{name: "Manager", id: 3}, ...]
|
65
|
+
def positions(user_id: @default_user_id)
|
66
|
+
position_response = @conn.get("users/#{user_id}/seats").body
|
67
|
+
position_response.map do |position|
|
68
|
+
{name: position["Group"]["Position"]["Name"], id: position["Group"]["Position"]["Id"]}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Searches for users based on a search term
|
73
|
+
#
|
74
|
+
# @param term [String] the search term
|
75
|
+
# @return [Array<Hash>] an array of hashes containing search results
|
76
|
+
# @example
|
77
|
+
# user.search("John")
|
78
|
+
# #=> [{id: 1, name: "John Doe", description: "Developer", ...}, ...]
|
79
|
+
def search(term)
|
80
|
+
response = @conn.get("search/user", term: term).body
|
81
|
+
response.map do |user|
|
82
|
+
{
|
83
|
+
id: user["Id"],
|
84
|
+
name: user["Name"],
|
85
|
+
description: user["Description"],
|
86
|
+
email: user["Email"],
|
87
|
+
organization_id: user["OrganizationId"],
|
88
|
+
image_url: user["ImageUrl"]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/bloomy.rb
ADDED
data/sig/bloomy.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bloomy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Franccesco Orozco
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.13'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- franccesco@thatai.dev
|
44
|
+
executables:
|
45
|
+
- bloomy
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".pre-commit-config.yaml"
|
50
|
+
- ".rspec"
|
51
|
+
- ".rubocop.yml"
|
52
|
+
- LICENSE
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- bloomy.gemspec
|
56
|
+
- exe/bloomy
|
57
|
+
- lib/bloomy.rb
|
58
|
+
- lib/bloomy/client.rb
|
59
|
+
- lib/bloomy/configuration.rb
|
60
|
+
- lib/bloomy/operations/issues.rb
|
61
|
+
- lib/bloomy/operations/measurables.rb
|
62
|
+
- lib/bloomy/operations/meetings.rb
|
63
|
+
- lib/bloomy/operations/rocks.rb
|
64
|
+
- lib/bloomy/operations/todos.rb
|
65
|
+
- lib/bloomy/operations/users.rb
|
66
|
+
- lib/bloomy/version.rb
|
67
|
+
- sig/bloomy.rbs
|
68
|
+
homepage: https://github.com/franccesco/bloomy
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata:
|
72
|
+
homepage_uri: https://github.com/franccesco/bloomy
|
73
|
+
rubygems_mfa_required: 'true'
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.6.0
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubygems_version: 3.5.11
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Manage your Bloom Growth account from the command line.
|
93
|
+
test_files: []
|