attio 0.2.0 → 0.4.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
@@ -1,10 +1,10 @@
1
1
  # Attio Ruby Client
2
2
 
3
3
  [![Tests](https://github.com/idl3/attio/actions/workflows/tests.yml/badge.svg)](https://github.com/idl3/attio/actions/workflows/tests.yml)
4
- [![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/idl3/attio/tree/master/spec)
4
+ [![Test Coverage](https://img.shields.io/badge/coverage-98.81%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-265_tests-green.svg)](https://github.com/idl3/attio/tree/master/spec)
7
+ [![RSpec](https://img.shields.io/badge/RSpec-607_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
 
@@ -84,22 +84,21 @@ client = Attio::Client.new(api_key: 'your-api-key', timeout: 60)
84
84
  # List all people
85
85
  people = client.records.list(object: 'people')
86
86
 
87
- # List with filters
87
+ # List with filtering and sorting
88
88
  filtered_people = client.records.list(
89
89
  object: 'people',
90
- filters: {
91
- name: { contains: 'John' },
92
- company: { target_object: 'companies', target_record_id: 'company-123' }
90
+ filter: {
91
+ name: { $contains: 'John' }
93
92
  },
94
- limit: 50
93
+ sort: 'created_at.desc',
94
+ limit: 50,
95
+ offset: 0
95
96
  )
96
97
 
97
- # List with sorting
98
- sorted_people = client.records.list(
99
- object: 'people',
100
- sorts: [{ field: 'created_at', direction: 'desc' }],
101
- limit: 25
102
- )
98
+ # List all records with automatic pagination
99
+ client.records.list_all(object: 'people', page_size: 50).each do |person|
100
+ puts person['name']
101
+ end
103
102
  ```
104
103
 
105
104
  #### Creating Records
@@ -361,6 +360,293 @@ users = client.users.list
361
360
  user = client.users.me
362
361
  ```
363
362
 
363
+ ### Advanced Features
364
+
365
+ #### Workspace Members
366
+
367
+ ```ruby
368
+ # List workspace members
369
+ members = client.workspace_members.list
370
+
371
+ # Invite a new member
372
+ invitation = client.workspace_members.invite(
373
+ email: 'new.member@example.com',
374
+ role: 'member' # admin, member, or guest
375
+ )
376
+
377
+ # Update member permissions
378
+ client.workspace_members.update(
379
+ member_id: 'user-123',
380
+ data: { role: 'admin' }
381
+ )
382
+
383
+ # Remove a member
384
+ client.workspace_members.remove(member_id: 'user-123')
385
+ ```
386
+
387
+ #### Deals
388
+
389
+ ```ruby
390
+ # List all deals
391
+ deals = client.deals.list
392
+
393
+ # Create a new deal
394
+ deal = client.deals.create(
395
+ data: {
396
+ name: 'Enterprise Contract',
397
+ value: 50000,
398
+ stage_id: 'stage-negotiation',
399
+ company_id: 'company-123'
400
+ }
401
+ )
402
+
403
+ # Update deal stage
404
+ client.deals.update_stage(id: 'deal-123', stage_id: 'stage-won')
405
+
406
+ # Mark deal as won/lost
407
+ client.deals.mark_won(id: 'deal-123', won_date: Date.today)
408
+ client.deals.mark_lost(id: 'deal-123', lost_reason: 'Budget constraints')
409
+
410
+ # List deals by various criteria
411
+ pipeline_deals = client.deals.list_by_stage(stage_id: 'stage-proposal')
412
+ company_deals = client.deals.list_by_company(company_id: 'company-123')
413
+ my_deals = client.deals.list_by_owner(owner_id: 'user-456')
414
+ ```
415
+
416
+ #### Bulk Operations
417
+
418
+ ```ruby
419
+ # Bulk create records
420
+ results = client.bulk.create_records(
421
+ object: 'people',
422
+ records: [
423
+ { name: 'John Doe', email: 'john@example.com' },
424
+ { name: 'Jane Smith', email: 'jane@example.com' },
425
+ # ... up to 100 records per batch
426
+ ]
427
+ )
428
+
429
+ # Bulk update records
430
+ results = client.bulk.update_records(
431
+ object: 'companies',
432
+ updates: [
433
+ { id: 'company-1', data: { status: 'active' } },
434
+ { id: 'company-2', data: { status: 'inactive' } }
435
+ ]
436
+ )
437
+
438
+ # Bulk upsert (create or update based on matching)
439
+ results = client.bulk.upsert_records(
440
+ object: 'people',
441
+ match_attribute: 'email',
442
+ records: [
443
+ { email: 'john@example.com', name: 'John Updated' },
444
+ { email: 'new@example.com', name: 'New Person' }
445
+ ]
446
+ )
447
+ ```
448
+
449
+ #### Rate Limiting
450
+
451
+ ```ruby
452
+ # Initialize client with custom rate limiter
453
+ limiter = Attio::RateLimiter.new(
454
+ max_requests: 100,
455
+ window_seconds: 60,
456
+ max_retries: 3
457
+ )
458
+ client.rate_limiter = limiter
459
+
460
+ # Execute with rate limiting
461
+ limiter.execute { client.records.list(object: 'people') }
462
+
463
+ # Queue requests for later processing
464
+ limiter.queue_request(priority: 1) { important_operation }
465
+ limiter.queue_request(priority: 5) { less_important_operation }
466
+
467
+ # Process queued requests
468
+ results = limiter.process_queue(max_per_batch: 10)
469
+
470
+ # Check rate limit status
471
+ status = limiter.status
472
+ puts "Remaining: #{status[:remaining]}/#{status[:limit]}"
473
+ ```
474
+
475
+ ## Enterprise Features
476
+
477
+ The gem includes advanced enterprise features for production use:
478
+
479
+ ### Enhanced Client
480
+
481
+ The `EnhancedClient` provides production-ready features including connection pooling, circuit breaker, observability, and webhook support:
482
+
483
+ ```ruby
484
+ # Create an enhanced client with all features
485
+ client = Attio.enhanced_client(
486
+ api_key: ENV['ATTIO_API_KEY'],
487
+ connection_pool: {
488
+ size: 10, # Pool size
489
+ timeout: 5 # Checkout timeout
490
+ },
491
+ circuit_breaker: {
492
+ threshold: 5, # Failures before opening
493
+ timeout: 30, # Recovery timeout in seconds
494
+ half_open_requests: 2
495
+ },
496
+ instrumentation: {
497
+ logger: Rails.logger,
498
+ metrics: :datadog, # or :statsd, :prometheus, :opentelemetry
499
+ traces: :datadog # or :opentelemetry
500
+ },
501
+ webhook_secret: ENV['ATTIO_WEBHOOK_SECRET']
502
+ )
503
+
504
+ # Use it like a regular client
505
+ records = client.records.list(object: 'people')
506
+
507
+ # Execute with circuit breaker protection
508
+ client.execute(endpoint: 'api/records') do
509
+ client.records.create(object: 'people', data: { name: 'John' })
510
+ end
511
+
512
+ # Check health of all components
513
+ health = client.health_check
514
+ # => { api: true, pool: true, circuit_breaker: :healthy, rate_limiter: true }
515
+
516
+ # Get statistics
517
+ stats = client.stats
518
+ # => { pool: { size: 10, available: 7 }, circuit_breaker: { state: :closed, requests: 100 } }
519
+ ```
520
+
521
+ ### Connection Pooling
522
+
523
+ Efficient connection management with thread-safe pooling:
524
+
525
+ ```ruby
526
+ pool = Attio::ConnectionPool.new(size: 5, timeout: 2) do
527
+ Attio::HttpClient.new(
528
+ base_url: 'https://api.attio.com/v2',
529
+ headers: { 'Authorization' => "Bearer #{api_key}" }
530
+ )
531
+ end
532
+
533
+ # Use connections from the pool
534
+ pool.with do |connection|
535
+ connection.get('records')
536
+ end
537
+
538
+ # Check pool status
539
+ stats = pool.stats
540
+ # => { size: 5, available: 3, allocated: 2 }
541
+
542
+ # Graceful shutdown
543
+ pool.shutdown
544
+ ```
545
+
546
+ ### Circuit Breaker
547
+
548
+ Fault tolerance with circuit breaker pattern:
549
+
550
+ ```ruby
551
+ breaker = Attio::CircuitBreaker.new(
552
+ threshold: 5, # Open after 5 failures
553
+ timeout: 30, # Reset after 30 seconds
554
+ half_open_requests: 2
555
+ )
556
+
557
+ # Execute with protection
558
+ result = breaker.execute do
559
+ risky_api_call
560
+ end
561
+
562
+ # Monitor state changes
563
+ breaker.on_state_change = ->(old_state, new_state) {
564
+ puts "Circuit breaker: #{old_state} -> #{new_state}"
565
+ }
566
+
567
+ # Check current state
568
+ breaker.state # => :closed, :open, or :half_open
569
+ breaker.stats # => { requests: 100, failures: 2, success_rate: 0.98 }
570
+ ```
571
+
572
+ ### Observability
573
+
574
+ Comprehensive monitoring with multiple backend support:
575
+
576
+ ```ruby
577
+ # Initialize with your preferred backend
578
+ instrumentation = Attio::Observability::Instrumentation.new(
579
+ logger: Logger.new(STDOUT),
580
+ metrics_backend: :datadog, # :statsd, :prometheus, :memory
581
+ trace_backend: :opentelemetry # :datadog, :memory
582
+ )
583
+
584
+ # Record API calls
585
+ instrumentation.record_api_call(
586
+ method: :post,
587
+ path: '/records',
588
+ duration: 0.125,
589
+ status: 200
590
+ )
591
+
592
+ # Record rate limits
593
+ instrumentation.record_rate_limit(
594
+ remaining: 450,
595
+ limit: 500,
596
+ reset_at: Time.now + 3600
597
+ )
598
+
599
+ # Record circuit breaker state changes
600
+ instrumentation.record_circuit_breaker(
601
+ endpoint: 'api/records',
602
+ old_state: :closed,
603
+ new_state: :open
604
+ )
605
+
606
+ # Track pool statistics
607
+ instrumentation.record_pool_stats(
608
+ size: 10,
609
+ available: 7,
610
+ allocated: 3
611
+ )
612
+ ```
613
+
614
+ ### Webhook Processing
615
+
616
+ Secure webhook handling with signature verification:
617
+
618
+ ```ruby
619
+ # Initialize webhook handler
620
+ webhooks = Attio::Webhooks.new(secret: ENV['ATTIO_WEBHOOK_SECRET'])
621
+
622
+ # Register event handlers
623
+ webhooks.on('record.created') do |event|
624
+ puts "New record: #{event.data['id']}"
625
+ end
626
+
627
+ webhooks.on_any do |event|
628
+ puts "Event: #{event.type}"
629
+ end
630
+
631
+ # Process incoming webhook
632
+ begin
633
+ event = webhooks.process(
634
+ request.body.read,
635
+ request.headers
636
+ )
637
+ render json: { status: 'ok' }
638
+ rescue Attio::Webhooks::InvalidSignatureError => e
639
+ render json: { error: 'Invalid signature' }, status: 401
640
+ end
641
+
642
+ # Development webhook server
643
+ server = Attio::WebhookServer.new(port: 3001, secret: 'test_secret')
644
+ server.webhooks.on('record.created') do |event|
645
+ puts "Received: #{event.inspect}"
646
+ end
647
+ server.start # Starts WEBrick server for testing
648
+ ```
649
+
364
650
  ### Error Handling
365
651
 
366
652
  The client will raise appropriate exceptions for different error conditions:
@@ -390,16 +676,35 @@ end
390
676
 
391
677
  This client supports all major Attio API endpoints:
392
678
 
393
- - Records (CRUD operations, querying with filters and sorting)
394
- - ✅ Objects (list, get schema)
395
- - ✅ Lists (list, get entries, manage list entries)
396
- - ✅ Workspaces (list, get current)
397
- - ✅ Attributes (list, create, update)
398
- - ✅ Users (list, get current user)
399
- - ✅ Comments (CRUD operations, reactions on records and threads)
400
- - ✅ Threads (CRUD operations, participant management, status control)
401
- - Tasks (CRUD operations, assignment, completion tracking)
402
- - ✅ Notes (CRUD operations on records)
679
+ ### Core Resources
680
+ - ✅ **Records** - Full CRUD operations, querying with filters and sorting
681
+ - ✅ **Objects** - List, get schema information
682
+ - ✅ **Lists** - List, get entries, manage list entries
683
+ - ✅ **Attributes** - List, create, update custom attributes
684
+ - ✅ **Workspaces** - List, get current workspace
685
+ - ✅ **Users** - List, get current user
686
+
687
+ ### Collaboration Features
688
+ - ✅ **Comments** - CRUD operations, emoji reactions on records and threads
689
+ - ✅ **Threads** - CRUD operations, participant management, status control
690
+ - ✅ **Tasks** - CRUD operations, assignment, completion tracking
691
+ - ✅ **Notes** - CRUD operations on records
692
+
693
+ ### Sales & CRM
694
+ - ✅ **Deals** - Pipeline management, stage tracking, win/loss tracking
695
+ - ✅ **Workspace Members** - Member management, invitations, permissions
696
+
697
+ ### Advanced Features
698
+ - ✅ **Bulk Operations** - Batch create/update/delete with automatic batching (1000 items max)
699
+ - ✅ **Rate Limiting** - Intelligent retry with exponential backoff and request queuing
700
+
701
+ ### Enterprise Features
702
+ - ✅ **Enhanced Client** - Production-ready client with pooling, circuit breaker, and observability
703
+ - ✅ **Connection Pooling** - Thread-safe connection management with configurable pool size
704
+ - ✅ **Circuit Breaker** - Fault tolerance with automatic recovery and state monitoring
705
+ - ✅ **Observability** - Metrics and tracing with StatsD, Datadog, Prometheus, OpenTelemetry support
706
+ - ✅ **Webhook Processing** - Secure webhook handling with HMAC signature verification
707
+ - ✅ **Middleware** - Request/response instrumentation for monitoring
403
708
 
404
709
  ## Development
405
710
 
@@ -426,10 +731,51 @@ bundle exec rake docs:serve
426
731
 
427
732
  ### Code Coverage
428
733
 
734
+ The gem maintains 100% test coverage across all features:
735
+
429
736
  ```bash
430
- bundle exec rake coverage:report
737
+ # Run tests with coverage report
738
+ bundle exec rspec
739
+
740
+ # View detailed coverage report
741
+ open coverage/index.html
431
742
  ```
432
743
 
744
+ Current stats:
745
+ - **Test Coverage**: 100% (1311/1311 lines)
746
+ - **Test Count**: 590 tests
747
+ - **RuboCop**: 0 violations
748
+
749
+ ## Migration from v0.3.0 to v0.4.0
750
+
751
+ ### Breaking Changes
752
+
753
+ 1. **Meta API Removed**: The Meta resource was completely fake and has been removed.
754
+ ```ruby
755
+ # OLD (will not work)
756
+ client.meta.identify
757
+
758
+ # NEW - use a real endpoint if needed
759
+ # No direct replacement - Meta API didn't exist in Attio
760
+ ```
761
+
762
+ 2. **Webhook Headers Fixed**: Header names no longer have X- prefix.
763
+ ```ruby
764
+ # OLD
765
+ headers["X-Attio-Signature"]
766
+
767
+ # NEW
768
+ headers["Attio-Signature"]
769
+ ```
770
+
771
+ 3. **Records List Method**: Now uses GET instead of POST internally (no API change needed).
772
+
773
+ ### New Features
774
+
775
+ - **Rate Limiting**: Now automatically enforced
776
+ - **Pagination**: Use `list_all` for automatic pagination
777
+ - **Filtering**: Full support for Attio's filter syntax
778
+
433
779
  ## Contributing
434
780
 
435
781
  Bug reports and pull requests are welcome on GitHub at https://github.com/idl3/attio.