|
14 | 14 | OurLogTestCase,
|
15 | 15 | SnubaTestCase,
|
16 | 16 | SpanTestCase,
|
| 17 | + TraceMetricsTestCase, |
17 | 18 | )
|
18 | 19 | from sentry.testutils.helpers import parse_link_header
|
19 | 20 | from sentry.testutils.helpers.datetime import before_now
|
@@ -789,6 +790,133 @@ def test_sentry_internal_attributes(self) -> None:
|
789 | 790 | assert "__sentry_internal_test" in attribute_names
|
790 | 791 |
|
791 | 792 |
|
| 793 | +class OrganizationTraceItemAttributesEndpointTraceMetricsTest( |
| 794 | + OrganizationTraceItemAttributesEndpointTestBase, TraceMetricsTestCase |
| 795 | +): |
| 796 | + feature_flags = {"organizations:tracemetrics-enabled": True} |
| 797 | + item_type = SupportedTraceItemType.TRACEMETRICS |
| 798 | + |
| 799 | + def test_no_feature(self) -> None: |
| 800 | + response = self.do_request(features={}) |
| 801 | + assert response.status_code == 404, response.content |
| 802 | + |
| 803 | + def test_invalid_item_type(self) -> None: |
| 804 | + response = self.do_request(query={"itemType": "invalid"}) |
| 805 | + assert response.status_code == 400, response.content |
| 806 | + assert response.data == { |
| 807 | + "itemType": [ |
| 808 | + ErrorDetail(string='"invalid" is not a valid choice.', code="invalid_choice") |
| 809 | + ], |
| 810 | + } |
| 811 | + |
| 812 | + def test_trace_metrics_string_attributes(self) -> None: |
| 813 | + """Test that we can successfully retrieve string attributes from trace metrics""" |
| 814 | + metrics = [ |
| 815 | + self.create_trace_metric( |
| 816 | + metric_name="http.request.duration", |
| 817 | + metric_value=123.45, |
| 818 | + organization=self.organization, |
| 819 | + project=self.project, |
| 820 | + attributes={ |
| 821 | + "http.method": "GET", |
| 822 | + "http.status_code": "200", |
| 823 | + "environment": "production", |
| 824 | + }, |
| 825 | + ), |
| 826 | + self.create_trace_metric( |
| 827 | + metric_name="http.request.duration", |
| 828 | + metric_value=234.56, |
| 829 | + organization=self.organization, |
| 830 | + project=self.project, |
| 831 | + attributes={ |
| 832 | + "http.method": "POST", |
| 833 | + "http.status_code": "201", |
| 834 | + "environment": "staging", |
| 835 | + }, |
| 836 | + ), |
| 837 | + ] |
| 838 | + self.store_trace_metrics(metrics) |
| 839 | + |
| 840 | + response = self.do_request(query={"attributeType": "string"}) |
| 841 | + |
| 842 | + assert response.status_code == 200, response.content |
| 843 | + data = response.data |
| 844 | + assert len(data) > 0 |
| 845 | + |
| 846 | + # Verify that our custom attributes are returned |
| 847 | + attribute_keys = {item["key"] for item in data} |
| 848 | + assert "http.method" in attribute_keys |
| 849 | + assert "http.status_code" in attribute_keys |
| 850 | + # Environment may be stored as tags[environment,string] |
| 851 | + assert "environment" in attribute_keys or "tags[environment,string]" in attribute_keys |
| 852 | + |
| 853 | + def test_trace_metrics_filter_by_metric_name(self) -> None: |
| 854 | + """Test that we can filter trace metrics attributes by metric name using query parameter""" |
| 855 | + metrics = [ |
| 856 | + self.create_trace_metric( |
| 857 | + metric_name="http.request.duration", |
| 858 | + metric_value=100.0, |
| 859 | + organization=self.organization, |
| 860 | + project=self.project, |
| 861 | + attributes={ |
| 862 | + "http.method": "GET", |
| 863 | + "http.route": "/api/users", |
| 864 | + }, |
| 865 | + ), |
| 866 | + self.create_trace_metric( |
| 867 | + metric_name="database.query.duration", |
| 868 | + metric_value=50.0, |
| 869 | + organization=self.organization, |
| 870 | + project=self.project, |
| 871 | + attributes={ |
| 872 | + "db.system": {"string_value": "postgresql"}, |
| 873 | + "db.operation": {"string_value": "SELECT"}, |
| 874 | + }, |
| 875 | + ), |
| 876 | + ] |
| 877 | + self.store_trace_metrics(metrics) |
| 878 | + |
| 879 | + # Query for http metric attributes |
| 880 | + response = self.do_request( |
| 881 | + query={ |
| 882 | + "attributeType": "string", |
| 883 | + "query": 'metric.name:"http.request.duration"', |
| 884 | + } |
| 885 | + ) |
| 886 | + |
| 887 | + assert response.status_code == 200, response.content |
| 888 | + data = response.data |
| 889 | + attribute_keys = {item["key"] for item in data} |
| 890 | + |
| 891 | + # Should include HTTP attributes |
| 892 | + assert "http.method" in attribute_keys or "http.route" in attribute_keys |
| 893 | + |
| 894 | + def test_trace_metrics_number_attributes(self) -> None: |
| 895 | + """Test that we can retrieve number attributes from trace metrics""" |
| 896 | + metrics = [ |
| 897 | + self.create_trace_metric( |
| 898 | + metric_name="custom.metric", |
| 899 | + metric_value=100.0, |
| 900 | + organization=self.organization, |
| 901 | + project=self.project, |
| 902 | + attributes={ |
| 903 | + "request.size": {"int_value": 1024}, |
| 904 | + "response.time": {"double_value": 42.5}, |
| 905 | + }, |
| 906 | + ), |
| 907 | + ] |
| 908 | + self.store_trace_metrics(metrics) |
| 909 | + |
| 910 | + response = self.do_request(query={"attributeType": "number"}) |
| 911 | + |
| 912 | + assert response.status_code == 200, response.content |
| 913 | + data = response.data |
| 914 | + |
| 915 | + # Verify number attributes are returned |
| 916 | + # Note: The exact keys depend on how the backend processes numeric attributes |
| 917 | + assert len(data) >= 0 # May be 0 if number attributes are handled differently |
| 918 | + |
| 919 | + |
792 | 920 | class OrganizationTraceItemAttributeValuesEndpointBaseTest(APITestCase, SnubaTestCase):
|
793 | 921 | feature_flags: dict[str, bool]
|
794 | 922 | item_type: SupportedTraceItemType
|
@@ -1771,3 +1899,35 @@ def test_autocomplete_timestamp(self) -> None:
|
1771 | 1899 | response = self.do_request(key="timestamp", query={"substringMatch": "20"})
|
1772 | 1900 | assert response.status_code == 200
|
1773 | 1901 | assert response.data == []
|
| 1902 | + |
| 1903 | + |
| 1904 | +class OrganizationTraceItemAttributeValuesEndpointTraceMetricsTest( |
| 1905 | + OrganizationTraceItemAttributeValuesEndpointBaseTest, TraceMetricsTestCase |
| 1906 | +): |
| 1907 | + feature_flags = {"organizations:tracemetrics-enabled": True} |
| 1908 | + item_type = SupportedTraceItemType.TRACEMETRICS |
| 1909 | + |
| 1910 | + def test_no_feature(self) -> None: |
| 1911 | + response = self.do_request(features={}, key="test.attribute") |
| 1912 | + assert response.status_code == 404, response.content |
| 1913 | + |
| 1914 | + def test_attribute_values(self) -> None: |
| 1915 | + metrics = [ |
| 1916 | + self.create_trace_metric( |
| 1917 | + metric_name="http.request.duration", |
| 1918 | + metric_value=123.45, |
| 1919 | + attributes={"http.method": "GET"}, |
| 1920 | + ), |
| 1921 | + self.create_trace_metric( |
| 1922 | + metric_name="http.request.duration", |
| 1923 | + metric_value=234.56, |
| 1924 | + attributes={"http.method": "POST"}, |
| 1925 | + ), |
| 1926 | + ] |
| 1927 | + self.store_trace_metrics(metrics) |
| 1928 | + |
| 1929 | + response = self.do_request(key="http.method") |
| 1930 | + assert response.status_code == 200 |
| 1931 | + values = {item["value"] for item in response.data} |
| 1932 | + assert "GET" in values |
| 1933 | + assert "POST" in values |
0 commit comments