airbyte-internal-ops 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

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.
@@ -26,9 +26,17 @@ from airbyte_ops_mcp.prod_db_access.sql import (
26
26
  SELECT_CONNECTIONS_BY_DESTINATION_CONNECTOR_AND_ORG,
27
27
  SELECT_CONNECTOR_VERSIONS,
28
28
  SELECT_DATAPLANES_LIST,
29
+ SELECT_DESTINATION_CONNECTION_STATS,
29
30
  SELECT_FAILED_SYNC_ATTEMPTS_FOR_CONNECTOR,
30
31
  SELECT_NEW_CONNECTOR_RELEASES,
31
32
  SELECT_ORG_WORKSPACES,
33
+ SELECT_RECENT_FAILED_SYNCS_FOR_DESTINATION_CONNECTOR,
34
+ SELECT_RECENT_FAILED_SYNCS_FOR_SOURCE_CONNECTOR,
35
+ SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_DESTINATION_CONNECTOR,
36
+ SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_SOURCE_CONNECTOR,
37
+ SELECT_RECENT_SYNCS_FOR_DESTINATION_CONNECTOR,
38
+ SELECT_RECENT_SYNCS_FOR_SOURCE_CONNECTOR,
39
+ SELECT_SOURCE_CONNECTION_STATS,
32
40
  SELECT_SUCCESSFUL_SYNCS_FOR_VERSION,
33
41
  SELECT_SYNC_RESULTS_FOR_VERSION,
34
42
  SELECT_WORKSPACE_INFO,
@@ -227,7 +235,7 @@ def query_actors_pinned_to_version(
227
235
  )
228
236
 
229
237
 
230
- def query_sync_results_for_version(
238
+ def query_syncs_for_version_pinned_connector(
231
239
  connector_version_id: str,
232
240
  days: int = 7,
233
241
  limit: int = 100,
@@ -320,6 +328,81 @@ def query_failed_sync_attempts_for_connector(
320
328
  return results
321
329
 
322
330
 
331
+ def query_recent_syncs_for_connector(
332
+ connector_definition_id: str,
333
+ is_destination: bool = False,
334
+ status_filter: str = "all",
335
+ organization_id: str | None = None,
336
+ days: int = 7,
337
+ limit: int = 100,
338
+ *,
339
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
340
+ ) -> list[dict[str, Any]]:
341
+ """Query recent sync jobs for ALL actors using a connector definition.
342
+
343
+ Finds all actors with the given actor_definition_id and returns their sync jobs,
344
+ regardless of whether they have explicit version pins. Filters out deleted actors,
345
+ deleted workspaces, and deprecated connections.
346
+
347
+ This is useful for finding healthy connections with recent successful syncs,
348
+ or for investigating connector issues across all users.
349
+
350
+ Args:
351
+ connector_definition_id: Connector definition UUID to filter by
352
+ is_destination: If True, query destination connectors; if False, query sources
353
+ status_filter: Filter by job status - "all", "succeeded", or "failed"
354
+ organization_id: Optional organization UUID to filter results by (post-query filter)
355
+ days: Number of days to look back (default: 7)
356
+ limit: Maximum number of results (default: 100)
357
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
358
+
359
+ Returns:
360
+ List of sync job records with workspace info and optional pin context
361
+ """
362
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
363
+
364
+ # Select the appropriate query based on connector type and status filter
365
+ if is_destination:
366
+ if status_filter == "succeeded":
367
+ query = SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_DESTINATION_CONNECTOR
368
+ query_name = "SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_DESTINATION_CONNECTOR"
369
+ elif status_filter == "failed":
370
+ query = SELECT_RECENT_FAILED_SYNCS_FOR_DESTINATION_CONNECTOR
371
+ query_name = "SELECT_RECENT_FAILED_SYNCS_FOR_DESTINATION_CONNECTOR"
372
+ else:
373
+ query = SELECT_RECENT_SYNCS_FOR_DESTINATION_CONNECTOR
374
+ query_name = "SELECT_RECENT_SYNCS_FOR_DESTINATION_CONNECTOR"
375
+ else:
376
+ if status_filter == "succeeded":
377
+ query = SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_SOURCE_CONNECTOR
378
+ query_name = "SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_SOURCE_CONNECTOR"
379
+ elif status_filter == "failed":
380
+ query = SELECT_RECENT_FAILED_SYNCS_FOR_SOURCE_CONNECTOR
381
+ query_name = "SELECT_RECENT_FAILED_SYNCS_FOR_SOURCE_CONNECTOR"
382
+ else:
383
+ query = SELECT_RECENT_SYNCS_FOR_SOURCE_CONNECTOR
384
+ query_name = "SELECT_RECENT_SYNCS_FOR_SOURCE_CONNECTOR"
385
+
386
+ results = _run_sql_query(
387
+ query,
388
+ parameters={
389
+ "connector_definition_id": connector_definition_id,
390
+ "cutoff_date": cutoff_date,
391
+ "limit": limit,
392
+ },
393
+ query_name=query_name,
394
+ gsm_client=gsm_client,
395
+ )
396
+
397
+ # Post-query filter by organization_id if provided
398
+ if organization_id is not None:
399
+ results = [
400
+ r for r in results if str(r.get("organization_id")) == organization_id
401
+ ]
402
+
403
+ return results
404
+
405
+
323
406
  def query_dataplanes_list(
324
407
  *,
325
408
  gsm_client: secretmanager.SecretManagerServiceClient | None = None,
@@ -416,3 +499,69 @@ def query_workspaces_by_email_domain(
416
499
  query_name="SELECT_WORKSPACES_BY_EMAIL_DOMAIN",
417
500
  gsm_client=gsm_client,
418
501
  )
502
+
503
+
504
+ def query_source_connection_stats(
505
+ connector_definition_id: str,
506
+ days: int = 7,
507
+ *,
508
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
509
+ ) -> list[dict[str, Any]]:
510
+ """Query aggregate connection stats for a SOURCE connector.
511
+
512
+ Returns counts of connections grouped by pinned version, including:
513
+ - Total, enabled, and active connection counts
514
+ - Pinned vs unpinned breakdown
515
+ - Latest attempt status breakdown (succeeded, failed, cancelled, running, unknown)
516
+
517
+ Args:
518
+ connector_definition_id: Source connector definition UUID
519
+ days: Number of days to look back for "active" connections (default: 7)
520
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
521
+
522
+ Returns:
523
+ List of dicts with aggregate counts grouped by pinned_version_id
524
+ """
525
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
526
+ return _run_sql_query(
527
+ SELECT_SOURCE_CONNECTION_STATS,
528
+ parameters={
529
+ "connector_definition_id": connector_definition_id,
530
+ "cutoff_date": cutoff_date,
531
+ },
532
+ query_name="SELECT_SOURCE_CONNECTION_STATS",
533
+ gsm_client=gsm_client,
534
+ )
535
+
536
+
537
+ def query_destination_connection_stats(
538
+ connector_definition_id: str,
539
+ days: int = 7,
540
+ *,
541
+ gsm_client: secretmanager.SecretManagerServiceClient | None = None,
542
+ ) -> list[dict[str, Any]]:
543
+ """Query aggregate connection stats for a DESTINATION connector.
544
+
545
+ Returns counts of connections grouped by pinned version, including:
546
+ - Total, enabled, and active connection counts
547
+ - Pinned vs unpinned breakdown
548
+ - Latest attempt status breakdown (succeeded, failed, cancelled, running, unknown)
549
+
550
+ Args:
551
+ connector_definition_id: Destination connector definition UUID
552
+ days: Number of days to look back for "active" connections (default: 7)
553
+ gsm_client: GCP Secret Manager client. If None, a new client will be instantiated.
554
+
555
+ Returns:
556
+ List of dicts with aggregate counts grouped by pinned_version_id
557
+ """
558
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
559
+ return _run_sql_query(
560
+ SELECT_DESTINATION_CONNECTION_STATS,
561
+ parameters={
562
+ "connector_definition_id": connector_definition_id,
563
+ "cutoff_date": cutoff_date,
564
+ },
565
+ query_name="SELECT_DESTINATION_CONNECTION_STATS",
566
+ gsm_client=gsm_client,
567
+ )
@@ -360,6 +360,305 @@ SELECT_SUCCESSFUL_SYNCS_FOR_VERSION = sqlalchemy.text(
360
360
  """
361
361
  )
362
362
 
363
+ # Get recent sync results for ALL actors using a SOURCE connector definition.
364
+ # Finds all actors with the given actor_definition_id and returns their sync attempts,
365
+ # regardless of whether they have explicit version pins.
366
+ # Query starts from jobs table to leverage indexed columns.
367
+ # The LEFT JOIN to scoped_configuration provides pin context when available (pin_origin_type,
368
+ # pin_origin, pinned_version_id will be NULL for unpinned actors).
369
+ # Status filtering ('all', 'succeeded', 'failed') is handled at the application layer by
370
+ # selecting among different SQL query constants; this query returns all statuses.
371
+ SELECT_RECENT_SYNCS_FOR_SOURCE_CONNECTOR = sqlalchemy.text(
372
+ """
373
+ SELECT
374
+ jobs.id AS job_id,
375
+ jobs.scope AS connection_id,
376
+ jobs.status AS job_status,
377
+ jobs.started_at AS job_started_at,
378
+ jobs.updated_at AS job_updated_at,
379
+ connection.name AS connection_name,
380
+ actor.id AS actor_id,
381
+ actor.name AS actor_name,
382
+ actor.actor_definition_id,
383
+ actor.tombstone AS actor_tombstone,
384
+ workspace.id AS workspace_id,
385
+ workspace.name AS workspace_name,
386
+ workspace.organization_id,
387
+ workspace.dataplane_group_id,
388
+ dataplane_group.name AS dataplane_name,
389
+ scoped_configuration.origin_type AS pin_origin_type,
390
+ scoped_configuration.origin AS pin_origin,
391
+ scoped_configuration.value AS pinned_version_id
392
+ FROM jobs
393
+ JOIN connection
394
+ ON jobs.scope = connection.id::text
395
+ AND connection.status != 'deprecated'
396
+ JOIN actor
397
+ ON connection.source_id = actor.id
398
+ AND actor.actor_definition_id = :connector_definition_id
399
+ AND actor.tombstone = false
400
+ JOIN workspace
401
+ ON actor.workspace_id = workspace.id
402
+ AND workspace.tombstone = false
403
+ LEFT JOIN dataplane_group
404
+ ON workspace.dataplane_group_id = dataplane_group.id
405
+ LEFT JOIN scoped_configuration
406
+ ON scoped_configuration.scope_id = actor.id
407
+ AND scoped_configuration.key = 'connector_version'
408
+ AND scoped_configuration.scope_type = 'actor'
409
+ WHERE
410
+ jobs.config_type = 'sync'
411
+ AND jobs.updated_at >= :cutoff_date
412
+ ORDER BY
413
+ jobs.updated_at DESC
414
+ LIMIT :limit
415
+ """
416
+ )
417
+
418
+ # Same as above but filtered to only successful syncs
419
+ SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_SOURCE_CONNECTOR = sqlalchemy.text(
420
+ """
421
+ SELECT
422
+ jobs.id AS job_id,
423
+ jobs.scope AS connection_id,
424
+ jobs.status AS job_status,
425
+ jobs.started_at AS job_started_at,
426
+ jobs.updated_at AS job_updated_at,
427
+ connection.name AS connection_name,
428
+ actor.id AS actor_id,
429
+ actor.name AS actor_name,
430
+ actor.actor_definition_id,
431
+ actor.tombstone AS actor_tombstone,
432
+ workspace.id AS workspace_id,
433
+ workspace.name AS workspace_name,
434
+ workspace.organization_id,
435
+ workspace.dataplane_group_id,
436
+ dataplane_group.name AS dataplane_name,
437
+ scoped_configuration.origin_type AS pin_origin_type,
438
+ scoped_configuration.origin AS pin_origin,
439
+ scoped_configuration.value AS pinned_version_id
440
+ FROM jobs
441
+ JOIN connection
442
+ ON jobs.scope = connection.id::text
443
+ AND connection.status != 'deprecated'
444
+ JOIN actor
445
+ ON connection.source_id = actor.id
446
+ AND actor.actor_definition_id = :connector_definition_id
447
+ AND actor.tombstone = false
448
+ JOIN workspace
449
+ ON actor.workspace_id = workspace.id
450
+ AND workspace.tombstone = false
451
+ LEFT JOIN dataplane_group
452
+ ON workspace.dataplane_group_id = dataplane_group.id
453
+ LEFT JOIN scoped_configuration
454
+ ON scoped_configuration.scope_id = actor.id
455
+ AND scoped_configuration.key = 'connector_version'
456
+ AND scoped_configuration.scope_type = 'actor'
457
+ WHERE
458
+ jobs.config_type = 'sync'
459
+ AND jobs.status = 'succeeded'
460
+ AND jobs.updated_at >= :cutoff_date
461
+ ORDER BY
462
+ jobs.updated_at DESC
463
+ LIMIT :limit
464
+ """
465
+ )
466
+
467
+ # Same as above but filtered to only failed syncs
468
+ SELECT_RECENT_FAILED_SYNCS_FOR_SOURCE_CONNECTOR = sqlalchemy.text(
469
+ """
470
+ SELECT
471
+ jobs.id AS job_id,
472
+ jobs.scope AS connection_id,
473
+ jobs.status AS job_status,
474
+ jobs.started_at AS job_started_at,
475
+ jobs.updated_at AS job_updated_at,
476
+ connection.name AS connection_name,
477
+ actor.id AS actor_id,
478
+ actor.name AS actor_name,
479
+ actor.actor_definition_id,
480
+ actor.tombstone AS actor_tombstone,
481
+ workspace.id AS workspace_id,
482
+ workspace.name AS workspace_name,
483
+ workspace.organization_id,
484
+ workspace.dataplane_group_id,
485
+ dataplane_group.name AS dataplane_name,
486
+ scoped_configuration.origin_type AS pin_origin_type,
487
+ scoped_configuration.origin AS pin_origin,
488
+ scoped_configuration.value AS pinned_version_id
489
+ FROM jobs
490
+ JOIN connection
491
+ ON jobs.scope = connection.id::text
492
+ AND connection.status != 'deprecated'
493
+ JOIN actor
494
+ ON connection.source_id = actor.id
495
+ AND actor.actor_definition_id = :connector_definition_id
496
+ AND actor.tombstone = false
497
+ JOIN workspace
498
+ ON actor.workspace_id = workspace.id
499
+ AND workspace.tombstone = false
500
+ LEFT JOIN dataplane_group
501
+ ON workspace.dataplane_group_id = dataplane_group.id
502
+ LEFT JOIN scoped_configuration
503
+ ON scoped_configuration.scope_id = actor.id
504
+ AND scoped_configuration.key = 'connector_version'
505
+ AND scoped_configuration.scope_type = 'actor'
506
+ WHERE
507
+ jobs.config_type = 'sync'
508
+ AND jobs.status = 'failed'
509
+ AND jobs.updated_at >= :cutoff_date
510
+ ORDER BY
511
+ jobs.updated_at DESC
512
+ LIMIT :limit
513
+ """
514
+ )
515
+
516
+ # Get recent sync results for ALL actors using a DESTINATION connector definition.
517
+ SELECT_RECENT_SYNCS_FOR_DESTINATION_CONNECTOR = sqlalchemy.text(
518
+ """
519
+ SELECT
520
+ jobs.id AS job_id,
521
+ jobs.scope AS connection_id,
522
+ jobs.status AS job_status,
523
+ jobs.started_at AS job_started_at,
524
+ jobs.updated_at AS job_updated_at,
525
+ connection.name AS connection_name,
526
+ actor.id AS actor_id,
527
+ actor.name AS actor_name,
528
+ actor.actor_definition_id,
529
+ actor.tombstone AS actor_tombstone,
530
+ workspace.id AS workspace_id,
531
+ workspace.name AS workspace_name,
532
+ workspace.organization_id,
533
+ workspace.dataplane_group_id,
534
+ dataplane_group.name AS dataplane_name,
535
+ scoped_configuration.origin_type AS pin_origin_type,
536
+ scoped_configuration.origin AS pin_origin,
537
+ scoped_configuration.value AS pinned_version_id
538
+ FROM jobs
539
+ JOIN connection
540
+ ON jobs.scope = connection.id::text
541
+ AND connection.status != 'deprecated'
542
+ JOIN actor
543
+ ON connection.destination_id = actor.id
544
+ AND actor.actor_definition_id = :connector_definition_id
545
+ AND actor.tombstone = false
546
+ JOIN workspace
547
+ ON actor.workspace_id = workspace.id
548
+ AND workspace.tombstone = false
549
+ LEFT JOIN dataplane_group
550
+ ON workspace.dataplane_group_id = dataplane_group.id
551
+ LEFT JOIN scoped_configuration
552
+ ON scoped_configuration.scope_id = actor.id
553
+ AND scoped_configuration.key = 'connector_version'
554
+ AND scoped_configuration.scope_type = 'actor'
555
+ WHERE
556
+ jobs.config_type = 'sync'
557
+ AND jobs.updated_at >= :cutoff_date
558
+ ORDER BY
559
+ jobs.updated_at DESC
560
+ LIMIT :limit
561
+ """
562
+ )
563
+
564
+ # Same as above but filtered to only successful syncs
565
+ SELECT_RECENT_SUCCESSFUL_SYNCS_FOR_DESTINATION_CONNECTOR = sqlalchemy.text(
566
+ """
567
+ SELECT
568
+ jobs.id AS job_id,
569
+ jobs.scope AS connection_id,
570
+ jobs.status AS job_status,
571
+ jobs.started_at AS job_started_at,
572
+ jobs.updated_at AS job_updated_at,
573
+ connection.name AS connection_name,
574
+ actor.id AS actor_id,
575
+ actor.name AS actor_name,
576
+ actor.actor_definition_id,
577
+ actor.tombstone AS actor_tombstone,
578
+ workspace.id AS workspace_id,
579
+ workspace.name AS workspace_name,
580
+ workspace.organization_id,
581
+ workspace.dataplane_group_id,
582
+ dataplane_group.name AS dataplane_name,
583
+ scoped_configuration.origin_type AS pin_origin_type,
584
+ scoped_configuration.origin AS pin_origin,
585
+ scoped_configuration.value AS pinned_version_id
586
+ FROM jobs
587
+ JOIN connection
588
+ ON jobs.scope = connection.id::text
589
+ AND connection.status != 'deprecated'
590
+ JOIN actor
591
+ ON connection.destination_id = actor.id
592
+ AND actor.actor_definition_id = :connector_definition_id
593
+ AND actor.tombstone = false
594
+ JOIN workspace
595
+ ON actor.workspace_id = workspace.id
596
+ AND workspace.tombstone = false
597
+ LEFT JOIN dataplane_group
598
+ ON workspace.dataplane_group_id = dataplane_group.id
599
+ LEFT JOIN scoped_configuration
600
+ ON scoped_configuration.scope_id = actor.id
601
+ AND scoped_configuration.key = 'connector_version'
602
+ AND scoped_configuration.scope_type = 'actor'
603
+ WHERE
604
+ jobs.config_type = 'sync'
605
+ AND jobs.status = 'succeeded'
606
+ AND jobs.updated_at >= :cutoff_date
607
+ ORDER BY
608
+ jobs.updated_at DESC
609
+ LIMIT :limit
610
+ """
611
+ )
612
+
613
+ # Same as above but filtered to only failed syncs
614
+ SELECT_RECENT_FAILED_SYNCS_FOR_DESTINATION_CONNECTOR = sqlalchemy.text(
615
+ """
616
+ SELECT
617
+ jobs.id AS job_id,
618
+ jobs.scope AS connection_id,
619
+ jobs.status AS job_status,
620
+ jobs.started_at AS job_started_at,
621
+ jobs.updated_at AS job_updated_at,
622
+ connection.name AS connection_name,
623
+ actor.id AS actor_id,
624
+ actor.name AS actor_name,
625
+ actor.actor_definition_id,
626
+ actor.tombstone AS actor_tombstone,
627
+ workspace.id AS workspace_id,
628
+ workspace.name AS workspace_name,
629
+ workspace.organization_id,
630
+ workspace.dataplane_group_id,
631
+ dataplane_group.name AS dataplane_name,
632
+ scoped_configuration.origin_type AS pin_origin_type,
633
+ scoped_configuration.origin AS pin_origin,
634
+ scoped_configuration.value AS pinned_version_id
635
+ FROM jobs
636
+ JOIN connection
637
+ ON jobs.scope = connection.id::text
638
+ AND connection.status != 'deprecated'
639
+ JOIN actor
640
+ ON connection.destination_id = actor.id
641
+ AND actor.actor_definition_id = :connector_definition_id
642
+ AND actor.tombstone = false
643
+ JOIN workspace
644
+ ON actor.workspace_id = workspace.id
645
+ AND workspace.tombstone = false
646
+ LEFT JOIN dataplane_group
647
+ ON workspace.dataplane_group_id = dataplane_group.id
648
+ LEFT JOIN scoped_configuration
649
+ ON scoped_configuration.scope_id = actor.id
650
+ AND scoped_configuration.key = 'connector_version'
651
+ AND scoped_configuration.scope_type = 'actor'
652
+ WHERE
653
+ jobs.config_type = 'sync'
654
+ AND jobs.status = 'failed'
655
+ AND jobs.updated_at >= :cutoff_date
656
+ ORDER BY
657
+ jobs.updated_at DESC
658
+ LIMIT :limit
659
+ """
660
+ )
661
+
363
662
  # Get failed attempt results for ALL actors using a connector definition.
364
663
  # Finds all actors with the given actor_definition_id and returns their failed sync attempts,
365
664
  # regardless of whether they have explicit version pins.
@@ -527,3 +826,111 @@ SELECT_WORKSPACES_BY_EMAIL_DOMAIN = sqlalchemy.text(
527
826
  LIMIT :limit
528
827
  """
529
828
  )
829
+
830
+ # =============================================================================
831
+ # Connector Connection Stats Queries (Aggregate Counts)
832
+ # =============================================================================
833
+
834
+ # Count connections by SOURCE connector with latest attempt status breakdown
835
+ # Groups by pinned version and provides counts of succeeded/failed/other attempts
836
+ # Uses a CTE to get the latest attempt per connection, then aggregates
837
+ SELECT_SOURCE_CONNECTION_STATS = sqlalchemy.text(
838
+ """
839
+ WITH latest_attempts AS (
840
+ SELECT DISTINCT ON (connection.id)
841
+ connection.id AS connection_id,
842
+ connection.status AS connection_status,
843
+ scoped_configuration.value AS pinned_version_id,
844
+ attempts.status::text AS latest_attempt_status
845
+ FROM connection
846
+ JOIN actor
847
+ ON connection.source_id = actor.id
848
+ AND actor.actor_definition_id = :connector_definition_id
849
+ AND actor.tombstone = false
850
+ JOIN workspace
851
+ ON actor.workspace_id = workspace.id
852
+ AND workspace.tombstone = false
853
+ LEFT JOIN scoped_configuration
854
+ ON scoped_configuration.scope_id = actor.id
855
+ AND scoped_configuration.key = 'connector_version'
856
+ AND scoped_configuration.scope_type = 'actor'
857
+ LEFT JOIN jobs
858
+ ON jobs.scope = connection.id::text
859
+ AND jobs.config_type = 'sync'
860
+ AND jobs.updated_at >= :cutoff_date
861
+ LEFT JOIN attempts
862
+ ON attempts.job_id = jobs.id
863
+ WHERE
864
+ connection.status != 'deprecated'
865
+ ORDER BY
866
+ connection.id,
867
+ attempts.ended_at DESC NULLS LAST
868
+ )
869
+ SELECT
870
+ pinned_version_id,
871
+ COUNT(*) AS total_connections,
872
+ COUNT(*) FILTER (WHERE connection_status = 'active') AS enabled_connections,
873
+ COUNT(*) FILTER (WHERE latest_attempt_status IS NOT NULL) AS active_connections,
874
+ COUNT(*) FILTER (WHERE pinned_version_id IS NOT NULL) AS pinned_connections,
875
+ COUNT(*) FILTER (WHERE pinned_version_id IS NULL) AS unpinned_connections,
876
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'succeeded') AS succeeded_connections,
877
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'failed') AS failed_connections,
878
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'cancelled') AS cancelled_connections,
879
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'running') AS running_connections,
880
+ COUNT(*) FILTER (WHERE latest_attempt_status IS NULL) AS unknown_connections
881
+ FROM latest_attempts
882
+ GROUP BY pinned_version_id
883
+ ORDER BY total_connections DESC
884
+ """
885
+ )
886
+
887
+ # Count connections by DESTINATION connector with latest attempt status breakdown
888
+ SELECT_DESTINATION_CONNECTION_STATS = sqlalchemy.text(
889
+ """
890
+ WITH latest_attempts AS (
891
+ SELECT DISTINCT ON (connection.id)
892
+ connection.id AS connection_id,
893
+ connection.status AS connection_status,
894
+ scoped_configuration.value AS pinned_version_id,
895
+ attempts.status::text AS latest_attempt_status
896
+ FROM connection
897
+ JOIN actor
898
+ ON connection.destination_id = actor.id
899
+ AND actor.actor_definition_id = :connector_definition_id
900
+ AND actor.tombstone = false
901
+ JOIN workspace
902
+ ON actor.workspace_id = workspace.id
903
+ AND workspace.tombstone = false
904
+ LEFT JOIN scoped_configuration
905
+ ON scoped_configuration.scope_id = actor.id
906
+ AND scoped_configuration.key = 'connector_version'
907
+ AND scoped_configuration.scope_type = 'actor'
908
+ LEFT JOIN jobs
909
+ ON jobs.scope = connection.id::text
910
+ AND jobs.config_type = 'sync'
911
+ AND jobs.updated_at >= :cutoff_date
912
+ LEFT JOIN attempts
913
+ ON attempts.job_id = jobs.id
914
+ WHERE
915
+ connection.status != 'deprecated'
916
+ ORDER BY
917
+ connection.id,
918
+ attempts.ended_at DESC NULLS LAST
919
+ )
920
+ SELECT
921
+ pinned_version_id,
922
+ COUNT(*) AS total_connections,
923
+ COUNT(*) FILTER (WHERE connection_status = 'active') AS enabled_connections,
924
+ COUNT(*) FILTER (WHERE latest_attempt_status IS NOT NULL) AS active_connections,
925
+ COUNT(*) FILTER (WHERE pinned_version_id IS NOT NULL) AS pinned_connections,
926
+ COUNT(*) FILTER (WHERE pinned_version_id IS NULL) AS unpinned_connections,
927
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'succeeded') AS succeeded_connections,
928
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'failed') AS failed_connections,
929
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'cancelled') AS cancelled_connections,
930
+ COUNT(*) FILTER (WHERE latest_attempt_status = 'running') AS running_connections,
931
+ COUNT(*) FILTER (WHERE latest_attempt_status IS NULL) AS unknown_connections
932
+ FROM latest_attempts
933
+ GROUP BY pinned_version_id
934
+ ORDER BY total_connections DESC
935
+ """
936
+ )
@@ -39,7 +39,6 @@ from airbyte_ops_mcp.connection_config_retriever import (
39
39
  ConnectionObject,
40
40
  retrieve_objects,
41
41
  )
42
- from airbyte_ops_mcp.gcp_auth import ensure_adc_credentials
43
42
 
44
43
  if TYPE_CHECKING:
45
44
  from airbyte_ops_mcp.regression_tests.connection_fetcher import ConnectionData
@@ -85,9 +84,6 @@ def retrieve_unmasked_config(
85
84
  Returns:
86
85
  The unmasked source config dict, or None if retrieval fails.
87
86
  """
88
- # Ensure GCP credentials are available (supports GCP_PROD_DB_ACCESS_CREDENTIALS fallback)
89
- ensure_adc_credentials()
90
-
91
87
  # Only request the source config - that's all we need for secrets
92
88
  requested_objects = [ConnectionObject.SOURCE_CONFIG]
93
89
 
@@ -105,16 +105,19 @@ class ConnectorRunner:
105
105
  if self.config is not None:
106
106
  config_path = temp_dir / self.CONFIG_FILE
107
107
  config_path.write_text(json.dumps(self.config))
108
+ config_path.chmod(0o666)
108
109
  self.logger.debug(f"Wrote config to {config_path}")
109
110
 
110
111
  if self.configured_catalog is not None:
111
112
  catalog_path = temp_dir / self.CATALOG_FILE
112
113
  catalog_path.write_text(self.configured_catalog.json())
114
+ catalog_path.chmod(0o666)
113
115
  self.logger.debug(f"Wrote catalog to {catalog_path}")
114
116
 
115
117
  if self.state is not None:
116
118
  state_path = temp_dir / self.STATE_FILE
117
119
  state_path.write_text(json.dumps(self.state))
120
+ state_path.chmod(0o666)
118
121
  self.logger.debug(f"Wrote state to {state_path}")
119
122
 
120
123
  def _build_docker_command(self, temp_dir: Path) -> list[str]:
@@ -135,7 +138,7 @@ class ConnectorRunner:
135
138
  "--name",
136
139
  container_name,
137
140
  "-v",
138
- f"{temp_dir}:{self.DATA_DIR}:ro",
141
+ f"{temp_dir}:{self.DATA_DIR}",
139
142
  ]
140
143
 
141
144
  if self.proxy_url:
@@ -168,9 +171,10 @@ class ConnectorRunner:
168
171
 
169
172
  with tempfile.TemporaryDirectory() as temp_dir:
170
173
  temp_path = Path(temp_dir)
171
- # Make temp directory world-readable so non-root container users can access it
172
- # Many connector images run as non-root users (e.g., 'airbyte' user)
173
- temp_path.chmod(0o755)
174
+ # Make temp directory world-writable so non-root container users can read/write
175
+ # Many connector images run as non-root users (e.g., 'airbyte' user) with
176
+ # different UIDs than the host user, so they need write access for config migration
177
+ temp_path.chmod(0o777)
174
178
  self._prepare_data_directory(temp_path)
175
179
 
176
180
  docker_cmd = self._build_docker_command(temp_path)