attio 0.1.3 โ†’ 0.3.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.
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/idl3/attio/tree/master/spec)
5
5
  [![Documentation](https://img.shields.io/badge/docs-yard-blue.svg)](https://idl3.github.io/attio)
6
6
  [![Gem Version](https://badge.fury.io/rb/attio.svg)](https://badge.fury.io/rb/attio)
7
- [![RSpec](https://img.shields.io/badge/RSpec-147_tests-green.svg)](https://github.com/idl3/attio/tree/master/spec)
7
+ [![RSpec](https://img.shields.io/badge/RSpec-392_tests-green.svg)](https://github.com/idl3/attio/tree/master/spec)
8
8
 
9
9
  Ruby client for the [Attio CRM API](https://developers.attio.com/). This library provides easy access to the Attio API, allowing you to manage records, objects, lists, and more.
10
10
 
@@ -154,6 +154,156 @@ client.records.update(
154
154
 
155
155
  ### Working with Other Resources
156
156
 
157
+ #### Comments
158
+
159
+ ```ruby
160
+ # List comments on a record
161
+ comments = client.comments.list(
162
+ parent_object: 'people',
163
+ parent_record_id: 'person-123'
164
+ )
165
+
166
+ # List comments in a thread
167
+ thread_comments = client.comments.list(thread_id: 'thread-456')
168
+
169
+ # Create a comment on a record
170
+ comment = client.comments.create(
171
+ parent_object: 'people',
172
+ parent_record_id: 'person-123',
173
+ content: 'This is a comment with **markdown** support!'
174
+ )
175
+
176
+ # Create a comment in a thread
177
+ thread_comment = client.comments.create(
178
+ thread_id: 'thread-456',
179
+ content: 'Following up on this discussion'
180
+ )
181
+
182
+ # Update a comment
183
+ updated_comment = client.comments.update(
184
+ id: 'comment-123',
185
+ content: 'Updated comment content'
186
+ )
187
+
188
+ # React to a comment
189
+ client.comments.react(id: 'comment-123', emoji: '๐Ÿ‘')
190
+
191
+ # Remove reaction
192
+ client.comments.unreact(id: 'comment-123', emoji: '๐Ÿ‘')
193
+
194
+ # Delete a comment
195
+ client.comments.delete(id: 'comment-123')
196
+ ```
197
+
198
+ #### Threads
199
+
200
+ ```ruby
201
+ # List threads on a record
202
+ threads = client.threads.list(
203
+ parent_object: 'companies',
204
+ parent_record_id: 'company-123'
205
+ )
206
+
207
+ # Get a thread with comments
208
+ thread = client.threads.get(id: 'thread-123', include_comments: true)
209
+
210
+ # Create a thread
211
+ thread = client.threads.create(
212
+ parent_object: 'companies',
213
+ parent_record_id: 'company-123',
214
+ title: 'Q4 Planning Discussion',
215
+ description: 'Thread for Q4 planning discussions',
216
+ participant_ids: ['user-1', 'user-2']
217
+ )
218
+
219
+ # Update thread title
220
+ client.threads.update(id: 'thread-123', title: 'Updated Q4 Planning')
221
+
222
+ # Manage participants
223
+ client.threads.add_participants(id: 'thread-123', user_ids: ['user-3', 'user-4'])
224
+ client.threads.remove_participants(id: 'thread-123', user_ids: ['user-2'])
225
+
226
+ # Close and reopen threads
227
+ client.threads.close(id: 'thread-123')
228
+ client.threads.reopen(id: 'thread-123')
229
+
230
+ # Delete a thread
231
+ client.threads.delete(id: 'thread-123')
232
+ ```
233
+
234
+ #### Tasks
235
+
236
+ ```ruby
237
+ # List all tasks
238
+ tasks = client.tasks.list
239
+
240
+ # List tasks with filters
241
+ my_tasks = client.tasks.list(
242
+ assignee_id: 'user-123',
243
+ status: 'pending'
244
+ )
245
+
246
+ # Get a specific task
247
+ task = client.tasks.get(id: 'task-123')
248
+
249
+ # Create a task
250
+ task = client.tasks.create(
251
+ parent_object: 'people',
252
+ parent_record_id: 'person-123',
253
+ title: 'Follow up with customer',
254
+ due_date: '2025-02-01',
255
+ assignee_id: 'user-456'
256
+ )
257
+
258
+ # Update a task
259
+ client.tasks.update(
260
+ id: 'task-123',
261
+ title: 'Updated task title',
262
+ status: 'in_progress'
263
+ )
264
+
265
+ # Complete a task
266
+ client.tasks.complete(id: 'task-123', completed_at: Time.now.iso8601)
267
+
268
+ # Reopen a task
269
+ client.tasks.reopen(id: 'task-123')
270
+
271
+ # Delete a task
272
+ client.tasks.delete(id: 'task-123')
273
+ ```
274
+
275
+ #### Notes
276
+
277
+ ```ruby
278
+ # List notes on a record
279
+ notes = client.notes.list(
280
+ parent_object: 'companies',
281
+ parent_record_id: 'company-123'
282
+ )
283
+
284
+ # Get a specific note
285
+ note = client.notes.get(id: 'note-123')
286
+
287
+ # Create a note
288
+ note = client.notes.create(
289
+ parent_object: 'companies',
290
+ parent_record_id: 'company-123',
291
+ title: 'Meeting Notes - Q4 Planning',
292
+ content: 'Discussed roadmap and resource allocation...',
293
+ tags: ['important', 'quarterly-planning']
294
+ )
295
+
296
+ # Update a note
297
+ client.notes.update(
298
+ id: 'note-123',
299
+ title: 'Updated Meeting Notes',
300
+ content: 'Added action items from discussion'
301
+ )
302
+
303
+ # Delete a note
304
+ client.notes.delete(id: 'note-123')
305
+ ```
306
+
157
307
  #### Objects
158
308
 
159
309
  ```ruby
@@ -211,6 +361,137 @@ users = client.users.list
211
361
  user = client.users.me
212
362
  ```
213
363
 
364
+ ### Advanced Features
365
+
366
+ #### Workspace Members
367
+
368
+ ```ruby
369
+ # List workspace members
370
+ members = client.workspace_members.list
371
+
372
+ # Invite a new member
373
+ invitation = client.workspace_members.invite(
374
+ email: 'new.member@example.com',
375
+ role: 'member' # admin, member, or guest
376
+ )
377
+
378
+ # Update member permissions
379
+ client.workspace_members.update(
380
+ member_id: 'user-123',
381
+ data: { role: 'admin' }
382
+ )
383
+
384
+ # Remove a member
385
+ client.workspace_members.remove(member_id: 'user-123')
386
+ ```
387
+
388
+ #### Deals
389
+
390
+ ```ruby
391
+ # List all deals
392
+ deals = client.deals.list
393
+
394
+ # Create a new deal
395
+ deal = client.deals.create(
396
+ data: {
397
+ name: 'Enterprise Contract',
398
+ value: 50000,
399
+ stage_id: 'stage-negotiation',
400
+ company_id: 'company-123'
401
+ }
402
+ )
403
+
404
+ # Update deal stage
405
+ client.deals.update_stage(id: 'deal-123', stage_id: 'stage-won')
406
+
407
+ # Mark deal as won/lost
408
+ client.deals.mark_won(id: 'deal-123', won_date: Date.today)
409
+ client.deals.mark_lost(id: 'deal-123', lost_reason: 'Budget constraints')
410
+
411
+ # List deals by various criteria
412
+ pipeline_deals = client.deals.list_by_stage(stage_id: 'stage-proposal')
413
+ company_deals = client.deals.list_by_company(company_id: 'company-123')
414
+ my_deals = client.deals.list_by_owner(owner_id: 'user-456')
415
+ ```
416
+
417
+ #### Bulk Operations
418
+
419
+ ```ruby
420
+ # Bulk create records
421
+ results = client.bulk.create_records(
422
+ object: 'people',
423
+ records: [
424
+ { name: 'John Doe', email: 'john@example.com' },
425
+ { name: 'Jane Smith', email: 'jane@example.com' },
426
+ # ... up to 100 records per batch
427
+ ]
428
+ )
429
+
430
+ # Bulk update records
431
+ results = client.bulk.update_records(
432
+ object: 'companies',
433
+ updates: [
434
+ { id: 'company-1', data: { status: 'active' } },
435
+ { id: 'company-2', data: { status: 'inactive' } }
436
+ ]
437
+ )
438
+
439
+ # Bulk upsert (create or update based on matching)
440
+ results = client.bulk.upsert_records(
441
+ object: 'people',
442
+ match_attribute: 'email',
443
+ records: [
444
+ { email: 'john@example.com', name: 'John Updated' },
445
+ { email: 'new@example.com', name: 'New Person' }
446
+ ]
447
+ )
448
+ ```
449
+
450
+ #### Rate Limiting
451
+
452
+ ```ruby
453
+ # Initialize client with custom rate limiter
454
+ limiter = Attio::RateLimiter.new(
455
+ max_requests: 100,
456
+ window_seconds: 60,
457
+ max_retries: 3
458
+ )
459
+ client.rate_limiter = limiter
460
+
461
+ # Execute with rate limiting
462
+ limiter.execute { client.records.list(object: 'people') }
463
+
464
+ # Queue requests for later processing
465
+ limiter.queue_request(priority: 1) { important_operation }
466
+ limiter.queue_request(priority: 5) { less_important_operation }
467
+
468
+ # Process queued requests
469
+ results = limiter.process_queue(max_per_batch: 10)
470
+
471
+ # Check rate limit status
472
+ status = limiter.status
473
+ puts "Remaining: #{status[:remaining]}/#{status[:limit]}"
474
+ ```
475
+
476
+ #### Meta API
477
+
478
+ ```ruby
479
+ # Identify current workspace and user
480
+ info = client.meta.identify
481
+ puts "Workspace: #{info['workspace']['name']}"
482
+ puts "User: #{info['user']['email']}"
483
+
484
+ # Validate API key
485
+ validation = client.meta.validate_key
486
+ puts "Valid: #{validation['valid']}"
487
+ puts "Permissions: #{validation['permissions']}"
488
+
489
+ # Get usage statistics
490
+ usage = client.meta.usage_stats
491
+ puts "Records: #{usage['records']['total']}"
492
+ puts "API calls today: #{usage['api_calls']['today']}"
493
+ ```
494
+
214
495
  ### Error Handling
215
496
 
216
497
  The client will raise appropriate exceptions for different error conditions:
@@ -240,12 +521,28 @@ end
240
521
 
241
522
  This client supports all major Attio API endpoints:
242
523
 
243
- - โœ… Records (CRUD operations, querying)
244
- - โœ… Objects (list, get schema)
245
- - โœ… Lists (list, get entries)
246
- - โœ… Workspaces (list, get current)
247
- - โœ… Attributes (list, create, update)
248
- - โœ… Users (list, get current user)
524
+ ### Core Resources
525
+ - โœ… **Records** - Full CRUD operations, querying with filters and sorting
526
+ - โœ… **Objects** - List, get schema information
527
+ - โœ… **Lists** - List, get entries, manage list entries
528
+ - โœ… **Attributes** - List, create, update custom attributes
529
+ - โœ… **Workspaces** - List, get current workspace
530
+ - โœ… **Users** - List, get current user
531
+
532
+ ### Collaboration Features
533
+ - โœ… **Comments** - CRUD operations, emoji reactions on records and threads
534
+ - โœ… **Threads** - CRUD operations, participant management, status control
535
+ - โœ… **Tasks** - CRUD operations, assignment, completion tracking
536
+ - โœ… **Notes** - CRUD operations on records
537
+
538
+ ### Sales & CRM
539
+ - โœ… **Deals** - Pipeline management, stage tracking, win/loss tracking
540
+ - โœ… **Workspace Members** - Member management, invitations, permissions
541
+
542
+ ### Advanced Features
543
+ - โœ… **Bulk Operations** - Batch create/update/delete with automatic batching
544
+ - โœ… **Rate Limiting** - Intelligent retry with exponential backoff and request queuing
545
+ - โœ… **Meta API** - Identify workspace, validate API keys, get usage stats
249
546
 
250
547
  ## Development
251
548
 
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "attio"
5
+ require "json"
6
+
7
+ # Example: Advanced Filtering and Querying
8
+ #
9
+ # This example demonstrates how to use advanced filtering and sorting
10
+ # capabilities when querying records in Attio.
11
+
12
+ # Initialize the client
13
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
14
+
15
+ puts "Attio Advanced Filtering Example"
16
+ puts "=" * 40
17
+
18
+ # 1. Query with simple filters
19
+ puts "\n1. Finding people by email domain..."
20
+ people_from_domain = client.records.list(
21
+ object: "people",
22
+ filters: {
23
+ email: { contains: "@example.com" },
24
+ },
25
+ limit: 10
26
+ )
27
+ puts " Found #{people_from_domain['data']&.length || 0} people from example.com"
28
+
29
+ # 2. Query with multiple filters (AND condition)
30
+ puts "\n2. Finding high-value companies in technology sector..."
31
+ high_value_tech = client.records.list(
32
+ object: "companies",
33
+ filters: {
34
+ industry: { equals: "Technology" },
35
+ annual_revenue: { greater_than: 1_000_000 },
36
+ },
37
+ sorts: [
38
+ { field: "annual_revenue", direction: "desc" },
39
+ ],
40
+ limit: 5
41
+ )
42
+ puts " Found #{high_value_tech['data']&.length || 0} high-value tech companies"
43
+
44
+ # 3. Query with date range filters
45
+ puts "\n3. Finding recently created records..."
46
+ recent_date = (Date.today - 30).iso8601
47
+ recent_records = client.records.list(
48
+ object: "people",
49
+ filters: {
50
+ created_at: { greater_than: recent_date },
51
+ },
52
+ sorts: [
53
+ { field: "created_at", direction: "desc" },
54
+ ]
55
+ )
56
+ puts " Found #{recent_records['data']&.length || 0} people created in last 30 days"
57
+
58
+ # 4. Query with relationship filters
59
+ puts "\n4. Finding people associated with specific companies..."
60
+ # First, get a company
61
+ companies = client.records.list(object: "companies", limit: 1)
62
+ if companies.dig("data", 0)
63
+ company_id = companies.dig("data", 0, "id", "record_id")
64
+
65
+ people_at_company = client.records.list(
66
+ object: "people",
67
+ filters: {
68
+ company: {
69
+ target_object: "companies",
70
+ target_record_id: company_id,
71
+ },
72
+ }
73
+ )
74
+ puts " Found #{people_at_company['data']&.length || 0} people at the company"
75
+ else
76
+ puts " No companies found for demo"
77
+ end
78
+
79
+ # 5. Query with null/not null filters
80
+ puts "\n5. Finding records with missing data..."
81
+ missing_email = client.records.list(
82
+ object: "people",
83
+ filters: {
84
+ email: { is_null: true },
85
+ },
86
+ limit: 10
87
+ )
88
+ puts " Found #{missing_email['data']&.length || 0} people without email addresses"
89
+
90
+ # 6. Complex sorting with multiple fields
91
+ puts "\n6. Sorting by multiple criteria..."
92
+ sorted_companies = client.records.list(
93
+ object: "companies",
94
+ sorts: [
95
+ { field: "industry", direction: "asc" },
96
+ { field: "annual_revenue", direction: "desc" },
97
+ { field: "name", direction: "asc" },
98
+ ],
99
+ limit: 20
100
+ )
101
+ puts " Retrieved #{sorted_companies['data']&.length || 0} companies sorted by industry, revenue, and name"
102
+
103
+ # 7. Pagination example
104
+ puts "\n7. Paginating through results..."
105
+ page_size = 5
106
+ total_fetched = 0
107
+ cursor = nil
108
+
109
+ 3.times do |page|
110
+ params = {
111
+ object: "people",
112
+ limit: page_size,
113
+ }
114
+ params[:cursor] = cursor if cursor
115
+
116
+ page_results = client.records.list(**params)
117
+ fetched = page_results["data"]&.length || 0
118
+ total_fetched += fetched
119
+
120
+ puts " Page #{page + 1}: fetched #{fetched} records"
121
+
122
+ cursor = page_results.dig("pagination", "next_cursor")
123
+ break unless cursor
124
+ end
125
+ puts " Total fetched across pages: #{total_fetched}"
126
+
127
+ # 8. Query tasks with status filter
128
+ puts "\n8. Finding pending tasks..."
129
+ pending_tasks = client.tasks.list(
130
+ status: "pending",
131
+ limit: 10
132
+ )
133
+ puts " Found #{pending_tasks['data']&.length || 0} pending tasks"
134
+
135
+ # 9. Query with custom field filters
136
+ puts "\n9. Filtering by custom fields..."
137
+ # This assumes you have custom fields set up
138
+ client.records.list(
139
+ object: "people",
140
+ filters: {
141
+ # Replace with your actual custom field API slug
142
+ # custom_field: { equals: "some_value" }
143
+ },
144
+ limit: 10
145
+ )
146
+ puts " Custom field filtering available for your specific schema"
147
+
148
+ # 10. Export query for analysis
149
+ puts "\n10. Exporting query results..."
150
+ export_data = client.records.list(
151
+ object: "companies",
152
+ filters: {
153
+ industry: { not_null: true },
154
+ },
155
+ limit: 100
156
+ )
157
+
158
+ if export_data["data"] && !export_data["data"].empty?
159
+ # Simple CSV-like export
160
+ puts " Sample export (first 3 records):"
161
+ export_data["data"].take(3).each do |record|
162
+ name = record.dig("values", "name", 0, "value") || "N/A"
163
+ industry = record.dig("values", "industry", 0, "value") || "N/A"
164
+ puts " - #{name}: #{industry}"
165
+ end
166
+ end
167
+
168
+ puts "\n#{'=' * 40}"
169
+ puts "Example completed successfully!"
170
+ puts "\nThis example demonstrated:"
171
+ puts " โ€ข Simple and complex filtering"
172
+ puts " โ€ข Multi-field sorting"
173
+ puts " โ€ข Date range queries"
174
+ puts " โ€ข Relationship filters"
175
+ puts " โ€ข Null/not-null checks"
176
+ puts " โ€ข Pagination"
177
+ puts " โ€ข Task filtering"
178
+ puts " โ€ข Custom field queries"
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "attio"
6
+
7
+ # Basic usage example for the Attio Ruby gem
8
+ #
9
+ # This example demonstrates:
10
+ # - Client initialization
11
+ # - Working with records (people, companies)
12
+ # - Creating relationships between records
13
+ # - Error handling
14
+
15
+ # Initialize the client with your API key
16
+ # You can get your API key from: https://app.attio.com/settings/api-keys
17
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
18
+
19
+ puts "๐Ÿš€ Attio Ruby Client - Basic Usage Example"
20
+ puts "=" * 50
21
+
22
+ begin
23
+ # List all available objects in your workspace
24
+ puts "\n๐Ÿ“‹ Available Objects:"
25
+ objects = client.objects.list
26
+ objects["data"].each do |object|
27
+ puts " - #{object['name']} (#{object['id']})"
28
+ end
29
+
30
+ # Working with People records
31
+ puts "\n๐Ÿ‘ฅ Working with People:"
32
+
33
+ # List existing people (first 5)
34
+ people = client.records.list(object: "people", limit: 5)
35
+ puts " Found #{people['data'].length} people records"
36
+
37
+ # Create a new person
38
+ new_person = client.records.create(
39
+ object: "people",
40
+ data: {
41
+ name: "John Doe",
42
+ email: "john.doe@example.com",
43
+ phone: "+1-555-0123",
44
+ title: "Software Engineer",
45
+ }
46
+ )
47
+ puts " โœ… Created person: #{new_person['data']['name']} (ID: #{new_person['data']['id']})"
48
+
49
+ # Update the person
50
+ updated_person = client.records.update(
51
+ object: "people",
52
+ id: new_person["data"]["id"],
53
+ data: { title: "Senior Software Engineer" }
54
+ )
55
+ puts " โœ… Updated title to: #{updated_person['data']['title']}"
56
+
57
+ # Working with Companies
58
+ puts "\n๐Ÿข Working with Companies:"
59
+
60
+ # Create a company
61
+ new_company = client.records.create(
62
+ object: "companies",
63
+ data: {
64
+ name: "Acme Corp",
65
+ domain: "acme.com",
66
+ employee_count: 100,
67
+ description: "Leading provider of innovative solutions",
68
+ }
69
+ )
70
+ puts " โœ… Created company: #{new_company['data']['name']}"
71
+
72
+ # Link person to company (create relationship)
73
+ # Note: This requires the person and company to have a relationship field configured
74
+ puts "\n๐Ÿ”— Creating Relationships:"
75
+ # This would typically be done through a reference field
76
+ # The exact implementation depends on your Attio workspace configuration
77
+
78
+ # Working with Lists
79
+ puts "\n๐Ÿ“ Working with Lists:"
80
+ lists = client.lists.list(limit: 5)
81
+ if lists["data"].any?
82
+ first_list = lists["data"].first
83
+ puts " Found list: #{first_list['name']}"
84
+
85
+ # Get entries in the list
86
+ entries = client.lists.entries(id: first_list["id"], limit: 5)
87
+ puts " List has #{entries['data'].length} entries"
88
+ else
89
+ puts " No lists found in workspace"
90
+ end
91
+
92
+ # Cleanup - Delete the test records
93
+ puts "\n๐Ÿงน Cleaning up test data:"
94
+ client.records.delete(object: "people", id: new_person["data"]["id"])
95
+ puts " โœ… Deleted test person record"
96
+
97
+ client.records.delete(object: "companies", id: new_company["data"]["id"])
98
+ puts " โœ… Deleted test company record"
99
+ rescue Attio::AuthenticationError => e
100
+ puts "โŒ Authentication failed: #{e.message}"
101
+ puts " Please check your API key"
102
+ rescue Attio::NotFoundError => e
103
+ puts "โŒ Resource not found: #{e.message}"
104
+ rescue Attio::ValidationError => e
105
+ puts "โŒ Validation error: #{e.message}"
106
+ rescue Attio::Error => e
107
+ puts "โŒ API error: #{e.message}"
108
+ end
109
+
110
+ puts "\nโœจ Example completed!"