monday_ruby 1.0.0 → 1.2.0
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/.env +1 -1
- data/.rspec +0 -1
- data/.rubocop.yml +19 -0
- data/.simplecov +1 -0
- data/CHANGELOG.md +49 -0
- data/CONTRIBUTING.md +165 -0
- data/README.md +167 -88
- data/docs/.vitepress/config.mjs +255 -0
- data/docs/.vitepress/theme/index.js +4 -0
- data/docs/.vitepress/theme/style.css +43 -0
- data/docs/README.md +80 -0
- data/docs/explanation/architecture.md +507 -0
- data/docs/explanation/best-practices/errors.md +478 -0
- data/docs/explanation/best-practices/performance.md +1084 -0
- data/docs/explanation/best-practices/rate-limiting.md +630 -0
- data/docs/explanation/best-practices/testing.md +820 -0
- data/docs/explanation/column-values.md +857 -0
- data/docs/explanation/design.md +795 -0
- data/docs/explanation/graphql.md +356 -0
- data/docs/explanation/migration/v1.md +808 -0
- data/docs/explanation/pagination.md +447 -0
- data/docs/guides/advanced/batch.md +1274 -0
- data/docs/guides/advanced/complex-queries.md +1114 -0
- data/docs/guides/advanced/errors.md +818 -0
- data/docs/guides/advanced/pagination.md +934 -0
- data/docs/guides/advanced/rate-limiting.md +981 -0
- data/docs/guides/authentication.md +286 -0
- data/docs/guides/boards/create.md +386 -0
- data/docs/guides/boards/delete.md +405 -0
- data/docs/guides/boards/duplicate.md +511 -0
- data/docs/guides/boards/query.md +530 -0
- data/docs/guides/boards/update.md +453 -0
- data/docs/guides/columns/create.md +452 -0
- data/docs/guides/columns/metadata.md +492 -0
- data/docs/guides/columns/query.md +455 -0
- data/docs/guides/columns/update-multiple.md +459 -0
- data/docs/guides/columns/update-values.md +509 -0
- data/docs/guides/files/add-to-column.md +40 -0
- data/docs/guides/files/add-to-update.md +37 -0
- data/docs/guides/files/clear-column.md +33 -0
- data/docs/guides/first-request.md +285 -0
- data/docs/guides/folders/manage.md +750 -0
- data/docs/guides/groups/items.md +626 -0
- data/docs/guides/groups/manage.md +501 -0
- data/docs/guides/installation.md +169 -0
- data/docs/guides/items/create.md +493 -0
- data/docs/guides/items/delete.md +514 -0
- data/docs/guides/items/query.md +605 -0
- data/docs/guides/items/subitems.md +483 -0
- data/docs/guides/items/update.md +699 -0
- data/docs/guides/updates/manage.md +619 -0
- data/docs/guides/use-cases/dashboard.md +1421 -0
- data/docs/guides/use-cases/import.md +1962 -0
- data/docs/guides/use-cases/task-management.md +1381 -0
- data/docs/guides/workspaces/manage.md +502 -0
- data/docs/index.md +69 -0
- data/docs/package-lock.json +2468 -0
- data/docs/package.json +13 -0
- data/docs/reference/client.md +540 -0
- data/docs/reference/configuration.md +586 -0
- data/docs/reference/errors.md +693 -0
- data/docs/reference/resources/account.md +208 -0
- data/docs/reference/resources/activity-log.md +369 -0
- data/docs/reference/resources/board-view.md +359 -0
- data/docs/reference/resources/board.md +393 -0
- data/docs/reference/resources/column.md +543 -0
- data/docs/reference/resources/file.md +236 -0
- data/docs/reference/resources/folder.md +386 -0
- data/docs/reference/resources/group.md +507 -0
- data/docs/reference/resources/item.md +348 -0
- data/docs/reference/resources/subitem.md +267 -0
- data/docs/reference/resources/update.md +259 -0
- data/docs/reference/resources/workspace.md +213 -0
- data/docs/reference/response.md +560 -0
- data/docs/tutorial/first-integration.md +713 -0
- data/lib/monday/client.rb +41 -2
- data/lib/monday/configuration.rb +13 -0
- data/lib/monday/deprecation.rb +23 -0
- data/lib/monday/error.rb +5 -2
- data/lib/monday/request.rb +19 -1
- data/lib/monday/resources/base.rb +4 -0
- data/lib/monday/resources/board.rb +52 -0
- data/lib/monday/resources/column.rb +6 -0
- data/lib/monday/resources/file.rb +56 -0
- data/lib/monday/resources/folder.rb +55 -0
- data/lib/monday/resources/group.rb +66 -0
- data/lib/monday/resources/item.rb +62 -0
- data/lib/monday/util.rb +33 -1
- data/lib/monday/version.rb +1 -1
- data/lib/monday_ruby.rb +1 -0
- metadata +92 -11
- data/monday_ruby.gemspec +0 -39
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Learn how to handle errors when using the monday_ruby gem, from basic rescue blocks to advanced retry strategies.
|
|
4
|
+
|
|
5
|
+
## Understanding monday_ruby Errors
|
|
6
|
+
|
|
7
|
+
The monday_ruby gem provides a comprehensive error hierarchy that maps both HTTP status codes and monday.com API error codes to specific Ruby exception classes.
|
|
8
|
+
|
|
9
|
+
### Error Hierarchy
|
|
10
|
+
|
|
11
|
+
All errors inherit from `Monday::Error`, making it easy to catch any monday.com-related error:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
Monday::Error (base class)
|
|
15
|
+
├── Monday::AuthorizationError (401, 403)
|
|
16
|
+
├── Monday::InvalidRequestError (400)
|
|
17
|
+
├── Monday::ResourceNotFoundError (404)
|
|
18
|
+
├── Monday::InternalServerError (500)
|
|
19
|
+
├── Monday::RateLimitError (429)
|
|
20
|
+
└── Monday::ComplexityError (GraphQL complexity limit)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### When Errors Are Raised
|
|
24
|
+
|
|
25
|
+
Errors are raised in two scenarios:
|
|
26
|
+
|
|
27
|
+
1. **HTTP Status Codes**: Non-2xx status codes (401, 404, 500, etc.)
|
|
28
|
+
2. **GraphQL Error Codes**: Even when HTTP status is 200, GraphQL errors in the response trigger exceptions
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# Example: HTTP 401 raises Monday::AuthorizationError
|
|
32
|
+
client = Monday::Client.new(token: "invalid_token")
|
|
33
|
+
client.account.query # => Monday::AuthorizationError
|
|
34
|
+
|
|
35
|
+
# Example: HTTP 200 with error_code raises Monday::InvalidRequestError
|
|
36
|
+
client.board.query(args: {ids: [999999]}) # => Monday::InvalidRequestError
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Error Properties
|
|
40
|
+
|
|
41
|
+
Every error object provides access to:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
begin
|
|
45
|
+
client.board.query(args: {ids: [123]})
|
|
46
|
+
rescue Monday::Error => e
|
|
47
|
+
e.message # Human-readable error message
|
|
48
|
+
e.code # Error code (HTTP status or error_code)
|
|
49
|
+
e.response # Full Monday::Response object
|
|
50
|
+
e.error_data # Additional error metadata (hash)
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Basic Error Handling
|
|
55
|
+
|
|
56
|
+
### Check Response Success
|
|
57
|
+
|
|
58
|
+
The safest approach is to check `response.success?` before accessing data:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
response = client.board.query(args: {ids: [123]}, select: ["id", "name"])
|
|
62
|
+
|
|
63
|
+
if response.success?
|
|
64
|
+
boards = response.body["data"]["boards"]
|
|
65
|
+
puts "Found #{boards.length} boards"
|
|
66
|
+
else
|
|
67
|
+
puts "Request failed: #{response.body["error_message"]}"
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Rescue Specific Errors
|
|
72
|
+
|
|
73
|
+
Catch specific error types to handle different scenarios:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
begin
|
|
77
|
+
response = client.item.create(
|
|
78
|
+
args: {
|
|
79
|
+
board_id: 123,
|
|
80
|
+
item_name: "New Task"
|
|
81
|
+
},
|
|
82
|
+
select: ["id", "name"]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
item = response.body["data"]["create_item"]
|
|
86
|
+
puts "Created item: #{item["name"]}"
|
|
87
|
+
|
|
88
|
+
rescue Monday::AuthorizationError => e
|
|
89
|
+
puts "Authentication failed. Check your API token."
|
|
90
|
+
|
|
91
|
+
rescue Monday::InvalidRequestError => e
|
|
92
|
+
puts "Invalid request: #{e.message}"
|
|
93
|
+
# Check error_data for specifics
|
|
94
|
+
puts "Error details: #{e.error_data}"
|
|
95
|
+
|
|
96
|
+
rescue Monday::Error => e
|
|
97
|
+
puts "Unexpected error: #{e.message}"
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Access Error Messages
|
|
102
|
+
|
|
103
|
+
Extract error information from the exception:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
begin
|
|
107
|
+
client.folder.delete(args: {folder_id: "invalid_id"})
|
|
108
|
+
rescue Monday::Error => e
|
|
109
|
+
puts "Error code: #{e.code}"
|
|
110
|
+
puts "Error message: #{e.message}"
|
|
111
|
+
|
|
112
|
+
# Access the raw response
|
|
113
|
+
puts "HTTP status: #{e.response.status}"
|
|
114
|
+
puts "Response body: #{e.response.body}"
|
|
115
|
+
|
|
116
|
+
# Get additional error data
|
|
117
|
+
if e.error_data.any?
|
|
118
|
+
puts "Error data: #{e.error_data.inspect}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Handle Common Errors
|
|
124
|
+
|
|
125
|
+
### AuthorizationError (401, 403)
|
|
126
|
+
|
|
127
|
+
Raised when authentication fails or you lack permissions.
|
|
128
|
+
|
|
129
|
+
**Common causes:**
|
|
130
|
+
- Invalid API token
|
|
131
|
+
- Token doesn't have required permissions
|
|
132
|
+
- Token has been revoked
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
begin
|
|
136
|
+
client = Monday::Client.new(token: ENV["MONDAY_TOKEN"])
|
|
137
|
+
response = client.account.query(select: ["id", "name"])
|
|
138
|
+
|
|
139
|
+
rescue Monday::AuthorizationError => e
|
|
140
|
+
puts "Authorization failed: #{e.message}"
|
|
141
|
+
puts "Please check your API token in the monday.com Developer Portal"
|
|
142
|
+
puts "Make sure the token has the required scopes"
|
|
143
|
+
|
|
144
|
+
# Log for debugging
|
|
145
|
+
logger.error("Monday.com auth error: #{e.message}")
|
|
146
|
+
|
|
147
|
+
# Return nil or default value
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Real error example:**
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"errors": ["Not Authenticated"],
|
|
156
|
+
"status": 401
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### InvalidRequestError (400)
|
|
161
|
+
|
|
162
|
+
Raised when request parameters are invalid.
|
|
163
|
+
|
|
164
|
+
**Common causes:**
|
|
165
|
+
- Invalid board/item/column IDs
|
|
166
|
+
- Malformed GraphQL query
|
|
167
|
+
- Invalid column values
|
|
168
|
+
- Missing required parameters
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
begin
|
|
172
|
+
response = client.column.change_simple_value(
|
|
173
|
+
args: {
|
|
174
|
+
board_id: 123, # Invalid board ID
|
|
175
|
+
item_id: 456,
|
|
176
|
+
column_id: "status",
|
|
177
|
+
value: "Working on it"
|
|
178
|
+
},
|
|
179
|
+
select: ["id", "name"]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
rescue Monday::InvalidRequestError => e
|
|
183
|
+
case e.error_data
|
|
184
|
+
when ->(data) { data["board_id"] }
|
|
185
|
+
puts "Board not found: #{e.error_data["board_id"]}"
|
|
186
|
+
puts "Please verify the board ID and try again"
|
|
187
|
+
|
|
188
|
+
when ->(data) { data["item_id"] }
|
|
189
|
+
puts "Item not found: #{e.error_data["item_id"]}"
|
|
190
|
+
|
|
191
|
+
when ->(data) { data["column_id"] }
|
|
192
|
+
puts "Invalid column: #{e.error_data["column_id"]}"
|
|
193
|
+
|
|
194
|
+
else
|
|
195
|
+
puts "Invalid request: #{e.message}"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Real error example:**
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"error_message": "The board does not exist. Please check your board ID and try again",
|
|
204
|
+
"error_code": "InvalidBoardIdException",
|
|
205
|
+
"error_data": {"board_id": 123},
|
|
206
|
+
"status_code": 200
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### ResourceNotFoundError (404)
|
|
211
|
+
|
|
212
|
+
Raised when a resource doesn't exist.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
def delete_folder_safely(client, folder_id)
|
|
216
|
+
begin
|
|
217
|
+
response = client.folder.delete(args: {folder_id: folder_id})
|
|
218
|
+
puts "Folder deleted successfully"
|
|
219
|
+
|
|
220
|
+
rescue Monday::ResourceNotFoundError => e
|
|
221
|
+
puts "Folder not found: #{e.error_data["folder_id"]}"
|
|
222
|
+
puts "It may have already been deleted"
|
|
223
|
+
# Don't raise - this is acceptable
|
|
224
|
+
|
|
225
|
+
rescue Monday::Error => e
|
|
226
|
+
puts "Failed to delete folder: #{e.message}"
|
|
227
|
+
raise # Re-raise for unexpected errors
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Real error example:**
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"error_message": "The folder does not exist. Please check your folder ID and try again",
|
|
236
|
+
"error_code": "InvalidFolderIdException",
|
|
237
|
+
"error_data": {"folder_id": 0},
|
|
238
|
+
"status_code": 200
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### RateLimitError (429)
|
|
243
|
+
|
|
244
|
+
Raised when you exceed monday.com's rate limits.
|
|
245
|
+
|
|
246
|
+
**Rate limits:**
|
|
247
|
+
- Queries: Complexity-based (max 10,000,000 per minute)
|
|
248
|
+
- Mutations: 60 requests per minute per user
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
def query_with_rate_limit_handling(client)
|
|
252
|
+
begin
|
|
253
|
+
response = client.board.query(
|
|
254
|
+
args: {limit: 100},
|
|
255
|
+
select: ["id", "name", "items"]
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
rescue Monday::RateLimitError => e
|
|
259
|
+
puts "Rate limit exceeded: #{e.message}"
|
|
260
|
+
|
|
261
|
+
# Wait before retrying
|
|
262
|
+
sleep 60
|
|
263
|
+
|
|
264
|
+
puts "Retrying after rate limit cooldown..."
|
|
265
|
+
retry
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### InternalServerError (500)
|
|
271
|
+
|
|
272
|
+
Raised when monday.com's servers encounter an error.
|
|
273
|
+
|
|
274
|
+
**Common causes:**
|
|
275
|
+
- monday.com service issues
|
|
276
|
+
- Invalid item/board ID causing server error
|
|
277
|
+
- Temporary API outages
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
def create_update_with_server_error_handling(client, item_id, body)
|
|
281
|
+
max_retries = 3
|
|
282
|
+
retry_count = 0
|
|
283
|
+
|
|
284
|
+
begin
|
|
285
|
+
response = client.update.create(
|
|
286
|
+
args: {item_id: item_id, body: body},
|
|
287
|
+
select: ["id", "body", "created_at"]
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
response.body["data"]["create_update"]
|
|
291
|
+
|
|
292
|
+
rescue Monday::InternalServerError => e
|
|
293
|
+
retry_count += 1
|
|
294
|
+
|
|
295
|
+
if retry_count < max_retries
|
|
296
|
+
puts "Server error (attempt #{retry_count}/#{max_retries}): #{e.message}"
|
|
297
|
+
sleep 2 ** retry_count # Exponential backoff
|
|
298
|
+
retry
|
|
299
|
+
else
|
|
300
|
+
puts "Server error persists after #{max_retries} attempts"
|
|
301
|
+
raise
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Real error example:**
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"status_code": 500,
|
|
311
|
+
"error_message": "Internal server error",
|
|
312
|
+
"error_code": "INTERNAL_SERVER_ERROR"
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Advanced Error Handling
|
|
317
|
+
|
|
318
|
+
### Retry Logic with Exponential Backoff
|
|
319
|
+
|
|
320
|
+
Implement smart retry logic for transient errors:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
class MondayRetryHandler
|
|
324
|
+
MAX_RETRIES = 3
|
|
325
|
+
BASE_DELAY = 1 # seconds
|
|
326
|
+
|
|
327
|
+
def self.with_retry(&block)
|
|
328
|
+
retry_count = 0
|
|
329
|
+
|
|
330
|
+
begin
|
|
331
|
+
yield
|
|
332
|
+
|
|
333
|
+
rescue Monday::RateLimitError => e
|
|
334
|
+
# Always wait for rate limits
|
|
335
|
+
puts "Rate limited. Waiting 60 seconds..."
|
|
336
|
+
sleep 60
|
|
337
|
+
retry
|
|
338
|
+
|
|
339
|
+
rescue Monday::InternalServerError, Monday::Error => e
|
|
340
|
+
retry_count += 1
|
|
341
|
+
|
|
342
|
+
if retry_count < MAX_RETRIES
|
|
343
|
+
delay = BASE_DELAY * (2 ** (retry_count - 1))
|
|
344
|
+
puts "Error: #{e.message}. Retrying in #{delay}s (attempt #{retry_count}/#{MAX_RETRIES})"
|
|
345
|
+
sleep delay
|
|
346
|
+
retry
|
|
347
|
+
else
|
|
348
|
+
puts "Failed after #{MAX_RETRIES} attempts"
|
|
349
|
+
raise
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Usage
|
|
356
|
+
MondayRetryHandler.with_retry do
|
|
357
|
+
client.item.create(
|
|
358
|
+
args: {board_id: 123, item_name: "New Task"},
|
|
359
|
+
select: ["id", "name"]
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Rescue with Fallbacks
|
|
365
|
+
|
|
366
|
+
Provide fallback values when errors occur:
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
def get_board_or_default(client, board_id)
|
|
370
|
+
response = client.board.query(
|
|
371
|
+
args: {ids: [board_id]},
|
|
372
|
+
select: ["id", "name", "description"]
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
response.body["data"]["boards"].first
|
|
376
|
+
|
|
377
|
+
rescue Monday::ResourceNotFoundError
|
|
378
|
+
# Return a default board structure
|
|
379
|
+
{
|
|
380
|
+
"id" => nil,
|
|
381
|
+
"name" => "Board not found",
|
|
382
|
+
"description" => ""
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
rescue Monday::AuthorizationError
|
|
386
|
+
# Return nil for auth errors
|
|
387
|
+
nil
|
|
388
|
+
|
|
389
|
+
rescue Monday::Error => e
|
|
390
|
+
# Log unexpected errors
|
|
391
|
+
puts "Unexpected error: #{e.message}"
|
|
392
|
+
nil
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Error Logging
|
|
397
|
+
|
|
398
|
+
Integrate with your logging system:
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
require 'logger'
|
|
402
|
+
|
|
403
|
+
class MondayClient
|
|
404
|
+
def initialize(token:, logger: Logger.new(STDOUT))
|
|
405
|
+
@client = Monday::Client.new(token: token)
|
|
406
|
+
@logger = logger
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def safe_query(resource, method, args)
|
|
410
|
+
response = @client.public_send(resource).public_send(method, **args)
|
|
411
|
+
|
|
412
|
+
if response.success?
|
|
413
|
+
@logger.info("monday.com API success: #{resource}.#{method}")
|
|
414
|
+
response.body["data"]
|
|
415
|
+
else
|
|
416
|
+
@logger.error("monday.com API error: #{response.body["error_message"]}")
|
|
417
|
+
nil
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
rescue Monday::AuthorizationError => e
|
|
421
|
+
@logger.error("monday.com auth error: #{e.message}")
|
|
422
|
+
raise
|
|
423
|
+
|
|
424
|
+
rescue Monday::RateLimitError => e
|
|
425
|
+
@logger.warn("monday.com rate limit: #{e.message}")
|
|
426
|
+
raise
|
|
427
|
+
|
|
428
|
+
rescue Monday::Error => e
|
|
429
|
+
@logger.error("monday.com error: #{e.class} - #{e.message}")
|
|
430
|
+
@logger.error("Error data: #{e.error_data.inspect}") if e.error_data.any?
|
|
431
|
+
raise
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Usage
|
|
436
|
+
client = MondayClient.new(
|
|
437
|
+
token: ENV["MONDAY_TOKEN"],
|
|
438
|
+
logger: Logger.new("monday.log")
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
client.safe_query(:board, :query, {args: {ids: [123]}, select: ["id", "name"]})
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Validation Before API Calls
|
|
445
|
+
|
|
446
|
+
Prevent errors by validating input:
|
|
447
|
+
|
|
448
|
+
```ruby
|
|
449
|
+
module MondayValidation
|
|
450
|
+
class ValidationError < StandardError; end
|
|
451
|
+
|
|
452
|
+
def self.validate_board_id!(board_id)
|
|
453
|
+
raise ValidationError, "board_id must be an integer" unless board_id.is_a?(Integer)
|
|
454
|
+
raise ValidationError, "board_id must be positive" unless board_id > 0
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def self.validate_item_name!(item_name)
|
|
458
|
+
raise ValidationError, "item_name cannot be empty" if item_name.to_s.strip.empty?
|
|
459
|
+
raise ValidationError, "item_name too long (max 255 chars)" if item_name.length > 255
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def self.validate_column_value!(column_id, value)
|
|
463
|
+
raise ValidationError, "column_id cannot be empty" if column_id.to_s.strip.empty?
|
|
464
|
+
raise ValidationError, "value cannot be nil" if value.nil?
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Usage
|
|
469
|
+
def create_item_safely(client, board_id, item_name)
|
|
470
|
+
# Validate before making API call
|
|
471
|
+
MondayValidation.validate_board_id!(board_id)
|
|
472
|
+
MondayValidation.validate_item_name!(item_name)
|
|
473
|
+
|
|
474
|
+
client.item.create(
|
|
475
|
+
args: {board_id: board_id, item_name: item_name},
|
|
476
|
+
select: ["id", "name"]
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
rescue MondayValidation::ValidationError => e
|
|
480
|
+
puts "Validation error: #{e.message}"
|
|
481
|
+
nil
|
|
482
|
+
|
|
483
|
+
rescue Monday::Error => e
|
|
484
|
+
puts "API error: #{e.message}"
|
|
485
|
+
nil
|
|
486
|
+
end
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Error Handling Patterns
|
|
490
|
+
|
|
491
|
+
### Safe Wrapper for API Calls
|
|
492
|
+
|
|
493
|
+
Create a reusable wrapper for consistent error handling:
|
|
494
|
+
|
|
495
|
+
```ruby
|
|
496
|
+
class SafeMondayClient
|
|
497
|
+
def initialize(client)
|
|
498
|
+
@client = client
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def safe_call(resource, method, args = {})
|
|
502
|
+
response = @client.public_send(resource).public_send(method, **args)
|
|
503
|
+
|
|
504
|
+
yield(response.body["data"]) if block_given? && response.success?
|
|
505
|
+
|
|
506
|
+
response.success? ? response.body["data"] : nil
|
|
507
|
+
|
|
508
|
+
rescue Monday::AuthorizationError => e
|
|
509
|
+
handle_auth_error(e)
|
|
510
|
+
nil
|
|
511
|
+
|
|
512
|
+
rescue Monday::ResourceNotFoundError => e
|
|
513
|
+
handle_not_found_error(e)
|
|
514
|
+
nil
|
|
515
|
+
|
|
516
|
+
rescue Monday::RateLimitError => e
|
|
517
|
+
handle_rate_limit_error(e)
|
|
518
|
+
sleep 60
|
|
519
|
+
retry
|
|
520
|
+
|
|
521
|
+
rescue Monday::Error => e
|
|
522
|
+
handle_generic_error(e)
|
|
523
|
+
nil
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
private
|
|
527
|
+
|
|
528
|
+
def handle_auth_error(error)
|
|
529
|
+
puts "Authentication failed. Please check your API token."
|
|
530
|
+
# Send alert, log to monitoring service, etc.
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def handle_not_found_error(error)
|
|
534
|
+
puts "Resource not found: #{error.message}"
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def handle_rate_limit_error(error)
|
|
538
|
+
puts "Rate limit exceeded. Retrying in 60 seconds..."
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def handle_generic_error(error)
|
|
542
|
+
puts "Error: #{error.message}"
|
|
543
|
+
# Log to error tracking service (Sentry, Rollbar, etc.)
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Usage
|
|
548
|
+
client = Monday::Client.new(token: ENV["MONDAY_TOKEN"])
|
|
549
|
+
safe_client = SafeMondayClient.new(client)
|
|
550
|
+
|
|
551
|
+
data = safe_client.safe_call(:board, :query, {
|
|
552
|
+
args: {ids: [123]},
|
|
553
|
+
select: ["id", "name"]
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
if data
|
|
557
|
+
boards = data["boards"]
|
|
558
|
+
puts "Found #{boards.length} boards"
|
|
559
|
+
else
|
|
560
|
+
puts "Failed to retrieve boards"
|
|
561
|
+
end
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Graceful Degradation
|
|
565
|
+
|
|
566
|
+
Continue operation even when some calls fail:
|
|
567
|
+
|
|
568
|
+
```ruby
|
|
569
|
+
def get_dashboard_data(client)
|
|
570
|
+
dashboard = {
|
|
571
|
+
boards: [],
|
|
572
|
+
workspaces: [],
|
|
573
|
+
account: nil
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
# Try to get boards
|
|
577
|
+
begin
|
|
578
|
+
response = client.board.query(args: {limit: 10}, select: ["id", "name"])
|
|
579
|
+
dashboard[:boards] = response.body["data"]["boards"] if response.success?
|
|
580
|
+
rescue Monday::Error => e
|
|
581
|
+
puts "Failed to load boards: #{e.message}"
|
|
582
|
+
# Continue anyway - boards will be empty array
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# Try to get workspaces
|
|
586
|
+
begin
|
|
587
|
+
response = client.workspace.query(select: ["id", "name"])
|
|
588
|
+
dashboard[:workspaces] = response.body["data"]["workspaces"] if response.success?
|
|
589
|
+
rescue Monday::Error => e
|
|
590
|
+
puts "Failed to load workspaces: #{e.message}"
|
|
591
|
+
# Continue anyway
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# Try to get account
|
|
595
|
+
begin
|
|
596
|
+
response = client.account.query(select: ["id", "name"])
|
|
597
|
+
dashboard[:account] = response.body["data"]["account"] if response.success?
|
|
598
|
+
rescue Monday::Error => e
|
|
599
|
+
puts "Failed to load account: #{e.message}"
|
|
600
|
+
# Continue anyway
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# Return partial data - better than nothing
|
|
604
|
+
dashboard
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Usage
|
|
608
|
+
dashboard = get_dashboard_data(client)
|
|
609
|
+
puts "Loaded #{dashboard[:boards].length} boards"
|
|
610
|
+
puts "Loaded #{dashboard[:workspaces].length} workspaces"
|
|
611
|
+
puts dashboard[:account] ? "Account: #{dashboard[:account]["name"]}" : "Account unavailable"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### User-Friendly Error Messages
|
|
615
|
+
|
|
616
|
+
Convert technical errors to user-friendly messages:
|
|
617
|
+
|
|
618
|
+
```ruby
|
|
619
|
+
module MondayErrorMessages
|
|
620
|
+
def self.humanize(error)
|
|
621
|
+
case error
|
|
622
|
+
when Monday::AuthorizationError
|
|
623
|
+
"Unable to connect to monday.com. Please check your access token."
|
|
624
|
+
|
|
625
|
+
when Monday::ResourceNotFoundError
|
|
626
|
+
if error.error_data["board_id"]
|
|
627
|
+
"The board you're looking for doesn't exist or has been deleted."
|
|
628
|
+
elsif error.error_data["item_id"]
|
|
629
|
+
"The item you're looking for doesn't exist or has been deleted."
|
|
630
|
+
elsif error.error_data["folder_id"]
|
|
631
|
+
"The folder you're looking for doesn't exist or has been deleted."
|
|
632
|
+
else
|
|
633
|
+
"The resource you're looking for could not be found."
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
when Monday::RateLimitError
|
|
637
|
+
"You're making too many requests. Please wait a minute and try again."
|
|
638
|
+
|
|
639
|
+
when Monday::InternalServerError
|
|
640
|
+
"monday.com is experiencing technical difficulties. Please try again later."
|
|
641
|
+
|
|
642
|
+
when Monday::InvalidRequestError
|
|
643
|
+
case error.message
|
|
644
|
+
when /InvalidBoardIdException/
|
|
645
|
+
"Invalid board. Please check the board ID and try again."
|
|
646
|
+
when /InvalidItemIdException/
|
|
647
|
+
"Invalid item. Please check the item ID and try again."
|
|
648
|
+
when /InvalidColumnIdException/
|
|
649
|
+
"Invalid column. Please check the column ID and try again."
|
|
650
|
+
when /ColumnValueException/
|
|
651
|
+
"Invalid column value. Please check the format and try again."
|
|
652
|
+
else
|
|
653
|
+
"Invalid request. Please check your input and try again."
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
else
|
|
657
|
+
"An unexpected error occurred. Please try again or contact support."
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Usage in a web application
|
|
663
|
+
begin
|
|
664
|
+
response = client.item.create(
|
|
665
|
+
args: {board_id: params[:board_id], item_name: params[:item_name]},
|
|
666
|
+
select: ["id", "name"]
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
flash[:success] = "Item created successfully!"
|
|
670
|
+
redirect_to board_path(params[:board_id])
|
|
671
|
+
|
|
672
|
+
rescue Monday::Error => e
|
|
673
|
+
flash[:error] = MondayErrorMessages.humanize(e)
|
|
674
|
+
redirect_to :back
|
|
675
|
+
end
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
## GraphQL Errors
|
|
679
|
+
|
|
680
|
+
### Access GraphQL Error Details
|
|
681
|
+
|
|
682
|
+
monday.com returns GraphQL errors with detailed information:
|
|
683
|
+
|
|
684
|
+
```ruby
|
|
685
|
+
begin
|
|
686
|
+
response = client.account.query(select: ["id", "invalid_field"])
|
|
687
|
+
rescue Monday::Error => e
|
|
688
|
+
# Access the full error array
|
|
689
|
+
if e.response.body["errors"]
|
|
690
|
+
e.response.body["errors"].each do |error|
|
|
691
|
+
puts "Error: #{error["message"]}"
|
|
692
|
+
puts "Location: line #{error["locations"]&.first&.dig("line")}"
|
|
693
|
+
puts "Path: #{error["path"]&.join(" > ")}"
|
|
694
|
+
puts "Code: #{error.dig("extensions", "code")}"
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Real GraphQL error structure:**
|
|
701
|
+
```json
|
|
702
|
+
{
|
|
703
|
+
"errors": [
|
|
704
|
+
{
|
|
705
|
+
"message": "Field 'invalid_field' doesn't exist on type 'Account'",
|
|
706
|
+
"locations": [{"line": 1, "column": 10}],
|
|
707
|
+
"path": ["account"],
|
|
708
|
+
"extensions": {
|
|
709
|
+
"code": "undefinedField",
|
|
710
|
+
"typeName": "Account",
|
|
711
|
+
"fieldName": "invalid_field"
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Parse GraphQL Error Messages
|
|
719
|
+
|
|
720
|
+
Extract meaningful information from GraphQL errors:
|
|
721
|
+
|
|
722
|
+
```ruby
|
|
723
|
+
def parse_graphql_errors(response_body)
|
|
724
|
+
return [] unless response_body["errors"]
|
|
725
|
+
|
|
726
|
+
response_body["errors"].map do |error|
|
|
727
|
+
{
|
|
728
|
+
message: error["message"],
|
|
729
|
+
field: error.dig("extensions", "fieldName"),
|
|
730
|
+
type: error.dig("extensions", "typeName"),
|
|
731
|
+
code: error.dig("extensions", "code"),
|
|
732
|
+
path: error["path"]&.join("."),
|
|
733
|
+
line: error.dig("locations", 0, "line")
|
|
734
|
+
}
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# Usage
|
|
739
|
+
begin
|
|
740
|
+
response = client.board.query(
|
|
741
|
+
args: {ids: [123]},
|
|
742
|
+
select: ["id", "invalid_field"]
|
|
743
|
+
)
|
|
744
|
+
rescue Monday::Error => e
|
|
745
|
+
errors = parse_graphql_errors(e.response.body)
|
|
746
|
+
|
|
747
|
+
errors.each do |error|
|
|
748
|
+
puts "GraphQL Error:"
|
|
749
|
+
puts " Message: #{error[:message]}"
|
|
750
|
+
puts " Field: #{error[:field]}" if error[:field]
|
|
751
|
+
puts " Type: #{error[:type]}" if error[:type]
|
|
752
|
+
puts " Code: #{error[:code]}" if error[:code]
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### Handle Field-Specific Errors
|
|
758
|
+
|
|
759
|
+
Catch errors for specific fields:
|
|
760
|
+
|
|
761
|
+
```ruby
|
|
762
|
+
def query_board_with_fallback_fields(client, board_id)
|
|
763
|
+
# Try querying with all desired fields
|
|
764
|
+
fields = ["id", "name", "description", "items_count", "board_kind"]
|
|
765
|
+
|
|
766
|
+
begin
|
|
767
|
+
response = client.board.query(
|
|
768
|
+
args: {ids: [board_id]},
|
|
769
|
+
select: fields
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
return response.body["data"]["boards"].first if response.success?
|
|
773
|
+
|
|
774
|
+
rescue Monday::Error => e
|
|
775
|
+
if e.response.body["errors"]
|
|
776
|
+
# Find which fields are invalid
|
|
777
|
+
invalid_fields = e.response.body["errors"].map do |error|
|
|
778
|
+
error.dig("extensions", "fieldName")
|
|
779
|
+
end.compact
|
|
780
|
+
|
|
781
|
+
# Retry with only valid fields
|
|
782
|
+
valid_fields = fields - invalid_fields
|
|
783
|
+
|
|
784
|
+
if valid_fields.any?
|
|
785
|
+
puts "Retrying with valid fields: #{valid_fields.join(", ")}"
|
|
786
|
+
|
|
787
|
+
response = client.board.query(
|
|
788
|
+
args: {ids: [board_id]},
|
|
789
|
+
select: valid_fields
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
return response.body["data"]["boards"].first if response.success?
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
raise # Re-raise if we can't recover
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
## Best Practices
|
|
802
|
+
|
|
803
|
+
1. **Always handle errors**: Never assume API calls will succeed
|
|
804
|
+
2. **Be specific**: Rescue specific error classes rather than catching all errors
|
|
805
|
+
3. **Validate input**: Check parameters before making API calls
|
|
806
|
+
4. **Log errors**: Keep track of errors for debugging and monitoring
|
|
807
|
+
5. **Retry wisely**: Implement exponential backoff for transient errors
|
|
808
|
+
6. **Fail gracefully**: Provide fallback values or partial data when possible
|
|
809
|
+
7. **User-friendly messages**: Convert technical errors to readable messages
|
|
810
|
+
8. **Monitor rate limits**: Track your API usage to avoid rate limit errors
|
|
811
|
+
9. **Check error_data**: Use the error_data hash for context-specific handling
|
|
812
|
+
10. **Test error paths**: Write tests for your error handling code
|
|
813
|
+
|
|
814
|
+
## Related Resources
|
|
815
|
+
|
|
816
|
+
- [monday.com API Rate Limits](https://developer.monday.com/api-reference/docs/rate-limits)
|
|
817
|
+
- [GraphQL Error Handling](https://developer.monday.com/api-reference/docs/errors)
|
|
818
|
+
- [Authentication Guide](../authentication.md)
|