monday_ruby 1.1.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.rubocop.yml +2 -1
  4. data/CHANGELOG.md +14 -0
  5. data/CONTRIBUTING.md +104 -0
  6. data/README.md +146 -142
  7. data/docs/.vitepress/config.mjs +255 -0
  8. data/docs/.vitepress/theme/index.js +4 -0
  9. data/docs/.vitepress/theme/style.css +43 -0
  10. data/docs/README.md +80 -0
  11. data/docs/explanation/architecture.md +507 -0
  12. data/docs/explanation/best-practices/errors.md +478 -0
  13. data/docs/explanation/best-practices/performance.md +1084 -0
  14. data/docs/explanation/best-practices/rate-limiting.md +630 -0
  15. data/docs/explanation/best-practices/testing.md +820 -0
  16. data/docs/explanation/column-values.md +857 -0
  17. data/docs/explanation/design.md +795 -0
  18. data/docs/explanation/graphql.md +356 -0
  19. data/docs/explanation/migration/v1.md +808 -0
  20. data/docs/explanation/pagination.md +447 -0
  21. data/docs/guides/advanced/batch.md +1274 -0
  22. data/docs/guides/advanced/complex-queries.md +1114 -0
  23. data/docs/guides/advanced/errors.md +818 -0
  24. data/docs/guides/advanced/pagination.md +934 -0
  25. data/docs/guides/advanced/rate-limiting.md +981 -0
  26. data/docs/guides/authentication.md +286 -0
  27. data/docs/guides/boards/create.md +386 -0
  28. data/docs/guides/boards/delete.md +405 -0
  29. data/docs/guides/boards/duplicate.md +511 -0
  30. data/docs/guides/boards/query.md +530 -0
  31. data/docs/guides/boards/update.md +453 -0
  32. data/docs/guides/columns/create.md +452 -0
  33. data/docs/guides/columns/metadata.md +492 -0
  34. data/docs/guides/columns/query.md +455 -0
  35. data/docs/guides/columns/update-multiple.md +459 -0
  36. data/docs/guides/columns/update-values.md +509 -0
  37. data/docs/guides/files/add-to-column.md +40 -0
  38. data/docs/guides/files/add-to-update.md +37 -0
  39. data/docs/guides/files/clear-column.md +33 -0
  40. data/docs/guides/first-request.md +285 -0
  41. data/docs/guides/folders/manage.md +750 -0
  42. data/docs/guides/groups/items.md +626 -0
  43. data/docs/guides/groups/manage.md +501 -0
  44. data/docs/guides/installation.md +169 -0
  45. data/docs/guides/items/create.md +493 -0
  46. data/docs/guides/items/delete.md +514 -0
  47. data/docs/guides/items/query.md +605 -0
  48. data/docs/guides/items/subitems.md +483 -0
  49. data/docs/guides/items/update.md +699 -0
  50. data/docs/guides/updates/manage.md +619 -0
  51. data/docs/guides/use-cases/dashboard.md +1421 -0
  52. data/docs/guides/use-cases/import.md +1962 -0
  53. data/docs/guides/use-cases/task-management.md +1381 -0
  54. data/docs/guides/workspaces/manage.md +502 -0
  55. data/docs/index.md +69 -0
  56. data/docs/package-lock.json +2468 -0
  57. data/docs/package.json +13 -0
  58. data/docs/reference/client.md +540 -0
  59. data/docs/reference/configuration.md +586 -0
  60. data/docs/reference/errors.md +693 -0
  61. data/docs/reference/resources/account.md +208 -0
  62. data/docs/reference/resources/activity-log.md +369 -0
  63. data/docs/reference/resources/board-view.md +359 -0
  64. data/docs/reference/resources/board.md +393 -0
  65. data/docs/reference/resources/column.md +543 -0
  66. data/docs/reference/resources/file.md +236 -0
  67. data/docs/reference/resources/folder.md +386 -0
  68. data/docs/reference/resources/group.md +507 -0
  69. data/docs/reference/resources/item.md +348 -0
  70. data/docs/reference/resources/subitem.md +267 -0
  71. data/docs/reference/resources/update.md +259 -0
  72. data/docs/reference/resources/workspace.md +213 -0
  73. data/docs/reference/response.md +560 -0
  74. data/docs/tutorial/first-integration.md +713 -0
  75. data/lib/monday/client.rb +24 -0
  76. data/lib/monday/configuration.rb +5 -0
  77. data/lib/monday/request.rb +15 -0
  78. data/lib/monday/resources/base.rb +4 -0
  79. data/lib/monday/resources/file.rb +56 -0
  80. data/lib/monday/util.rb +1 -0
  81. data/lib/monday/version.rb +1 -1
  82. metadata +87 -4
