diff --git a/src/sentry/api/endpoints/organization_insights_tree.py b/src/sentry/api/endpoints/organization_insights_tree.py index f089e7cb8533..1f0a4c668955 100644 --- a/src/sentry/api/endpoints/organization_insights_tree.py +++ b/src/sentry/api/endpoints/organization_insights_tree.py @@ -41,22 +41,51 @@ def get(self, request: Request, organization: Organization) -> Response: response = super().get(request, organization) return self._separate_span_description_info(response) - def _separate_span_description_info(self, response): - # Regex to split string into '{component_type}{space}({path})' - pattern = re.compile(r"^(.*?)\s+\((.*?)\)$") + # SDK <10.32.0: '{component_type} ({path})' + # e.g. 'Page Server Component (/dashboard)' + _pattern_parens = re.compile(r"^(.*?)\s+\((.*?)\)$") + + # SDK >=10.32.0: 'resolve {page|root layout|layout} server component "{route_or_segment}"' + # e.g. 'resolve page server component "/dashboard"' + _pattern_resolve = re.compile( + r'^resolve (page|root layout|layout) server component(?:\s+"(.*)")?$' + ) + + _RESOLVE_KIND_TO_COMPONENT_TYPE = { + "page": "Page Server Component", + "layout": "Layout Server Component", + "root layout": "Layout Server Component", + } + def _separate_span_description_info(self, response): for line in response.data["data"]: - match = pattern.match(line["span.description"]) + desc = line["span.description"] + + match = self._pattern_parens.match(desc) if match: component_type = match.group(1) path = match.group(2) path_components = path.strip("/").split("/") if not path_components or (len(path_components) == 1 and path_components[0] == ""): - path_components = [] # Handle root path case - + path_components = [] else: - component_type = None - path_components = [] + match = self._pattern_resolve.match(desc) + if match: + kind = match.group(1) + value = match.group(2) + component_type = self._RESOLVE_KIND_TO_COMPONENT_TYPE[kind] + if value: + path_components = value.strip("/").split("/") + if not path_components or ( + len(path_components) == 1 and path_components[0] == "" + ): + path_components = [] + else: + path_components = [] + else: + component_type = None + path_components = [] + line["function.nextjs.component_type"] = component_type line["function.nextjs.path"] = path_components diff --git a/tests/sentry/api/endpoints/test_organization_insights_tree.py b/tests/sentry/api/endpoints/test_organization_insights_tree.py index ab7180c57159..39c486ef58a8 100644 --- a/tests/sentry/api/endpoints/test_organization_insights_tree.py +++ b/tests/sentry/api/endpoints/test_organization_insights_tree.py @@ -30,6 +30,7 @@ def setUp(self) -> None: self._store_unrelated_spans() def _store_nextjs_function_spans(self) -> None: + # Old SDK format (<10.32.0): '{ComponentType} Server Component ({route})' descriptions = [ "Page Server Component (/app/dashboard/)", "Loading Server Component (/app/dashboard/)", @@ -50,6 +51,13 @@ def _store_nextjs_function_spans(self) -> None: "Page Server Component (/app/[id]/)", "Page Server Component (/app/[...slug]/)", "Page Server Component (/app/[[...optional]]/)", + # New SDK format (>=10.32.0): 'resolve {type} server component "{route_or_segment}"' + 'resolve page server component "/dashboard"', + 'resolve page server component "/nested-layout/[dynamic]"', + 'resolve layout server component "nested-layout"', + 'resolve layout server component "(route-group)"', + 'resolve layout server component "[dynamic]"', + "resolve root layout server component", "unrelated description", ] spans = [] @@ -125,4 +133,34 @@ def test_get_nextjs_function_data(self) -> None: assert element["function.nextjs.component_type"] == "Page Server Component" assert element["function.nextjs.path"] == ["app", "[...slug]"] + # New SDK format: resolve page server component with full route + resolve_page_idx = span_descriptions.index( + 'resolve page server component "/nested-layout/[dynamic]"' + ) + element = response.data["data"][resolve_page_idx] + assert element["function.nextjs.component_type"] == "Page Server Component" + assert element["function.nextjs.path"] == ["nested-layout", "[dynamic]"] + + # New SDK format: resolve layout server component with segment + resolve_layout_idx = span_descriptions.index( + 'resolve layout server component "nested-layout"' + ) + element = response.data["data"][resolve_layout_idx] + assert element["function.nextjs.component_type"] == "Layout Server Component" + assert element["function.nextjs.path"] == ["nested-layout"] + + # New SDK format: resolve root layout server component (no path) + resolve_root_idx = span_descriptions.index("resolve root layout server component") + element = response.data["data"][resolve_root_idx] + assert element["function.nextjs.component_type"] == "Layout Server Component" + assert element["function.nextjs.path"] == [] + + # New SDK format: route group segment + resolve_group_idx = span_descriptions.index( + 'resolve layout server component "(route-group)"' + ) + element = response.data["data"][resolve_group_idx] + assert element["function.nextjs.component_type"] == "Layout Server Component" + assert element["function.nextjs.path"] == ["(route-group)"] + assert "INSERT value INTO table" not in span_descriptions