mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-13 01:38:41 +00:00
Deployments were only countable through two span types that no aggregation reads, and cli_usage:deploy counted the TUI button rather than deployments. Emit deploy:created and deploy:pushed alongside the existing spans so a deployment is countable from the feature-usage aggregation no matter how it was started, and tag Create Crew Deployment / Start Deployment with source=cli|tui so the two origins stay distinguishable. Separately, the TUI's `t` and `d` key bindings dispatch straight to action_view_traces / action_deploy_crew, which never recorded anything - only on_button_pressed did. Every keyboard-driven trace view and deploy was therefore invisible. Move the recording into the actions, which both input paths funnel through, and past the completed guard so a mid-run keypress that does nothing is not counted as usage. Claude-Session: https://claude.ai/code/session_01ASfWmW3RGy4qAQm6s8U9jH Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
139 lines
5.1 KiB
Python
139 lines
5.1 KiB
Python
"""Deployment telemetry: attribution by origin, and an origin-independent count.
|
|
|
|
``Create Crew Deployment`` and ``Start Deployment`` answer "which deployment,
|
|
from where"; ``deploy:created`` / ``deploy:pushed`` answer "how many
|
|
deployments", from the feature-usage aggregation, regardless of origin.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from crewai_core.telemetry import Telemetry
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def telemetry() -> Iterator[tuple[Telemetry, MagicMock]]:
|
|
"""A Telemetry whose spans are captured instead of exported.
|
|
|
|
The singleton is disabled in tests and never builds a provider, so both the
|
|
gate and the provider are supplied here.
|
|
"""
|
|
instance = Telemetry()
|
|
span = MagicMock()
|
|
provider = MagicMock()
|
|
provider.get_tracer.return_value.start_span.return_value = span
|
|
|
|
with (
|
|
patch.object(instance, "provider", provider, create=True),
|
|
patch.object(instance, "_should_execute_telemetry", return_value=True),
|
|
patch("crewai_core.telemetry.close_span"),
|
|
):
|
|
yield instance, span
|
|
|
|
|
|
def _attributes(span: MagicMock) -> dict[str, Any]:
|
|
return {call.args[0]: call.args[1] for call in span.set_attribute.call_args_list}
|
|
|
|
|
|
def _span_names(provider: MagicMock) -> list[str]:
|
|
tracer = provider.get_tracer.return_value
|
|
return [call.args[0] for call in tracer.start_span.call_args_list]
|
|
|
|
|
|
class TestCreateDeployment:
|
|
def test_defaults_to_cli(self, telemetry: tuple[Telemetry, MagicMock]) -> None:
|
|
instance, span = telemetry
|
|
instance.create_crew_deployment_span()
|
|
assert _attributes(span)["source"] == "cli"
|
|
|
|
def test_records_tui_when_started_from_the_run_ui(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
instance, span = telemetry
|
|
instance.create_crew_deployment_span(source="tui")
|
|
assert _attributes(span)["source"] == "tui"
|
|
|
|
def test_also_counts_the_deployment_as_a_feature(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
instance, _ = telemetry
|
|
with patch.object(instance, "feature_usage_span") as feature:
|
|
instance.create_crew_deployment_span()
|
|
feature.assert_called_once_with("deploy:created")
|
|
|
|
def test_feature_count_is_the_same_from_either_origin(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
"""The whole point: one number for deployments, whatever started them."""
|
|
instance, _ = telemetry
|
|
with patch.object(instance, "feature_usage_span") as feature:
|
|
instance.create_crew_deployment_span(source="cli")
|
|
instance.create_crew_deployment_span(source="tui")
|
|
assert [call.args[0] for call in feature.call_args_list] == [
|
|
"deploy:created",
|
|
"deploy:created",
|
|
]
|
|
|
|
|
|
class TestStartDeployment:
|
|
def test_defaults_to_cli_and_keeps_the_uuid(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
instance, span = telemetry
|
|
instance.start_deployment_span("dep-123")
|
|
attributes = _attributes(span)
|
|
assert attributes["source"] == "cli"
|
|
assert attributes["uuid"] == "dep-123"
|
|
|
|
def test_records_tui_when_started_from_the_run_ui(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
instance, span = telemetry
|
|
instance.start_deployment_span("dep-123", source="tui")
|
|
assert _attributes(span)["source"] == "tui"
|
|
|
|
def test_source_is_recorded_even_without_a_uuid(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
"""uuid is optional; source must not be conditional on it."""
|
|
instance, span = telemetry
|
|
instance.start_deployment_span(None, source="tui")
|
|
attributes = _attributes(span)
|
|
assert attributes["source"] == "tui"
|
|
assert "uuid" not in attributes
|
|
|
|
def test_also_counts_the_deployment_as_a_feature(
|
|
self, telemetry: tuple[Telemetry, MagicMock]
|
|
) -> None:
|
|
instance, _ = telemetry
|
|
with patch.object(instance, "feature_usage_span") as feature:
|
|
instance.start_deployment_span("dep-123")
|
|
feature.assert_called_once_with("deploy:pushed")
|
|
|
|
|
|
class TestDisabledTelemetry:
|
|
def test_opted_out_users_emit_nothing(self) -> None:
|
|
"""No span and no feature count when telemetry is off."""
|
|
instance = Telemetry()
|
|
provider = MagicMock()
|
|
|
|
with (
|
|
patch.object(instance, "provider", provider, create=True),
|
|
patch.object(instance, "_should_execute_telemetry", return_value=False),
|
|
patch.object(instance, "feature_usage_span") as feature,
|
|
):
|
|
instance.create_crew_deployment_span(source="tui")
|
|
instance.start_deployment_span("dep-123", source="tui")
|
|
|
|
assert _span_names(provider) == []
|
|
# feature_usage_span is itself gated, so it is still called; it is the
|
|
# export that must not happen. Assert it was not bypassed some other way.
|
|
assert [call.args[0] for call in feature.call_args_list] == [
|
|
"deploy:created",
|
|
"deploy:pushed",
|
|
]
|