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.
- {airbyte_internal_ops-0.3.0.dist-info → airbyte_internal_ops-0.4.0.dist-info}/METADATA +1 -1
- {airbyte_internal_ops-0.3.0.dist-info → airbyte_internal_ops-0.4.0.dist-info}/RECORD +14 -14
- airbyte_ops_mcp/connection_config_retriever/audit_logging.py +4 -8
- airbyte_ops_mcp/gcp_auth.py +142 -49
- airbyte_ops_mcp/gcp_logs/error_lookup.py +6 -5
- airbyte_ops_mcp/mcp/prerelease.py +48 -11
- airbyte_ops_mcp/mcp/prod_db_queries.py +478 -8
- airbyte_ops_mcp/mcp/regression_tests.py +16 -3
- airbyte_ops_mcp/prod_db_access/queries.py +150 -1
- airbyte_ops_mcp/prod_db_access/sql.py +407 -0
- airbyte_ops_mcp/regression_tests/connection_secret_retriever.py +0 -4
- airbyte_ops_mcp/regression_tests/connector_runner.py +8 -4
- {airbyte_internal_ops-0.3.0.dist-info → airbyte_internal_ops-0.4.0.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.3.0.dist-info → airbyte_internal_ops-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -7,7 +7,8 @@ airbyte_ops_mcp.prod_db_access.queries for use by AI agents.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from enum import StrEnum
|
|
11
12
|
from typing import Annotated, Any
|
|
12
13
|
|
|
13
14
|
import requests
|
|
@@ -23,13 +24,25 @@ from airbyte_ops_mcp.prod_db_access.queries import (
|
|
|
23
24
|
query_connections_by_destination_connector,
|
|
24
25
|
query_connector_versions,
|
|
25
26
|
query_dataplanes_list,
|
|
27
|
+
query_destination_connection_stats,
|
|
26
28
|
query_failed_sync_attempts_for_connector,
|
|
27
29
|
query_new_connector_releases,
|
|
28
|
-
|
|
30
|
+
query_recent_syncs_for_connector,
|
|
31
|
+
query_source_connection_stats,
|
|
32
|
+
query_syncs_for_version_pinned_connector,
|
|
29
33
|
query_workspace_info,
|
|
30
34
|
query_workspaces_by_email_domain,
|
|
31
35
|
)
|
|
32
36
|
|
|
37
|
+
|
|
38
|
+
class StatusFilter(StrEnum):
|
|
39
|
+
"""Filter for job status in sync queries."""
|
|
40
|
+
|
|
41
|
+
ALL = "all"
|
|
42
|
+
SUCCEEDED = "succeeded"
|
|
43
|
+
FAILED = "failed"
|
|
44
|
+
|
|
45
|
+
|
|
33
46
|
# Cloud UI base URL for building connection URLs
|
|
34
47
|
CLOUD_UI_BASE_URL = "https://cloud.airbyte.com"
|
|
35
48
|
|
|
@@ -79,6 +92,94 @@ class WorkspacesByEmailDomainResult(BaseModel):
|
|
|
79
92
|
)
|
|
80
93
|
|
|
81
94
|
|
|
95
|
+
class LatestAttemptBreakdown(BaseModel):
|
|
96
|
+
"""Breakdown of connections by latest attempt status."""
|
|
97
|
+
|
|
98
|
+
succeeded: int = Field(
|
|
99
|
+
default=0, description="Connections where latest attempt succeeded"
|
|
100
|
+
)
|
|
101
|
+
failed: int = Field(
|
|
102
|
+
default=0, description="Connections where latest attempt failed"
|
|
103
|
+
)
|
|
104
|
+
cancelled: int = Field(
|
|
105
|
+
default=0, description="Connections where latest attempt was cancelled"
|
|
106
|
+
)
|
|
107
|
+
running: int = Field(
|
|
108
|
+
default=0, description="Connections where latest attempt is still running"
|
|
109
|
+
)
|
|
110
|
+
unknown: int = Field(
|
|
111
|
+
default=0,
|
|
112
|
+
description="Connections with no recent attempts in the lookback window",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class VersionPinStats(BaseModel):
|
|
117
|
+
"""Stats for connections pinned to a specific version."""
|
|
118
|
+
|
|
119
|
+
pinned_version_id: str | None = Field(
|
|
120
|
+
description="The connector version UUID (None for unpinned connections)"
|
|
121
|
+
)
|
|
122
|
+
docker_image_tag: str | None = Field(
|
|
123
|
+
default=None, description="The docker image tag for this version"
|
|
124
|
+
)
|
|
125
|
+
total_connections: int = Field(description="Total number of connections")
|
|
126
|
+
enabled_connections: int = Field(
|
|
127
|
+
description="Number of enabled (active status) connections"
|
|
128
|
+
)
|
|
129
|
+
active_connections: int = Field(
|
|
130
|
+
description="Number of connections with recent sync activity"
|
|
131
|
+
)
|
|
132
|
+
latest_attempt: LatestAttemptBreakdown = Field(
|
|
133
|
+
description="Breakdown by latest attempt status"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ConnectorConnectionStats(BaseModel):
|
|
138
|
+
"""Aggregate connection stats for a connector."""
|
|
139
|
+
|
|
140
|
+
connector_definition_id: str = Field(description="The connector definition UUID")
|
|
141
|
+
connector_type: str = Field(description="'source' or 'destination'")
|
|
142
|
+
canonical_name: str | None = Field(
|
|
143
|
+
default=None, description="The canonical connector name if resolved"
|
|
144
|
+
)
|
|
145
|
+
total_connections: int = Field(
|
|
146
|
+
description="Total number of non-deprecated connections"
|
|
147
|
+
)
|
|
148
|
+
enabled_connections: int = Field(
|
|
149
|
+
description="Number of enabled (active status) connections"
|
|
150
|
+
)
|
|
151
|
+
active_connections: int = Field(
|
|
152
|
+
description="Number of connections with recent sync activity"
|
|
153
|
+
)
|
|
154
|
+
pinned_connections: int = Field(
|
|
155
|
+
description="Number of connections with explicit version pins"
|
|
156
|
+
)
|
|
157
|
+
unpinned_connections: int = Field(
|
|
158
|
+
description="Number of connections on default version"
|
|
159
|
+
)
|
|
160
|
+
latest_attempt: LatestAttemptBreakdown = Field(
|
|
161
|
+
description="Overall breakdown by latest attempt status"
|
|
162
|
+
)
|
|
163
|
+
by_version: list[VersionPinStats] = Field(
|
|
164
|
+
description="Stats broken down by pinned version"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ConnectorConnectionStatsResponse(BaseModel):
|
|
169
|
+
"""Response containing connection stats for multiple connectors."""
|
|
170
|
+
|
|
171
|
+
sources: list[ConnectorConnectionStats] = Field(
|
|
172
|
+
default_factory=list, description="Stats for source connectors"
|
|
173
|
+
)
|
|
174
|
+
destinations: list[ConnectorConnectionStats] = Field(
|
|
175
|
+
default_factory=list, description="Stats for destination connectors"
|
|
176
|
+
)
|
|
177
|
+
active_within_days: int = Field(
|
|
178
|
+
description="Lookback window used for 'active' connections"
|
|
179
|
+
)
|
|
180
|
+
generated_at: datetime = Field(description="When this response was generated")
|
|
181
|
+
|
|
182
|
+
|
|
82
183
|
# Cloud registry URL for resolving canonical names
|
|
83
184
|
CLOUD_REGISTRY_URL = (
|
|
84
185
|
"https://connectors.airbyte.com/files/registries/v0/cloud_registry.json"
|
|
@@ -293,7 +394,7 @@ def query_prod_actors_by_connector_version(
|
|
|
293
394
|
read_only=True,
|
|
294
395
|
idempotent=True,
|
|
295
396
|
)
|
|
296
|
-
def
|
|
397
|
+
def query_prod_recent_syncs_for_version_pinned_connector(
|
|
297
398
|
connector_version_id: Annotated[
|
|
298
399
|
str,
|
|
299
400
|
Field(description="Connector version UUID to find sync results for"),
|
|
@@ -314,11 +415,16 @@ def query_prod_connector_version_sync_results(
|
|
|
314
415
|
),
|
|
315
416
|
] = False,
|
|
316
417
|
) -> list[dict[str, Any]]:
|
|
317
|
-
"""List sync job results for actors
|
|
418
|
+
"""List sync job results for actors PINNED to a specific connector version.
|
|
419
|
+
|
|
420
|
+
IMPORTANT: This tool ONLY returns results for actors that have been explicitly
|
|
421
|
+
pinned to the specified version via scoped_configuration. Most connections run
|
|
422
|
+
unpinned and will NOT appear in these results.
|
|
318
423
|
|
|
319
|
-
|
|
320
|
-
to
|
|
321
|
-
|
|
424
|
+
Use this tool when you want to monitor rollout health for actors that have been
|
|
425
|
+
explicitly pinned to a pre-release or specific version. For finding healthy
|
|
426
|
+
connections across ALL actors using a connector type (regardless of pinning),
|
|
427
|
+
use query_prod_recent_syncs_for_connector instead.
|
|
322
428
|
|
|
323
429
|
The actor_id field is the actor ID (superset of source_id/destination_id).
|
|
324
430
|
|
|
@@ -327,7 +433,7 @@ def query_prod_connector_version_sync_results(
|
|
|
327
433
|
pin_origin_type, pin_origin, workspace_id, workspace_name, organization_id,
|
|
328
434
|
dataplane_group_id, dataplane_name
|
|
329
435
|
"""
|
|
330
|
-
return
|
|
436
|
+
return query_syncs_for_version_pinned_connector(
|
|
331
437
|
connector_version_id,
|
|
332
438
|
days=days,
|
|
333
439
|
limit=limit,
|
|
@@ -335,6 +441,163 @@ def query_prod_connector_version_sync_results(
|
|
|
335
441
|
)
|
|
336
442
|
|
|
337
443
|
|
|
444
|
+
@mcp_tool(
|
|
445
|
+
read_only=True,
|
|
446
|
+
idempotent=True,
|
|
447
|
+
open_world=True,
|
|
448
|
+
)
|
|
449
|
+
def query_prod_recent_syncs_for_connector(
|
|
450
|
+
source_definition_id: Annotated[
|
|
451
|
+
str | None,
|
|
452
|
+
Field(
|
|
453
|
+
description=(
|
|
454
|
+
"Source connector definition ID (UUID) to search for. "
|
|
455
|
+
"Provide this OR source_canonical_name OR destination_definition_id "
|
|
456
|
+
"OR destination_canonical_name (exactly one required). "
|
|
457
|
+
"Example: 'afa734e4-3571-11ec-991a-1e0031268139' for YouTube Analytics."
|
|
458
|
+
),
|
|
459
|
+
default=None,
|
|
460
|
+
),
|
|
461
|
+
],
|
|
462
|
+
source_canonical_name: Annotated[
|
|
463
|
+
str | None,
|
|
464
|
+
Field(
|
|
465
|
+
description=(
|
|
466
|
+
"Canonical source connector name to search for. "
|
|
467
|
+
"Provide this OR source_definition_id OR destination_definition_id "
|
|
468
|
+
"OR destination_canonical_name (exactly one required). "
|
|
469
|
+
"Examples: 'source-youtube-analytics', 'YouTube Analytics'."
|
|
470
|
+
),
|
|
471
|
+
default=None,
|
|
472
|
+
),
|
|
473
|
+
],
|
|
474
|
+
destination_definition_id: Annotated[
|
|
475
|
+
str | None,
|
|
476
|
+
Field(
|
|
477
|
+
description=(
|
|
478
|
+
"Destination connector definition ID (UUID) to search for. "
|
|
479
|
+
"Provide this OR destination_canonical_name OR source_definition_id "
|
|
480
|
+
"OR source_canonical_name (exactly one required). "
|
|
481
|
+
"Example: '94bd199c-2ff0-4aa2-b98e-17f0acb72610' for DuckDB."
|
|
482
|
+
),
|
|
483
|
+
default=None,
|
|
484
|
+
),
|
|
485
|
+
],
|
|
486
|
+
destination_canonical_name: Annotated[
|
|
487
|
+
str | None,
|
|
488
|
+
Field(
|
|
489
|
+
description=(
|
|
490
|
+
"Canonical destination connector name to search for. "
|
|
491
|
+
"Provide this OR destination_definition_id OR source_definition_id "
|
|
492
|
+
"OR source_canonical_name (exactly one required). "
|
|
493
|
+
"Examples: 'destination-duckdb', 'DuckDB'."
|
|
494
|
+
),
|
|
495
|
+
default=None,
|
|
496
|
+
),
|
|
497
|
+
],
|
|
498
|
+
status_filter: Annotated[
|
|
499
|
+
StatusFilter,
|
|
500
|
+
Field(
|
|
501
|
+
description=(
|
|
502
|
+
"Filter by job status: 'all' (default), 'succeeded', or 'failed'. "
|
|
503
|
+
"Use 'succeeded' to find healthy connections with recent successful syncs. "
|
|
504
|
+
"Use 'failed' to find connections with recent failures."
|
|
505
|
+
),
|
|
506
|
+
default=StatusFilter.ALL,
|
|
507
|
+
),
|
|
508
|
+
],
|
|
509
|
+
organization_id: Annotated[
|
|
510
|
+
str | OrganizationAliasEnum | None,
|
|
511
|
+
Field(
|
|
512
|
+
description=(
|
|
513
|
+
"Optional organization ID (UUID) or alias to filter results. "
|
|
514
|
+
"If provided, only syncs from this organization will be returned. "
|
|
515
|
+
"Accepts '@airbyte-internal' as an alias for the Airbyte internal org."
|
|
516
|
+
),
|
|
517
|
+
default=None,
|
|
518
|
+
),
|
|
519
|
+
],
|
|
520
|
+
lookback_days: Annotated[
|
|
521
|
+
int,
|
|
522
|
+
Field(description="Number of days to look back (default: 7)", default=7),
|
|
523
|
+
],
|
|
524
|
+
limit: Annotated[
|
|
525
|
+
int,
|
|
526
|
+
Field(description="Maximum number of results (default: 100)", default=100),
|
|
527
|
+
],
|
|
528
|
+
) -> list[dict[str, Any]]:
|
|
529
|
+
"""List recent sync jobs for ALL actors using a connector type.
|
|
530
|
+
|
|
531
|
+
This tool finds all actors with the given connector definition and returns their
|
|
532
|
+
recent sync jobs, regardless of whether they have explicit version pins. It filters
|
|
533
|
+
out deleted actors, deleted workspaces, and deprecated connections.
|
|
534
|
+
|
|
535
|
+
Use this tool to:
|
|
536
|
+
- Find healthy connections with recent successful syncs (status_filter='succeeded')
|
|
537
|
+
- Investigate connector issues across all users (status_filter='failed')
|
|
538
|
+
- Get an overview of all recent sync activity (status_filter='all')
|
|
539
|
+
|
|
540
|
+
Supports both SOURCE and DESTINATION connectors. Provide exactly one of:
|
|
541
|
+
source_definition_id, source_canonical_name, destination_definition_id,
|
|
542
|
+
or destination_canonical_name.
|
|
543
|
+
|
|
544
|
+
Key fields in results:
|
|
545
|
+
- job_status: 'succeeded', 'failed', 'cancelled', etc.
|
|
546
|
+
- connection_id, connection_name: The connection that ran the sync
|
|
547
|
+
- actor_id, actor_name: The source or destination actor
|
|
548
|
+
- pin_origin_type, pin_origin, pinned_version_id: Version pin context (NULL if not pinned)
|
|
549
|
+
"""
|
|
550
|
+
# Validate that exactly one connector parameter is provided
|
|
551
|
+
provided_params = [
|
|
552
|
+
source_definition_id,
|
|
553
|
+
source_canonical_name,
|
|
554
|
+
destination_definition_id,
|
|
555
|
+
destination_canonical_name,
|
|
556
|
+
]
|
|
557
|
+
num_provided = sum(p is not None for p in provided_params)
|
|
558
|
+
if num_provided != 1:
|
|
559
|
+
raise PyAirbyteInputError(
|
|
560
|
+
message=(
|
|
561
|
+
"Exactly one of source_definition_id, source_canonical_name, "
|
|
562
|
+
"destination_definition_id, or destination_canonical_name must be provided."
|
|
563
|
+
),
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Determine if this is a destination connector
|
|
567
|
+
is_destination = (
|
|
568
|
+
destination_definition_id is not None or destination_canonical_name is not None
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Resolve canonical name to definition ID if needed
|
|
572
|
+
resolved_definition_id: str
|
|
573
|
+
if source_canonical_name:
|
|
574
|
+
resolved_definition_id = _resolve_canonical_name_to_definition_id(
|
|
575
|
+
canonical_name=source_canonical_name,
|
|
576
|
+
)
|
|
577
|
+
elif destination_canonical_name:
|
|
578
|
+
resolved_definition_id = _resolve_canonical_name_to_definition_id(
|
|
579
|
+
canonical_name=destination_canonical_name,
|
|
580
|
+
)
|
|
581
|
+
elif source_definition_id:
|
|
582
|
+
resolved_definition_id = source_definition_id
|
|
583
|
+
else:
|
|
584
|
+
# We've validated exactly one param is provided, so this must be set
|
|
585
|
+
assert destination_definition_id is not None
|
|
586
|
+
resolved_definition_id = destination_definition_id
|
|
587
|
+
|
|
588
|
+
# Resolve organization ID alias
|
|
589
|
+
resolved_organization_id = OrganizationAliasEnum.resolve(organization_id)
|
|
590
|
+
|
|
591
|
+
return query_recent_syncs_for_connector(
|
|
592
|
+
connector_definition_id=resolved_definition_id,
|
|
593
|
+
is_destination=is_destination,
|
|
594
|
+
status_filter=status_filter,
|
|
595
|
+
organization_id=resolved_organization_id,
|
|
596
|
+
days=lookback_days,
|
|
597
|
+
limit=limit,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
338
601
|
@mcp_tool(
|
|
339
602
|
read_only=True,
|
|
340
603
|
idempotent=True,
|
|
@@ -678,6 +941,213 @@ def query_prod_workspaces_by_email_domain(
|
|
|
678
941
|
)
|
|
679
942
|
|
|
680
943
|
|
|
944
|
+
def _build_connector_stats(
|
|
945
|
+
connector_definition_id: str,
|
|
946
|
+
connector_type: str,
|
|
947
|
+
canonical_name: str | None,
|
|
948
|
+
rows: list[dict[str, Any]],
|
|
949
|
+
version_tags: dict[str, str | None],
|
|
950
|
+
) -> ConnectorConnectionStats:
|
|
951
|
+
"""Build ConnectorConnectionStats from query result rows."""
|
|
952
|
+
# Aggregate totals across all version groups
|
|
953
|
+
total_connections = 0
|
|
954
|
+
enabled_connections = 0
|
|
955
|
+
active_connections = 0
|
|
956
|
+
pinned_connections = 0
|
|
957
|
+
unpinned_connections = 0
|
|
958
|
+
total_succeeded = 0
|
|
959
|
+
total_failed = 0
|
|
960
|
+
total_cancelled = 0
|
|
961
|
+
total_running = 0
|
|
962
|
+
total_unknown = 0
|
|
963
|
+
|
|
964
|
+
by_version: list[VersionPinStats] = []
|
|
965
|
+
|
|
966
|
+
for row in rows:
|
|
967
|
+
version_id = row.get("pinned_version_id")
|
|
968
|
+
row_total = int(row.get("total_connections", 0))
|
|
969
|
+
row_enabled = int(row.get("enabled_connections", 0))
|
|
970
|
+
row_active = int(row.get("active_connections", 0))
|
|
971
|
+
row_pinned = int(row.get("pinned_connections", 0))
|
|
972
|
+
row_unpinned = int(row.get("unpinned_connections", 0))
|
|
973
|
+
row_succeeded = int(row.get("succeeded_connections", 0))
|
|
974
|
+
row_failed = int(row.get("failed_connections", 0))
|
|
975
|
+
row_cancelled = int(row.get("cancelled_connections", 0))
|
|
976
|
+
row_running = int(row.get("running_connections", 0))
|
|
977
|
+
row_unknown = int(row.get("unknown_connections", 0))
|
|
978
|
+
|
|
979
|
+
total_connections += row_total
|
|
980
|
+
enabled_connections += row_enabled
|
|
981
|
+
active_connections += row_active
|
|
982
|
+
pinned_connections += row_pinned
|
|
983
|
+
unpinned_connections += row_unpinned
|
|
984
|
+
total_succeeded += row_succeeded
|
|
985
|
+
total_failed += row_failed
|
|
986
|
+
total_cancelled += row_cancelled
|
|
987
|
+
total_running += row_running
|
|
988
|
+
total_unknown += row_unknown
|
|
989
|
+
|
|
990
|
+
by_version.append(
|
|
991
|
+
VersionPinStats(
|
|
992
|
+
pinned_version_id=str(version_id) if version_id else None,
|
|
993
|
+
docker_image_tag=version_tags.get(str(version_id))
|
|
994
|
+
if version_id
|
|
995
|
+
else None,
|
|
996
|
+
total_connections=row_total,
|
|
997
|
+
enabled_connections=row_enabled,
|
|
998
|
+
active_connections=row_active,
|
|
999
|
+
latest_attempt=LatestAttemptBreakdown(
|
|
1000
|
+
succeeded=row_succeeded,
|
|
1001
|
+
failed=row_failed,
|
|
1002
|
+
cancelled=row_cancelled,
|
|
1003
|
+
running=row_running,
|
|
1004
|
+
unknown=row_unknown,
|
|
1005
|
+
),
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
return ConnectorConnectionStats(
|
|
1010
|
+
connector_definition_id=connector_definition_id,
|
|
1011
|
+
connector_type=connector_type,
|
|
1012
|
+
canonical_name=canonical_name,
|
|
1013
|
+
total_connections=total_connections,
|
|
1014
|
+
enabled_connections=enabled_connections,
|
|
1015
|
+
active_connections=active_connections,
|
|
1016
|
+
pinned_connections=pinned_connections,
|
|
1017
|
+
unpinned_connections=unpinned_connections,
|
|
1018
|
+
latest_attempt=LatestAttemptBreakdown(
|
|
1019
|
+
succeeded=total_succeeded,
|
|
1020
|
+
failed=total_failed,
|
|
1021
|
+
cancelled=total_cancelled,
|
|
1022
|
+
running=total_running,
|
|
1023
|
+
unknown=total_unknown,
|
|
1024
|
+
),
|
|
1025
|
+
by_version=by_version,
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
@mcp_tool(
|
|
1030
|
+
read_only=True,
|
|
1031
|
+
idempotent=True,
|
|
1032
|
+
open_world=True,
|
|
1033
|
+
)
|
|
1034
|
+
def query_prod_connector_connection_stats(
|
|
1035
|
+
source_definition_ids: Annotated[
|
|
1036
|
+
list[str] | None,
|
|
1037
|
+
Field(
|
|
1038
|
+
description=(
|
|
1039
|
+
"List of source connector definition IDs (UUIDs) to get stats for. "
|
|
1040
|
+
"Example: ['afa734e4-3571-11ec-991a-1e0031268139']"
|
|
1041
|
+
),
|
|
1042
|
+
default=None,
|
|
1043
|
+
),
|
|
1044
|
+
] = None,
|
|
1045
|
+
destination_definition_ids: Annotated[
|
|
1046
|
+
list[str] | None,
|
|
1047
|
+
Field(
|
|
1048
|
+
description=(
|
|
1049
|
+
"List of destination connector definition IDs (UUIDs) to get stats for. "
|
|
1050
|
+
"Example: ['94bd199c-2ff0-4aa2-b98e-17f0acb72610']"
|
|
1051
|
+
),
|
|
1052
|
+
default=None,
|
|
1053
|
+
),
|
|
1054
|
+
] = None,
|
|
1055
|
+
active_within_days: Annotated[
|
|
1056
|
+
int,
|
|
1057
|
+
Field(
|
|
1058
|
+
description=(
|
|
1059
|
+
"Number of days to look back for 'active' connections (default: 7). "
|
|
1060
|
+
"Connections with sync activity within this window are counted as active."
|
|
1061
|
+
),
|
|
1062
|
+
default=7,
|
|
1063
|
+
),
|
|
1064
|
+
] = 7,
|
|
1065
|
+
) -> ConnectorConnectionStatsResponse:
|
|
1066
|
+
"""Get aggregate connection stats for multiple connectors.
|
|
1067
|
+
|
|
1068
|
+
Returns counts of connections grouped by pinned version for each connector,
|
|
1069
|
+
including:
|
|
1070
|
+
- Total, enabled, and active connection counts
|
|
1071
|
+
- Pinned vs unpinned breakdown
|
|
1072
|
+
- Latest attempt status breakdown (succeeded, failed, cancelled, running, unknown)
|
|
1073
|
+
|
|
1074
|
+
This tool is designed for release monitoring workflows. It allows you to:
|
|
1075
|
+
1. Query recently released connectors to identify which ones to monitor
|
|
1076
|
+
2. Get aggregate stats showing how many connections are using each version
|
|
1077
|
+
3. See health metrics (pass/fail) broken down by version
|
|
1078
|
+
|
|
1079
|
+
The 'active_within_days' parameter controls the lookback window for:
|
|
1080
|
+
- Counting 'active' connections (those with recent sync activity)
|
|
1081
|
+
- Determining 'latest attempt status' (most recent attempt within the window)
|
|
1082
|
+
|
|
1083
|
+
Connections with no sync activity in the lookback window will have
|
|
1084
|
+
'unknown' status in the latest_attempt breakdown.
|
|
1085
|
+
"""
|
|
1086
|
+
# Initialize empty lists if None
|
|
1087
|
+
source_ids = source_definition_ids or []
|
|
1088
|
+
destination_ids = destination_definition_ids or []
|
|
1089
|
+
|
|
1090
|
+
if not source_ids and not destination_ids:
|
|
1091
|
+
raise PyAirbyteInputError(
|
|
1092
|
+
message=(
|
|
1093
|
+
"At least one of source_definition_ids or destination_definition_ids "
|
|
1094
|
+
"must be provided."
|
|
1095
|
+
),
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
sources: list[ConnectorConnectionStats] = []
|
|
1099
|
+
destinations: list[ConnectorConnectionStats] = []
|
|
1100
|
+
|
|
1101
|
+
# Process source connectors
|
|
1102
|
+
for source_def_id in source_ids:
|
|
1103
|
+
# Get version info for tag lookup
|
|
1104
|
+
versions = query_connector_versions(source_def_id)
|
|
1105
|
+
version_tags = {
|
|
1106
|
+
str(v["version_id"]): v.get("docker_image_tag") for v in versions
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
# Get aggregate stats
|
|
1110
|
+
rows = query_source_connection_stats(source_def_id, days=active_within_days)
|
|
1111
|
+
|
|
1112
|
+
sources.append(
|
|
1113
|
+
_build_connector_stats(
|
|
1114
|
+
connector_definition_id=source_def_id,
|
|
1115
|
+
connector_type="source",
|
|
1116
|
+
canonical_name=None,
|
|
1117
|
+
rows=rows,
|
|
1118
|
+
version_tags=version_tags,
|
|
1119
|
+
)
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
# Process destination connectors
|
|
1123
|
+
for dest_def_id in destination_ids:
|
|
1124
|
+
# Get version info for tag lookup
|
|
1125
|
+
versions = query_connector_versions(dest_def_id)
|
|
1126
|
+
version_tags = {
|
|
1127
|
+
str(v["version_id"]): v.get("docker_image_tag") for v in versions
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
# Get aggregate stats
|
|
1131
|
+
rows = query_destination_connection_stats(dest_def_id, days=active_within_days)
|
|
1132
|
+
|
|
1133
|
+
destinations.append(
|
|
1134
|
+
_build_connector_stats(
|
|
1135
|
+
connector_definition_id=dest_def_id,
|
|
1136
|
+
connector_type="destination",
|
|
1137
|
+
canonical_name=None,
|
|
1138
|
+
rows=rows,
|
|
1139
|
+
version_tags=version_tags,
|
|
1140
|
+
)
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
return ConnectorConnectionStatsResponse(
|
|
1144
|
+
sources=sources,
|
|
1145
|
+
destinations=destinations,
|
|
1146
|
+
active_within_days=active_within_days,
|
|
1147
|
+
generated_at=datetime.now(timezone.utc),
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
|
|
681
1151
|
def register_prod_db_query_tools(app: FastMCP) -> None:
|
|
682
1152
|
"""Register prod DB query tools with the FastMCP app."""
|
|
683
1153
|
register_mcp_tools(app, domain=__name__)
|
|
@@ -37,6 +37,7 @@ from airbyte_ops_mcp.github_actions import (
|
|
|
37
37
|
trigger_workflow_dispatch,
|
|
38
38
|
)
|
|
39
39
|
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
40
|
+
from airbyte_ops_mcp.mcp.prerelease import ConnectorRepo
|
|
40
41
|
|
|
41
42
|
logger = logging.getLogger(__name__)
|
|
42
43
|
|
|
@@ -309,7 +310,13 @@ def run_regression_tests(
|
|
|
309
310
|
],
|
|
310
311
|
pr: Annotated[
|
|
311
312
|
int,
|
|
312
|
-
"PR number
|
|
313
|
+
"PR number to checkout and build from (e.g., 70847). Required. "
|
|
314
|
+
"The PR must be from the repository specified by the 'repo' parameter.",
|
|
315
|
+
],
|
|
316
|
+
repo: Annotated[
|
|
317
|
+
ConnectorRepo,
|
|
318
|
+
"Repository where the connector PR is located. "
|
|
319
|
+
"Use 'airbyte' for OSS connectors (default) or 'airbyte-enterprise' for enterprise connectors.",
|
|
313
320
|
],
|
|
314
321
|
connection_id: Annotated[
|
|
315
322
|
str | None,
|
|
@@ -347,6 +354,10 @@ def run_regression_tests(
|
|
|
347
354
|
This tool triggers the regression test workflow which builds the connector
|
|
348
355
|
from the specified PR and runs tests against it.
|
|
349
356
|
|
|
357
|
+
Supports both OSS connectors (from airbytehq/airbyte) and enterprise connectors
|
|
358
|
+
(from airbytehq/airbyte-enterprise). Use the 'repo' parameter to specify which
|
|
359
|
+
repository contains the connector PR.
|
|
360
|
+
|
|
350
361
|
- skip_compare=False (default): Comparison mode - compares the PR version
|
|
351
362
|
against the baseline (control) version.
|
|
352
363
|
- skip_compare=True: Single-version mode - runs tests without comparison.
|
|
@@ -390,10 +401,11 @@ def run_regression_tests(
|
|
|
390
401
|
workflow_url=None,
|
|
391
402
|
)
|
|
392
403
|
|
|
393
|
-
# Build workflow inputs - connector_name and
|
|
404
|
+
# Build workflow inputs - connector_name, pr, and repo are required
|
|
394
405
|
workflow_inputs: dict[str, str] = {
|
|
395
406
|
"connector_name": connector_name,
|
|
396
407
|
"pr": str(pr),
|
|
408
|
+
"repo": repo,
|
|
397
409
|
}
|
|
398
410
|
|
|
399
411
|
# Add optional inputs
|
|
@@ -431,12 +443,13 @@ def run_regression_tests(
|
|
|
431
443
|
|
|
432
444
|
view_url = dispatch_result.run_url or dispatch_result.workflow_url
|
|
433
445
|
connection_info = f" for connection {connection_id}" if connection_id else ""
|
|
446
|
+
repo_info = f" from {repo}" if repo != ConnectorRepo.AIRBYTE else ""
|
|
434
447
|
return RunRegressionTestsResponse(
|
|
435
448
|
run_id=run_id,
|
|
436
449
|
status=TestRunStatus.QUEUED,
|
|
437
450
|
message=(
|
|
438
451
|
f"{mode_description.capitalize()} regression test workflow triggered "
|
|
439
|
-
f"for {connector_name} (PR #{pr}){connection_info}. View progress at: {view_url}"
|
|
452
|
+
f"for {connector_name} (PR #{pr}{repo_info}){connection_info}. View progress at: {view_url}"
|
|
440
453
|
),
|
|
441
454
|
workflow_url=dispatch_result.workflow_url,
|
|
442
455
|
github_run_id=dispatch_result.run_id,
|