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,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
|
+
```
|