-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable queries using project slug as filter and groupby in Metrics API (
- Loading branch information
Showing
12 changed files
with
666 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import abc | ||
from collections.abc import Sequence | ||
from typing import Any, TypeVar | ||
|
||
from sentry.models.project import Project | ||
|
||
|
||
class Mapper(abc.ABC): | ||
from_key: str = "" | ||
to_key: str = "" | ||
applied_on_groupby: bool = False | ||
|
||
def __init__(self): | ||
# This exists to satisfy mypy, which complains otherwise | ||
self.map: dict[Any, Any] = {} | ||
|
||
def __hash__(self): | ||
return hash((self.from_key, self.to_key)) | ||
|
||
@abc.abstractmethod | ||
def forward(self, projects: Sequence[Project], value: Any) -> Any: | ||
return value | ||
|
||
@abc.abstractmethod | ||
def backward(self, projects: Sequence[Project], value: Any) -> Any: | ||
return value | ||
|
||
|
||
TMapper = TypeVar("TMapper", bound=Mapper) | ||
|
||
|
||
class MapperConfig: | ||
def __init__(self): | ||
self.mappers: set[type[Mapper]] = set() | ||
|
||
def add(self, mapper: type[Mapper]) -> "MapperConfig": | ||
self.mappers.add(mapper) | ||
return self | ||
|
||
def get(self, from_key: str | None = None, to_key: str | None = None) -> type[Mapper] | None: | ||
for mapper in self.mappers: | ||
if mapper.from_key == from_key: | ||
return mapper | ||
if mapper.to_key == to_key: | ||
return mapper | ||
return None | ||
|
||
|
||
def get_or_create_mapper( | ||
mapper_config: MapperConfig, | ||
mappers: list[Mapper], | ||
from_key: str | None = None, | ||
to_key: str | None = None, | ||
) -> Mapper | None: | ||
# retrieve the mapper type that is applicable for the given key | ||
mapper_class = mapper_config.get(from_key=from_key, to_key=to_key) | ||
# check if a mapper of the type already exists | ||
if mapper_class: | ||
for mapper in mappers: | ||
if mapper_class == type(mapper): | ||
# if a mapper already exists, return the existing mapper | ||
return mapper | ||
else: | ||
# if no mapper exists yet, instantiate the object and append it to the mappers list | ||
mapper_instance = mapper_class() | ||
mappers.append(mapper_instance) | ||
return mapper_instance | ||
else: | ||
# if no mapper is configured for the key, return None | ||
return None | ||
|
||
|
||
class Project2ProjectIDMapper(Mapper): | ||
from_key: str = "project" | ||
to_key: str = "project_id" | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def forward(self, projects: Sequence[Project], value: str) -> int: | ||
if value not in self.map: | ||
self.map[value] = None | ||
for project in projects: | ||
if project.slug == value: | ||
self.map[value] = project.id | ||
return self.map[value] | ||
|
||
def backward(self, projects: Sequence[Project], value: int) -> str: | ||
if value not in self.map: | ||
for project in projects: | ||
if project.id == value: | ||
self.map[value] = project.slug | ||
|
||
return self.map[value] |
Empty file.
37 changes: 37 additions & 0 deletions
37
src/sentry/sentry_metrics/querying/data/postprocessing/base.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
from sentry.sentry_metrics.querying.data.execution import QueryResult | ||
|
||
|
||
class PostProcessingStep(ABC): | ||
""" | ||
Represents an abstract step that post-processes a collection of QueryResult objects. | ||
The post-processing of these objects might include transforming them or just obtaining some intermediate data that | ||
is useful to compute other things before returning the results. | ||
""" | ||
|
||
@abstractmethod | ||
def run(self, query_results: list[QueryResult]) -> list[QueryResult]: | ||
""" | ||
Runs the post-processing steps on a list of query results. | ||
Returns: | ||
A list of post-processed query results. | ||
""" | ||
raise NotImplementedError | ||
|
||
|
||
def run_post_processing_steps(query_results: list[QueryResult], *steps) -> list[QueryResult]: | ||
""" | ||
Takes a series of query results and steps and runs the post-processing steps one after each other in order they are | ||
supplied in. | ||
Returns: | ||
A list of query results after running the post-processing steps. | ||
""" | ||
for step in steps: | ||
if isinstance(step, PostProcessingStep): | ||
query_results = step.run(query_results=query_results) | ||
|
||
return query_results |
54 changes: 54 additions & 0 deletions
54
src/sentry/sentry_metrics/querying/data/postprocessing/remapping.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from collections.abc import Mapping, Sequence | ||
from copy import deepcopy | ||
from typing import Any, cast | ||
|
||
from sentry.models.project import Project | ||
from sentry.sentry_metrics.querying.data.execution import QueryResult | ||
from sentry.sentry_metrics.querying.data.mapping.mapper import Mapper | ||
from sentry.sentry_metrics.querying.data.postprocessing.base import PostProcessingStep | ||
|
||
|
||
class QueryRemappingStep(PostProcessingStep): | ||
def __init__(self, projects: Sequence[Project]): | ||
self.projects = projects | ||
|
||
def run(self, query_results: list[QueryResult]) -> list[QueryResult]: | ||
for query_result in query_results: | ||
if ( | ||
query_result.totals is not None | ||
and query_result.totals_query is not None | ||
and len(query_result.totals) > 0 | ||
): | ||
query_result.totals = self._unmap_data( | ||
query_result.totals, query_result.totals_query.mappers | ||
) | ||
if ( | ||
query_result.series is not None | ||
and query_result.series_query is not None | ||
and len(query_result.series) > 0 | ||
): | ||
query_result.series = self._unmap_data( | ||
query_result.series, query_result.series_query.mappers | ||
) | ||
|
||
return query_results | ||
|
||
def _unmap_data( | ||
self, data: Sequence[Mapping[str, Any]], mappers: list[Mapper] | ||
) -> Sequence[Mapping[str, Any]]: | ||
unmapped_data: list[dict[str, Any]] = cast(list[dict[str, Any]], deepcopy(data)) | ||
for element in unmapped_data: | ||
updated_element = dict() | ||
keys_to_delete = [] | ||
for result_key in element.keys(): | ||
for mapper in mappers: | ||
if mapper.to_key == result_key and mapper.applied_on_groupby: | ||
original_value = mapper.backward(self.projects, element[result_key]) | ||
updated_element[mapper.from_key] = original_value | ||
keys_to_delete.append(result_key) | ||
|
||
for key in keys_to_delete: | ||
del element[key] | ||
element.update(updated_element) | ||
|
||
return cast(Sequence[Mapping[str, Any]], unmapped_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
src/sentry/sentry_metrics/querying/data/preparation/mapping.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from collections.abc import Sequence | ||
from dataclasses import replace | ||
|
||
from sentry.models.project import Project | ||
from sentry.sentry_metrics.querying.data.mapping.mapper import MapperConfig | ||
from sentry.sentry_metrics.querying.data.preparation.base import IntermediateQuery, PreparationStep | ||
from sentry.sentry_metrics.querying.visitors.query_expression import MapperVisitor | ||
|
||
|
||
class QueryMappingStep(PreparationStep): | ||
def __init__(self, projects: Sequence[Project], mapper_config: MapperConfig): | ||
self.projects = projects | ||
self.mapper_config = mapper_config | ||
|
||
def _get_mapped_intermediate_query( | ||
self, intermediate_query: IntermediateQuery | ||
) -> IntermediateQuery: | ||
visitor = MapperVisitor(self.projects, self.mapper_config) | ||
mapped_query = visitor.visit(intermediate_query.metrics_query.query) | ||
|
||
return replace( | ||
intermediate_query, | ||
metrics_query=intermediate_query.metrics_query.set_query(mapped_query), | ||
mappers=visitor.mappers, | ||
) | ||
|
||
def run(self, intermediate_queries: list[IntermediateQuery]) -> list[IntermediateQuery]: | ||
mapped_intermediate_queries = [] | ||
|
||
for intermediate_query in intermediate_queries: | ||
mapped_intermediate_queries.append( | ||
self._get_mapped_intermediate_query(intermediate_query) | ||
) | ||
|
||
return mapped_intermediate_queries |
Oops, something went wrong.