@@ -0,0 +1,1381 @@
1
+ # Task Management System
2
+
3
+ Build a complete task management system using the monday_ruby gem. This guide demonstrates real-world usage patterns by creating a production-ready task tracker with proper structure, team assignments, status updates, and reporting.
4
+
5
+ ## What You'll Build
6
+
7
+ A complete task management system that:
8
+ - Creates task boards with proper column structure
9
+ - Manages tasks across different project phases
10
+ - Assigns tasks to team members
11
+ - Tracks status and priority
12
+ - Adds comments and updates
13
+ - Generates completion reports
14
+
15
+ ## Prerequisites
16
+
17
+ ```ruby
18
+ # Gemfile
19
+ gem "monday_ruby"
20
+ gem "dotenv"
21
+
22
+ # .env file
23
+ MONDAY_TOKEN=your_api_token_here
24
+ ```
25
+
26
+ Install dependencies:
27
+ ```bash
28
+ bundle install
29
+ ```
30
+
31
+ ## Complete Working Example
32
+
33
+ Here's a full task management system implementation. Copy and run this code:
34
+
35
+ ```ruby
36
+ #!/usr/bin/env ruby
37
+ # task_manager.rb
38
+
39
+ require "monday_ruby"
40
+ require "dotenv/load"
41
+ require "json"
42
+ require "date"
43
+
44
+ # Configure the client
45
+ Monday.configure do |config|
46
+ config.token = ENV["MONDAY_TOKEN"]
47
+ end
48
+
49
+ class TaskManager
50
+ attr_reader :client, :board_id
51
+
52
+ def initialize
53
+ @client = Monday::Client.new
54
+ @board_id = nil
55
+ end
56
+
57
+ # Step 1: Setup - Create a task board with proper structure
58
+ def setup_task_board
59
+ puts "\n" + "=" * 70
60
+ puts "STEP 1: Setting Up Task Board"
61
+ puts "=" * 70
62
+
63
+ # Create the board
64
+ response = client.board.create(
65
+ args: {
66
+ board_name: "Project Tasks - #{Date.today}",
67
+ board_kind: :public,
68
+ description: "Task management board for team collaboration"
69
+ },
70
+ select: ["id", "name", "description"]
71
+ )
72
+
73
+ unless response.success?
74
+ handle_error("Failed to create board", response)
75
+ return false
76
+ end
77
+
78
+ board = response.body.dig("data", "create_board")
79
+ @board_id = board["id"].to_i
80
+
81
+ puts "✓ Created board: #{board['name']}"
82
+ puts " ID: #{@board_id}"
83
+ puts " Description: #{board['description']}"
84
+
85
+ # Create task management columns
86
+ create_task_columns
87
+
88
+ # Create groups for project phases
89
+ create_project_groups
90
+
91
+ true
92
+ end
93
+
94
+ # Create columns for task management
95
+ def create_task_columns
96
+ puts "\nCreating task management columns..."
97
+
98
+ columns = [
99
+ {
100
+ title: "Status",
101
+ column_type: :status,
102
+ description: "Task status"
103
+ },
104
+ {
105
+ title: "Owner",
106
+ column_type: :people,
107
+ description: "Task assignee"
108
+ },
109
+ {
110
+ title: "Due Date",
111
+ column_type: :date,
112
+ description: "Task deadline"
113
+ },
114
+ {
115
+ title: "Priority",
116
+ column_type: :status,
117
+ description: "Task priority level"
118
+ },
119
+ {
120
+ title: "Notes",
121
+ column_type: :text,
122
+ description: "Additional notes"
123
+ }
124
+ ]
125
+
126
+ columns.each do |col|
127
+ response = client.column.create(
128
+ args: {
129
+ board_id: @board_id,
130
+ title: col[:title],
131
+ column_type: col[:column_type],
132
+ description: col[:description]
133
+ },
134
+ select: ["id", "title", "type"]
135
+ )
136
+
137
+ if response.success?
138
+ column = response.body.dig("data", "create_column")
139
+ puts " ✓ Created column: #{column['title']} (#{column['type']})"
140
+ else
141
+ puts " ✗ Failed to create column: #{col[:title]}"
142
+ end
143
+
144
+ # Rate limiting: avoid hitting API limits
145
+ sleep(0.3)
146
+ end
147
+ end
148
+
149
+ # Create groups for different project phases
150
+ def create_project_groups
151
+ puts "\nCreating project phase groups..."
152
+
153
+ groups = [
154
+ { name: "Planning", color: "#579bfc" },
155
+ { name: "In Progress", color: "#fdab3d" },
156
+ { name: "Review", color: "#a25ddc" },
157
+ { name: "Completed", color: "#00c875" }
158
+ ]
159
+
160
+ groups.each do |group_data|
161
+ response = client.group.create(
162
+ args: {
163
+ board_id: @board_id,
164
+ group_name: group_data[:name]
165
+ },
166
+ select: ["id", "title"]
167
+ )
168
+
169
+ if response.success?
170
+ group = response.body.dig("data", "create_group")
171
+ puts " ✓ Created group: #{group['title']}"
172
+ end
173
+
174
+ sleep(0.3)
175
+ end
176
+ end
177
+
178
+ # Step 2: Get board structure (column IDs)
179
+ def get_board_structure
180
+ puts "\n" + "=" * 70
181
+ puts "STEP 2: Retrieving Board Structure"
182
+ puts "=" * 70
183
+
184
+ response = client.board.query(
185
+ args: { ids: [@board_id] },
186
+ select: [
187
+ "id",
188
+ "name",
189
+ {
190
+ columns: ["id", "title", "type"],
191
+ groups: ["id", "title"]
192
+ }
193
+ ]
194
+ )
195
+
196
+ unless response.success?
197
+ handle_error("Failed to get board structure", response)
198
+ return nil
199
+ end
200
+
201
+ board = response.body.dig("data", "boards", 0)
202
+
203
+ puts "\nBoard: #{board['name']}"
204
+ puts "\nColumns:"
205
+ board["columns"].each do |col|
206
+ puts " • #{col['title']}: '#{col['id']}' (#{col['type']})"
207
+ end
208
+
209
+ puts "\nGroups:"
210
+ board["groups"].each do |group|
211
+ puts " • #{group['title']}: '#{group['id']}'"
212
+ end
213
+
214
+ board
215
+ end
216
+
217
+ # Step 3: Create tasks with proper fields
218
+ def create_tasks(board_structure)
219
+ puts "\n" + "=" * 70
220
+ puts "STEP 3: Creating Tasks"
221
+ puts "=" * 70
222
+
223
+ # Extract column IDs (these will be dynamic based on board)
224
+ columns = board_structure["columns"].each_with_object({}) do |col, hash|
225
+ hash[col["title"].downcase.gsub(" ", "_")] = col["id"]
226
+ end
227
+
228
+ # Get the first group (Planning)
229
+ planning_group = board_structure["groups"].find { |g| g["title"] == "Planning" }
230
+
231
+ tasks = [
232
+ {
233
+ name: "Define project requirements",
234
+ status: "Working on it",
235
+ priority: "High",
236
+ due_date: (Date.today + 7).to_s,
237
+ notes: "Gather stakeholder requirements and create specification"
238
+ },
239
+ {
240
+ name: "Design database schema",
241
+ status: "Not Started",
242
+ priority: "High",
243
+ due_date: (Date.today + 10).to_s,
244
+ notes: "Create ERD and define table structures"
245
+ },
246
+ {
247
+ name: "Setup development environment",
248
+ status: "Done",
249
+ priority: "Medium",
250
+ due_date: Date.today.to_s,
251
+ notes: "Configure local environment and CI/CD pipeline"
252
+ },
253
+ {
254
+ name: "Write API documentation",
255
+ status: "Not Started",
256
+ priority: "Medium",
257
+ due_date: (Date.today + 14).to_s,
258
+ notes: "Document all API endpoints and authentication"
259
+ }
260
+ ]
261
+
262
+ created_items = []
263
+
264
+ tasks.each do |task|
265
+ # Build column values
266
+ column_values = {}
267
+
268
+ # Status column
269
+ if columns["status"]
270
+ column_values[columns["status"]] = { label: task[:status] }
271
+ end
272
+
273
+ # Priority column
274
+ if columns["priority"]
275
+ column_values[columns["priority"]] = { label: task[:priority] }
276
+ end
277
+
278
+ # Due date column
279
+ if columns["due_date"]
280
+ column_values[columns["due_date"]] = { date: task[:due_date] }
281
+ end
282
+
283
+ # Notes column
284
+ if columns["notes"]
285
+ column_values[columns["notes"]] = task[:notes]
286
+ end
287
+
288
+ response = client.item.create(
289
+ args: {
290
+ board_id: @board_id,
291
+ group_id: planning_group["id"],
292
+ item_name: task[:name],
293
+ column_values: column_values,
294
+ create_labels_if_missing: true
295
+ },
296
+ select: [
297
+ "id",
298
+ "name",
299
+ {
300
+ group: ["id", "title"],
301
+ column_values: ["id", "text"]
302
+ }
303
+ ]
304
+ )
305
+
306
+ if response.success?
307
+ item = response.body.dig("data", "create_item")
308
+ created_items << item
309
+ puts "✓ Created task: #{item['name']}"
310
+ puts " ID: #{item['id']}"
311
+ puts " Group: #{item.dig('group', 'title')}"
312
+ else
313
+ puts "✗ Failed to create task: #{task[:name]}"
314
+ handle_error("", response)
315
+ end
316
+
317
+ sleep(0.5)
318
+ end
319
+
320
+ created_items
321
+ end
322
+
323
+ # Step 4: Assign tasks to team members
324
+ def assign_tasks(items, team_member_id)
325
+ puts "\n" + "=" * 70
326
+ puts "STEP 4: Assigning Tasks to Team Members"
327
+ puts "=" * 70
328
+
329
+ unless team_member_id
330
+ puts "⚠ No team member ID provided. Skipping assignments."
331
+ puts " To assign tasks, provide a user ID from your workspace."
332
+ return
333
+ end
334
+
335
+ # Get the owner column ID first
336
+ response = client.board.query(
337
+ args: { ids: [@board_id] },
338
+ select: [
339
+ {
340
+ columns: ["id", "title", "type"]
341
+ }
342
+ ]
343
+ )
344
+
345
+ return unless response.success?
346
+
347
+ board = response.body.dig("data", "boards", 0)
348
+ owner_column = board["columns"].find { |c| c["title"] == "Owner" }
349
+
350
+ unless owner_column
351
+ puts "⚠ Owner column not found"
352
+ return
353
+ end
354
+
355
+ # Assign first two tasks
356
+ items.first(2).each do |item|
357
+ response = client.column.change_value(
358
+ args: {
359
+ board_id: @board_id,
360
+ item_id: item["id"].to_i,
361
+ column_id: owner_column["id"],
362
+ value: {
363
+ personsAndTeams: [
364
+ { id: team_member_id.to_i, kind: "person" }
365
+ ]
366
+ }
367
+ },
368
+ select: ["id", "name"]
369
+ )
370
+
371
+ if response.success?
372
+ updated_item = response.body.dig("data", "change_column_value")
373
+ puts "✓ Assigned task to team member: #{updated_item['name']}"
374
+ else
375
+ puts "✗ Failed to assign: #{item['name']}"
376
+ end
377
+
378
+ sleep(0.5)
379
+ end
380
+ end
381
+
382
+ # Step 5: Update task status
383
+ def update_task_status(items)
384
+ puts "\n" + "=" * 70
385
+ puts "STEP 5: Updating Task Status"
386
+ puts "=" * 70
387
+
388
+ # Get column structure
389
+ response = client.board.query(
390
+ args: { ids: [@board_id] },
391
+ select: [
392
+ {
393
+ columns: ["id", "title", "type"],
394
+ groups: ["id", "title"]
395
+ }
396
+ ]
397
+ )
398
+
399
+ return unless response.success?
400
+
401
+ board = response.body.dig("data", "boards", 0)
402
+ status_column = board["columns"].find { |c| c["title"] == "Status" }
403
+ in_progress_group = board["groups"].find { |g| g["title"] == "In Progress" }
404
+
405
+ # Move first task to "In Progress" and update status
406
+ if items.any? && status_column && in_progress_group
407
+ task = items.first
408
+
409
+ # Update status and move to group using change_multiple_values
410
+ response = client.column.change_multiple_values(
411
+ args: {
412
+ board_id: @board_id,
413
+ item_id: task["id"].to_i,
414
+ column_values: {
415
+ status_column["id"] => { label: "Working on it" }
416
+ }
417
+ },
418
+ select: ["id", "name"]
419
+ )
420
+
421
+ if response.success?
422
+ puts "✓ Updated status to 'Working on it': #{task['name']}"
423
+
424
+ # Move to In Progress group
425
+ move_response = client.group.move_item(
426
+ args: {
427
+ item_id: task["id"].to_i,
428
+ group_id: in_progress_group["id"]
429
+ },
430
+ select: ["id"]
431
+ )
432
+
433
+ if move_response.success?
434
+ puts "✓ Moved to 'In Progress' group"
435
+ end
436
+ end
437
+
438
+ sleep(0.5)
439
+
440
+ # Complete the third task
441
+ if items.length > 2
442
+ completed_task = items[2]
443
+ completed_group = board["groups"].find { |g| g["title"] == "Completed" }
444
+
445
+ response = client.column.change_multiple_values(
446
+ args: {
447
+ board_id: @board_id,
448
+ item_id: completed_task["id"].to_i,
449
+ column_values: {
450
+ status_column["id"] => { label: "Done" }
451
+ }
452
+ },
453
+ select: ["id", "name"]
454
+ )
455
+
456
+ if response.success?
457
+ puts "✓ Marked as complete: #{completed_task['name']}"
458
+
459
+ if completed_group
460
+ client.group.move_item(
461
+ args: {
462
+ item_id: completed_task["id"].to_i,
463
+ group_id: completed_group["id"]
464
+ }
465
+ )
466
+ puts "✓ Moved to 'Completed' group"
467
+ end
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ # Step 6: Add comments and updates to tasks
474
+ def add_task_updates(items)
475
+ puts "\n" + "=" * 70
476
+ puts "STEP 6: Adding Comments and Updates"
477
+ puts "=" * 70
478
+
479
+ updates = [
480
+ {
481
+ item: items.first,
482
+ text: "Started gathering requirements from stakeholders. Initial meeting scheduled for tomorrow."
483
+ },
484
+ {
485
+ item: items[1],
486
+ text: "Reviewed existing schema. Planning to add new tables for user preferences and audit logs."
487
+ }
488
+ ]
489
+
490
+ updates.each do |update_data|
491
+ next unless update_data[:item]
492
+
493
+ response = client.update.create(
494
+ args: {
495
+ item_id: update_data[:item]["id"].to_i,
496
+ body: update_data[:text]
497
+ },
498
+ select: ["id", "body", "created_at"]
499
+ )
500
+
501
+ if response.success?
502
+ update = response.body.dig("data", "create_update")
503
+ puts "✓ Added update to: #{update_data[:item]['name']}"
504
+ puts " Comment: #{update['body'][0..60]}..."
505
+ else
506
+ puts "✗ Failed to add update"
507
+ end
508
+
509
+ sleep(0.5)
510
+ end
511
+ end
512
+
513
+ # Step 7: Query and filter tasks
514
+ def query_tasks
515
+ puts "\n" + "=" * 70
516
+ puts "STEP 7: Querying and Filtering Tasks"
517
+ puts "=" * 70
518
+
519
+ # Get all tasks with full details
520
+ response = client.board.items_page(
521
+ board_ids: @board_id,
522
+ limit: 100,
523
+ select: [
524
+ "id",
525
+ "name",
526
+ "state",
527
+ "created_at",
528
+ {
529
+ group: ["id", "title"],
530
+ column_values: ["id", "text", "type"],
531
+ updates: ["id", "body", "created_at"]
532
+ }
533
+ ]
534
+ )
535
+
536
+ unless response.success?
537
+ handle_error("Failed to query tasks", response)
538
+ return
539
+ end
540
+
541
+ board = response.body.dig("data", "boards", 0)
542
+ items = board.dig("items_page", "items") || []
543
+
544
+ puts "\nAll Tasks (#{items.length}):"
545
+ items.each do |item|
546
+ group = item.dig("group", "title")
547
+ status = item["column_values"].find { |cv| cv["id"].include?("status") }
548
+ due_date = item["column_values"].find { |cv| cv["type"] == "date" }
549
+ updates_count = item["updates"]&.length || 0
550
+
551
+ puts "\n #{item['name']}"
552
+ puts " Group: #{group}"
553
+ puts " Status: #{status&.dig('text') || 'N/A'}"
554
+ puts " Due Date: #{due_date&.dig('text') || 'N/A'}"
555
+ puts " Updates: #{updates_count}"
556
+ end
557
+
558
+ items
559
+ end
560
+
561
+ # Step 8: Find overdue tasks
562
+ def find_overdue_tasks(items)
563
+ puts "\n" + "=" * 70
564
+ puts "STEP 8: Finding Overdue Tasks"
565
+ puts "=" * 70
566
+
567
+ overdue = []
568
+
569
+ items.each do |item|
570
+ date_column = item["column_values"].find { |cv| cv["type"] == "date" }
571
+ next unless date_column && date_column["text"]
572
+
573
+ # Parse date from text (format: "2024-12-31")
574
+ begin
575
+ due_date_text = date_column["text"]
576
+ # Handle both date and datetime formats
577
+ due_date = Date.parse(due_date_text.split(" ").first)
578
+
579
+ if due_date < Date.today
580
+ overdue << {
581
+ item: item,
582
+ due_date: due_date,
583
+ days_overdue: (Date.today - due_date).to_i
584
+ }
585
+ end
586
+ rescue Date::Error
587
+ # Skip invalid dates
588
+ next
589
+ end
590
+ end
591
+
592
+ if overdue.any?
593
+ puts "\nOverdue Tasks (#{overdue.length}):"
594
+ overdue.sort_by { |t| t[:days_overdue] }.reverse.each do |task|
595
+ puts "\n ⚠ #{task[:item]['name']}"
596
+ puts " Due: #{task[:due_date]}"
597
+ puts " Overdue by: #{task[:days_overdue]} day(s)"
598
+ end
599
+ else
600
+ puts "\n✓ No overdue tasks!"
601
+ end
602
+
603
+ overdue
604
+ end
605
+
606
+ # Step 9: Generate completion report
607
+ def generate_report(items)
608
+ puts "\n" + "=" * 70
609
+ puts "STEP 9: Generating Task Completion Report"
610
+ puts "=" * 70
611
+
612
+ # Group by status
613
+ by_status = items.group_by do |item|
614
+ status_col = item["column_values"].find { |cv| cv["id"].include?("status") }
615
+ status_col&.dig("text") || "No Status"
616
+ end
617
+
618
+ # Group by priority
619
+ by_priority = items.group_by do |item|
620
+ # Find the second status column (Priority)
621
+ priority_col = item["column_values"].select { |cv| cv["type"] == "color" }[1]
622
+ priority_col&.dig("text") || "No Priority"
623
+ end
624
+
625
+ # Group by group (project phase)
626
+ by_group = items.group_by do |item|
627
+ item.dig("group", "title") || "Unknown"
628
+ end
629
+
630
+ total = items.length
631
+ completed = by_status["Done"]&.length || 0
632
+ completion_rate = total > 0 ? (completed.to_f / total * 100).round(1) : 0
633
+
634
+ puts "\nTask Summary:"
635
+ puts " Total Tasks: #{total}"
636
+ puts " Completed: #{completed}"
637
+ puts " Completion Rate: #{completion_rate}%"
638
+
639
+ puts "\nBy Status:"
640
+ by_status.sort_by { |status, _| status }.each do |status, tasks|
641
+ puts " #{status}: #{tasks.length}"
642
+ end
643
+
644
+ puts "\nBy Priority:"
645
+ by_priority.sort_by { |priority, _| priority }.each do |priority, tasks|
646
+ puts " #{priority}: #{tasks.length}"
647
+ end
648
+
649
+ puts "\nBy Project Phase:"
650
+ by_group.sort_by { |group, _| group }.each do |group, tasks|
651
+ puts " #{group}: #{tasks.length}"
652
+ end
653
+
654
+ {
655
+ total: total,
656
+ completed: completed,
657
+ completion_rate: completion_rate,
658
+ by_status: by_status,
659
+ by_priority: by_priority,
660
+ by_group: by_group
661
+ }
662
+ end
663
+
664
+ # Step 10: Cleanup (optional)
665
+ def cleanup
666
+ puts "\n" + "=" * 70
667
+ puts "STEP 10: Cleanup"
668
+ puts "=" * 70
669
+
670
+ print "\nDelete the task board? (y/N): "
671
+ answer = gets.chomp.downcase
672
+
673
+ if answer == "y"
674
+ response = client.board.delete(@board_id)
675
+
676
+ if response.success?
677
+ puts "✓ Board deleted successfully"
678
+ else
679
+ puts "✗ Failed to delete board"
680
+ end
681
+ else
682
+ puts "Board kept. ID: #{@board_id}"
683
+ puts "View at: https://monday.com/boards/#{@board_id}"
684
+ end
685
+ end
686
+
687
+ # Error handling helper
688
+ def handle_error(message, response)
689
+ puts "✗ #{message}"
690
+ puts " Status: #{response.status}"
691
+
692
+ if response.body["errors"]
693
+ response.body["errors"].each do |error|
694
+ puts " Error: #{error['message']}"
695
+ end
696
+ elsif response.body["error_message"]
697
+ puts " Error: #{response.body['error_message']}"
698
+ end
699
+ end
700
+ end
701
+
702
+ # Main execution
703
+ def main
704
+ puts "\n" + "🚀 TASK MANAGEMENT SYSTEM 🚀".center(70)
705
+
706
+ manager = TaskManager.new
707
+
708
+ # Step 1: Setup board
709
+ return unless manager.setup_task_board
710
+
711
+ # Step 2: Get board structure
712
+ board_structure = manager.get_board_structure
713
+ return unless board_structure
714
+
715
+ # Step 3: Create tasks
716
+ items = manager.create_tasks(board_structure)
717
+ return if items.empty?
718
+
719
+ # Step 4: Assign tasks (optional - requires user ID)
720
+ # Get your user ID from: https://monday.com/account/profile
721
+ # Uncomment and add your user ID:
722
+ # team_member_id = 12345678
723
+ # manager.assign_tasks(items, team_member_id)
724
+
725
+ # Step 5: Update task status
726
+ manager.update_task_status(items)
727
+
728
+ # Step 6: Add updates
729
+ manager.add_task_updates(items)
730
+
731
+ # Step 7: Query all tasks
732
+ all_items = manager.query_tasks
733
+ return unless all_items
734
+
735
+ # Step 8: Find overdue tasks
736
+ manager.find_overdue_tasks(all_items)
737
+
738
+ # Step 9: Generate report
739
+ manager.generate_report(all_items)
740
+
741
+ # Step 10: Cleanup (optional)
742
+ manager.cleanup
743
+
744
+ puts "\n" + "=" * 70
745
+ puts "Task Management System Demo Complete!"
746
+ puts "=" * 70
747
+ end
748
+
749
+ # Run the program
750
+ begin
751
+ main
752
+ rescue Monday::AuthorizationError
753
+ puts "\n✗ Authentication failed. Check your MONDAY_TOKEN in .env"
754
+ rescue Monday::Error => e
755
+ puts "\n✗ API Error: #{e.message}"
756
+ rescue StandardError => e
757
+ puts "\n✗ Unexpected error: #{e.message}"
758
+ puts e.backtrace.first(5)
759
+ end
760
+ ```
761
+
762
+ ## Running the Example
763
+
764
+ Save the code above as `task_manager.rb` and run it:
765
+
766
+ ```bash
767
+ ruby task_manager.rb
768
+ ```
769
+
770
+ Expected output:
771
+ ```
772
+ ======================================================================
773
+ 🚀 TASK MANAGEMENT SYSTEM 🚀
774
+ ======================================================================
775
+
776
+ ======================================================================
777
+ STEP 1: Setting Up Task Board
778
+ ======================================================================
779
+ ✓ Created board: Project Tasks - 2024-11-01
780
+ ID: 1234567890
781
+ Description: Task management board for team collaboration
782
+
783
+ Creating task management columns...
784
+ ✓ Created column: Status (color)
785
+ ✓ Created column: Owner (people)
786
+ ✓ Created column: Due Date (date)
787
+ ✓ Created column: Priority (color)
788
+ ✓ Created column: Notes (text)
789
+
790
+ Creating project phase groups...
791
+ ✓ Created group: Planning
792
+ ✓ Created group: In Progress
793
+ ✓ Created group: Review
794
+ ✓ Created group: Completed
795
+
796
+ ======================================================================
797
+ STEP 2: Retrieving Board Structure
798
+ ======================================================================
799
+
800
+ Board: Project Tasks - 2024-11-01
801
+
802
+ Columns:
803
+ • Name: 'name' (name)
804
+ • Status: 'status' (color)
805
+ • Owner: 'people' (people)
806
+ • Due Date: 'date' (date)
807
+ • Priority: 'status_1' (color)
808
+ • Notes: 'text' (text)
809
+
810
+ Groups:
811
+ • Planning: 'topics'
812
+ • In Progress: 'group_12345'
813
+ • Review: 'group_23456'
814
+ • Completed: 'group_34567'
815
+
816
+ ======================================================================
817
+ STEP 3: Creating Tasks
818
+ ======================================================================
819
+ ✓ Created task: Define project requirements
820
+ ID: 987654321
821
+ Group: Planning
822
+ ✓ Created task: Design database schema
823
+ ID: 987654322
824
+ Group: Planning
825
+ ✓ Created task: Setup development environment
826
+ ID: 987654323
827
+ Group: Planning
828
+ ✓ Created task: Write API documentation
829
+ ID: 987654324
830
+ Group: Planning
831
+
832
+ ... (continues with all steps)
833
+ ```
834
+
835
+ ## Key Patterns and Best Practices
836
+
837
+ ### 1. Board Setup Pattern
838
+
839
+ Always create boards with proper structure:
840
+
841
+ ```ruby
842
+ # Create board
843
+ board_response = client.board.create(
844
+ args: {
845
+ board_name: "Task Board",
846
+ board_kind: :public,
847
+ description: "Description here"
848
+ }
849
+ )
850
+
851
+ board_id = board_response.body.dig("data", "create_board", "id").to_i
852
+
853
+ # Add columns
854
+ client.column.create(
855
+ args: {
856
+ board_id: board_id,
857
+ title: "Status",
858
+ column_type: :status
859
+ }
860
+ )
861
+ ```
862
+
863
+ ### 2. Get Column IDs Before Setting Values
864
+
865
+ Column IDs are board-specific:
866
+
867
+ ```ruby
868
+ response = client.board.query(
869
+ args: { ids: [board_id] },
870
+ select: [
871
+ {
872
+ columns: ["id", "title", "type"]
873
+ }
874
+ ]
875
+ )
876
+
877
+ board = response.body.dig("data", "boards", 0)
878
+ columns = board["columns"].each_with_object({}) do |col, hash|
879
+ hash[col["title"].downcase] = col["id"]
880
+ end
881
+
882
+ # Now use actual column IDs
883
+ column_values = {
884
+ columns["status"] => { label: "Working on it" }
885
+ }
886
+ ```
887
+
888
+ ### 3. Efficient Bulk Updates
889
+
890
+ Use `change_multiple_values` to update multiple columns at once:
891
+
892
+ ```ruby
893
+ # Good - Single request
894
+ client.column.change_multiple_values(
895
+ args: {
896
+ board_id: board_id,
897
+ item_id: item_id,
898
+ column_values: {
899
+ "status" => { label: "Done" },
900
+ "priority" => { label: "High" },
901
+ "date4" => { date: "2024-12-31" }
902
+ }
903
+ }
904
+ )
905
+
906
+ # Bad - Multiple requests
907
+ client.column.change_value(args: { column_id: "status", value: {...} })
908
+ client.column.change_value(args: { column_id: "priority", value: {...} })
909
+ client.column.change_value(args: { column_id: "date4", value: {...} })
910
+ ```
911
+
912
+ ### 4. Error Handling Pattern
913
+
914
+ Always check response status and handle errors:
915
+
916
+ ```ruby
917
+ def safe_create_item(client, board_id, name, columns = {})
918
+ response = client.item.create(
919
+ args: {
920
+ board_id: board_id,
921
+ item_name: name,
922
+ column_values: columns
923
+ }
924
+ )
925
+
926
+ if response.success?
927
+ item = response.body.dig("data", "create_item")
928
+ puts "✓ Created: #{item['name']}"
929
+ item
930
+ else
931
+ puts "✗ Failed to create: #{name}"
932
+
933
+ if response.body["errors"]
934
+ response.body["errors"].each do |error|
935
+ puts " Error: #{error['message']}"
936
+ end
937
+ end
938
+
939
+ nil
940
+ end
941
+ rescue Monday::AuthorizationError
942
+ puts "✗ Invalid API token"
943
+ nil
944
+ rescue Monday::InvalidRequestError => e
945
+ puts "✗ Invalid request: #{e.message}"
946
+ nil
947
+ rescue Monday::Error => e
948
+ puts "✗ API error: #{e.message}"
949
+ nil
950
+ end
951
+ ```
952
+
953
+ ### 5. Rate Limiting
954
+
955
+ Add delays between requests to avoid rate limits:
956
+
957
+ ```ruby
958
+ tasks.each do |task|
959
+ client.item.create(args: task_data)
960
+
961
+ # Prevent rate limiting
962
+ sleep(0.5)
963
+ end
964
+ ```
965
+
966
+ ### 6. Query Optimization
967
+
968
+ Select only needed fields:
969
+
970
+ ```ruby
971
+ # Good - Minimal fields
972
+ response = client.item.query(
973
+ args: { ids: item_ids },
974
+ select: [
975
+ "id",
976
+ "name",
977
+ {
978
+ column_values: ["id", "text"]
979
+ }
980
+ ]
981
+ )
982
+
983
+ # Bad - Over-fetching
984
+ response = client.item.query(
985
+ args: { ids: item_ids }
986
+ # Uses all default fields plus unused nested data
987
+ )
988
+ ```
989
+
990
+ ## Advanced Use Cases
991
+
992
+ ### Finding Tasks by Assignee
993
+
994
+ ```ruby
995
+ def find_user_tasks(client, board_id, user_id)
996
+ # Get all items
997
+ response = client.board.items_page(
998
+ board_ids: board_id,
999
+ limit: 100,
1000
+ select: [
1001
+ "id",
1002
+ "name",
1003
+ {
1004
+ column_values: ["id", "text", "type"]
1005
+ }
1006
+ ]
1007
+ )
1008
+
1009
+ return [] unless response.success?
1010
+
1011
+ board = response.body.dig("data", "boards", 0)
1012
+ items = board.dig("items_page", "items") || []
1013
+
1014
+ # Filter by people column
1015
+ items.select do |item|
1016
+ people_col = item["column_values"].find { |cv| cv["type"] == "people" }
1017
+ people_col && people_col["text"]&.include?(user_id.to_s)
1018
+ end
1019
+ end
1020
+
1021
+ user_tasks = find_user_tasks(client, board_id, 12345678)
1022
+ puts "User has #{user_tasks.length} assigned tasks"
1023
+ ```
1024
+
1025
+ ### Task Completion Analytics
1026
+
1027
+ ```ruby
1028
+ def calculate_completion_metrics(items)
1029
+ total = items.length
1030
+ return {} if total.zero?
1031
+
1032
+ # Count completed tasks
1033
+ completed = items.count do |item|
1034
+ status = item["column_values"].find { |cv| cv["id"].include?("status") }
1035
+ status&.dig("text") == "Done"
1036
+ end
1037
+
1038
+ # Calculate average completion time (if you track created/completed dates)
1039
+ completion_rate = (completed.to_f / total * 100).round(1)
1040
+
1041
+ # Group by priority
1042
+ high_priority = items.count do |item|
1043
+ priority = item["column_values"].select { |cv| cv["type"] == "color" }[1]
1044
+ priority&.dig("text") == "High"
1045
+ end
1046
+
1047
+ {
1048
+ total: total,
1049
+ completed: completed,
1050
+ in_progress: total - completed,
1051
+ completion_rate: completion_rate,
1052
+ high_priority: high_priority,
1053
+ high_priority_percentage: (high_priority.to_f / total * 100).round(1)
1054
+ }
1055
+ end
1056
+
1057
+ metrics = calculate_completion_metrics(all_items)
1058
+ puts "Completion Rate: #{metrics[:completion_rate]}%"
1059
+ puts "High Priority Tasks: #{metrics[:high_priority_percentage]}%"
1060
+ ```
1061
+
1062
+ ### Bulk Status Update
1063
+
1064
+ ```ruby
1065
+ def bulk_update_status(client, board_id, item_ids, new_status)
1066
+ # Get status column ID
1067
+ board_response = client.board.query(
1068
+ args: { ids: [board_id] },
1069
+ select: [
1070
+ {
1071
+ columns: ["id", "title", "type"]
1072
+ }
1073
+ ]
1074
+ )
1075
+
1076
+ return unless board_response.success?
1077
+
1078
+ board = board_response.body.dig("data", "boards", 0)
1079
+ status_column = board["columns"].find { |c| c["title"] == "Status" }
1080
+
1081
+ return unless status_column
1082
+
1083
+ # Update each item
1084
+ item_ids.each do |item_id|
1085
+ response = client.column.change_value(
1086
+ args: {
1087
+ board_id: board_id,
1088
+ item_id: item_id,
1089
+ column_id: status_column["id"],
1090
+ value: { label: new_status }
1091
+ }
1092
+ )
1093
+
1094
+ if response.success?
1095
+ puts "✓ Updated item #{item_id} to #{new_status}"
1096
+ else
1097
+ puts "✗ Failed to update item #{item_id}"
1098
+ end
1099
+
1100
+ sleep(0.5)
1101
+ end
1102
+ end
1103
+
1104
+ # Mark multiple tasks as complete
1105
+ bulk_update_status(client, board_id, [123, 456, 789], "Done")
1106
+ ```
1107
+
1108
+ ### Filter Tasks by Date Range
1109
+
1110
+ ```ruby
1111
+ def get_tasks_due_in_range(items, start_date, end_date)
1112
+ items.select do |item|
1113
+ date_col = item["column_values"].find { |cv| cv["type"] == "date" }
1114
+ next false unless date_col && date_col["text"]
1115
+
1116
+ begin
1117
+ due_date = Date.parse(date_col["text"].split(" ").first)
1118
+ due_date >= start_date && due_date <= end_date
1119
+ rescue Date::Error
1120
+ false
1121
+ end
1122
+ end
1123
+ end
1124
+
1125
+ # Get tasks due this week
1126
+ start_of_week = Date.today
1127
+ end_of_week = start_of_week + 7
1128
+
1129
+ this_week_tasks = get_tasks_due_in_range(
1130
+ all_items,
1131
+ start_of_week,
1132
+ end_of_week
1133
+ )
1134
+
1135
+ puts "Tasks due this week: #{this_week_tasks.length}"
1136
+ ```
1137
+
1138
+ ## Production Considerations
1139
+
1140
+ ### 1. Environment Configuration
1141
+
1142
+ Use environment variables for configuration:
1143
+
1144
+ ```ruby
1145
+ # config/monday.rb
1146
+ Monday.configure do |config|
1147
+ config.token = ENV.fetch("MONDAY_TOKEN")
1148
+ config.version = ENV.fetch("MONDAY_API_VERSION", "2024-10")
1149
+ end
1150
+ ```
1151
+
1152
+ ### 2. Logging
1153
+
1154
+ Add proper logging for production:
1155
+
1156
+ ```ruby
1157
+ require "logger"
1158
+
1159
+ class TaskManager
1160
+ attr_reader :client, :logger
1161
+
1162
+ def initialize
1163
+ @client = Monday::Client.new
1164
+ @logger = Logger.new(STDOUT)
1165
+ @logger.level = Logger::INFO
1166
+ end
1167
+
1168
+ def create_task(board_id, task_data)
1169
+ logger.info("Creating task: #{task_data[:name]}")
1170
+
1171
+ response = client.item.create(args: task_data)
1172
+
1173
+ if response.success?
1174
+ logger.info("Task created successfully")
1175
+ else
1176
+ logger.error("Failed to create task: #{response.body}")
1177
+ end
1178
+
1179
+ response
1180
+ end
1181
+ end
1182
+ ```
1183
+
1184
+ ### 3. Retry Logic
1185
+
1186
+ Handle transient failures:
1187
+
1188
+ ```ruby
1189
+ def with_retry(max_retries: 3, delay: 1)
1190
+ retries = 0
1191
+
1192
+ begin
1193
+ yield
1194
+ rescue Monday::RateLimitError
1195
+ retries += 1
1196
+ if retries <= max_retries
1197
+ sleep(delay * retries)
1198
+ retry
1199
+ else
1200
+ raise
1201
+ end
1202
+ end
1203
+ end
1204
+
1205
+ # Usage
1206
+ with_retry do
1207
+ client.item.create(args: task_data)
1208
+ end
1209
+ ```
1210
+
1211
+ ### 4. Batch Processing
1212
+
1213
+ Process tasks in batches:
1214
+
1215
+ ```ruby
1216
+ def process_tasks_in_batches(tasks, batch_size: 10)
1217
+ tasks.each_slice(batch_size) do |batch|
1218
+ batch.each do |task|
1219
+ yield task
1220
+ sleep(0.5)
1221
+ end
1222
+
1223
+ # Longer pause between batches
1224
+ sleep(2)
1225
+ end
1226
+ end
1227
+
1228
+ # Usage
1229
+ process_tasks_in_batches(tasks) do |task|
1230
+ client.item.create(args: task)
1231
+ end
1232
+ ```
1233
+
1234
+ ## Common Patterns
1235
+
1236
+ ### Get User ID
1237
+
1238
+ Find your user ID for assignments:
1239
+
1240
+ ```ruby
1241
+ # Visit your Monday.com profile
1242
+ # URL will be: https://monday.com/users/12345678
1243
+ # The number is your user ID
1244
+
1245
+ # Or query via API
1246
+ response = client.client.make_request(
1247
+ "query { me { id name email } }"
1248
+ )
1249
+
1250
+ if response.success?
1251
+ user = response.body.dig("data", "me")
1252
+ puts "User ID: #{user['id']}"
1253
+ puts "Name: #{user['name']}"
1254
+ puts "Email: #{user['email']}"
1255
+ end
1256
+ ```
1257
+
1258
+ ### Create Task Template
1259
+
1260
+ Reusable task creation:
1261
+
1262
+ ```ruby
1263
+ class TaskTemplate
1264
+ def self.bug_report(board_id, title, description, priority: "High")
1265
+ {
1266
+ board_id: board_id,
1267
+ item_name: "[BUG] #{title}",
1268
+ column_values: {
1269
+ "status" => { label: "Not Started" },
1270
+ "priority" => { label: priority },
1271
+ "text" => description,
1272
+ "date4" => { date: (Date.today + 3).to_s }
1273
+ }
1274
+ }
1275
+ end
1276
+
1277
+ def self.feature_request(board_id, title, description)
1278
+ {
1279
+ board_id: board_id,
1280
+ item_name: "[FEATURE] #{title}",
1281
+ column_values: {
1282
+ "status" => { label: "Planning" },
1283
+ "priority" => { label: "Medium" },
1284
+ "text" => description
1285
+ }
1286
+ }
1287
+ end
1288
+ end
1289
+
1290
+ # Usage
1291
+ bug_task = TaskTemplate.bug_report(
1292
+ board_id,
1293
+ "Login button not working",
1294
+ "Users report that clicking login does nothing"
1295
+ )
1296
+
1297
+ client.item.create(args: bug_task)
1298
+ ```
1299
+
1300
+ ## Next Steps
1301
+
1302
+ - [Query Items](/guides/items/query) - Learn advanced item queries
1303
+ - [Update Column Values](/guides/columns/update-values) - Master column updates
1304
+ - [Pagination](/guides/advanced/pagination) - Handle large datasets
1305
+ - [Error Handling](/guides/advanced/errors) - Robust error handling
1306
+ - [Rate Limiting](/guides/advanced/rate-limiting) - Manage API limits
1307
+
1308
+ ## Troubleshooting
1309
+
1310
+ ### "Column not found" errors
1311
+
1312
+ Get actual column IDs before setting values:
1313
+
1314
+ ```ruby
1315
+ response = client.board.query(
1316
+ args: { ids: [board_id] },
1317
+ select: [{ columns: ["id", "title", "type"] }]
1318
+ )
1319
+
1320
+ board = response.body.dig("data", "boards", 0)
1321
+ board["columns"].each do |col|
1322
+ puts "#{col['title']}: #{col['id']}"
1323
+ end
1324
+ ```
1325
+
1326
+ ### Rate limit errors
1327
+
1328
+ Add delays between requests:
1329
+
1330
+ ```ruby
1331
+ tasks.each do |task|
1332
+ client.item.create(args: task)
1333
+ sleep(0.5) # 500ms delay
1334
+ end
1335
+ ```
1336
+
1337
+ ### Invalid column value format
1338
+
1339
+ Check column type requirements:
1340
+
1341
+ ```ruby
1342
+ # Status column
1343
+ { label: "Done" }
1344
+
1345
+ # Date column
1346
+ { date: "2024-12-31" }
1347
+ { date: "2024-12-31", time: "14:00:00" }
1348
+
1349
+ # People column
1350
+ { personsAndTeams: [{ id: 12345678, kind: "person" }] }
1351
+
1352
+ # Text column
1353
+ "Just a string value"
1354
+
1355
+ # Numbers column
1356
+ 42 # or "42"
1357
+ ```
1358
+
1359
+ ### Authentication errors
1360
+
1361
+ Verify your token:
1362
+
1363
+ ```ruby
1364
+ # .env
1365
+ MONDAY_TOKEN=your_token_here
1366
+
1367
+ # In code
1368
+ require "dotenv/load"
1369
+
1370
+ Monday.configure do |config|
1371
+ config.token = ENV["MONDAY_TOKEN"]
1372
+ end
1373
+
1374
+ # Test authentication
1375
+ response = client.board.query(args: { limit: 1 })
1376
+ if response.success?
1377
+ puts "✓ Authentication successful"
1378
+ else
1379
+ puts "✗ Authentication failed"
1380
+ end
1381
+ ```