Skip to content

Commit

Permalink
add flags, components
Browse files Browse the repository at this point in the history
  • Loading branch information
suejung-sentry committed Sep 19, 2024
1 parent 1586a9b commit 77596d8
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 11 deletions.
27 changes: 27 additions & 0 deletions graphql_api/types/coverage_analytics/coverage_analytics.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,33 @@ type CoverageAnalytics {
before: DateTime
branch: String
): [Measurement!]! # formerly repository.measurements

### FLAGS ###
flagsCount: Int!
flagsMeasurementsActive: Boolean!
flagsMeasurementsBackfilled: Boolean!
flags(
filters: FlagSetFilters
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): FlagConnection! @cost(complexity: 3, multipliers: ["first", "last"])

### COMPONENTS ###
componentsMeasurementsActive: Boolean!
componentsMeasurementsBackfilled: Boolean!
componentsCount: Int!
components(
interval: MeasurementInterval!
before: DateTime!
after: DateTime!
branch: String
filters: ComponentMeasurementsSetFilters
orderingDirection: OrderingDirection
): [ComponentMeasurements!]!
componentsYaml(termId: String): [ComponentsYaml]!
}

"CoverageAnalyticsResult is CoverageAnalytics or potential error(s)"
Expand Down
254 changes: 243 additions & 11 deletions graphql_api/types/coverage_analytics/coverage_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def resolve_coverage_analytics_result_type(obj, *_):


@coverage_analytics_bindable.field("percentCovered")
def resolve_percent_covered(coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo):
def resolve_percent_covered(
coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo
):
repository = info.context.get("repository", None)
return repository.recent_coverage if repository else None

Expand All @@ -52,35 +54,43 @@ def resolve_commit_sha(coverage_analytics: CoverageAnalytics, info: GraphQLResol


@coverage_analytics_bindable.field("hits")
def resolve_hits(coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo) -> Optional[int]:
def resolve_hits(
coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo
) -> Optional[int]:
repository = info.context.get("repository", None)
return repository.hits if repository else None


@coverage_analytics_bindable.field("misses")
def resolve_misses(coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo) -> Optional[int]:
def resolve_misses(
coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo
) -> Optional[int]:
repository = info.context.get("repository", None)
return repository.misses if repository else None


@coverage_analytics_bindable.field("lines")
def resolve_lines(coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo) -> Optional[int]:
def resolve_lines(
coverage_analytics: CoverageAnalytics, info: GraphQLResolveInfo
) -> Optional[int]:
repository = info.context.get("repository", None)
return repository.lines if repository else None


@coverage_analytics_bindable.field("measurements")
async def resolve_measurements(
coverage_analytics: CoverageAnalytics,
info: GraphQLResolveInfo,
interval: Interval,
before: Optional[datetime] = None,
after: Optional[datetime] = None,
branch: Optional[str] = None,
coverage_analytics: CoverageAnalytics,
info: GraphQLResolveInfo,
interval: Interval,
before: Optional[datetime] = None,
after: Optional[datetime] = None,
branch: Optional[str] = None,
) -> Iterable[MeasurementSummary]:
repository = info.context.get("repository", None)

coverage_data = await sync_to_async(timeseries_helpers.repository_coverage_measurements_with_fallback)(
coverage_data = await sync_to_async(
timeseries_helpers.repository_coverage_measurements_with_fallback
)(
repository,
interval,
start_date=after,
Expand All @@ -96,3 +106,225 @@ async def resolve_measurements(
)

return measurements


@coverage_analytics_bindable.field("flags")
@convert_kwargs_to_snake_case
@sync_to_async
def resolve_flags(
repository: Repository,
info: GraphQLResolveInfo,
filters: Mapping = None,
ordering_direction: OrderingDirection = OrderingDirection.ASC,
**kwargs,
):
queryset = flags_for_repo(repository, filters)
connection = queryset_to_connection_sync(
queryset,
ordering=("flag_name",),
ordering_direction=ordering_direction,
**kwargs,
)

# We fetch the measurements in this resolver since there are multiple child
# flag resolvers that depend on this data. Additionally, we're able to fetch
# measurements for all the flags being returned at once.
# Use the lookahead to make sure we don't overfetch measurements that we don't
# need.
node = lookahead(info, ("edges", "node", "measurements"))
if node:
if settings.TIMESERIES_ENABLED:
# TODO: is there a way to have these automatically casted at a
# lower level (i.e. based on the schema)?
interval = node.args["interval"]
if isinstance(interval, str):
interval = Interval[interval]
after = node.args["after"]
if isinstance(after, str):
after = from_current_timezone(datetime.fromisoformat(after))
before = node.args["before"]
if isinstance(before, str):
before = from_current_timezone(datetime.fromisoformat(before))

flag_ids = [edge["node"].pk for edge in connection.edges]

info.context["flag_measurements"] = flag_measurements(
repository, flag_ids, interval, after, before
)
else:
info.context["flag_measurements"] = {}

return connection


@coverage_analytics_bindable.field("flagsCount")
@sync_to_async
def resolve_flags_count(repository: Repository, info: GraphQLResolveInfo) -> int:
return repository.flags.filter(deleted__isnot=True).count()


@coverage_analytics_bindable.field("flagsMeasurementsActive")
@sync_to_async
def resolve_flags_measurements_active(
repository: Repository, info: GraphQLResolveInfo
) -> bool:
if not settings.TIMESERIES_ENABLED:
return False

return Dataset.objects.filter(
name=MeasurementName.FLAG_COVERAGE.value,
repository_id=repository.pk,
).exists()


@coverage_analytics_bindable.field("flagsMeasurementsBackfilled")
@sync_to_async
def resolve_flags_measurements_backfilled(
repository: Repository, info: GraphQLResolveInfo
) -> bool:
if not settings.TIMESERIES_ENABLED:
return False

dataset = Dataset.objects.filter(
name=MeasurementName.FLAG_COVERAGE.value,
repository_id=repository.pk,
).first()

if not dataset:
return False

return dataset.is_backfilled()


@coverage_analytics_bindable.field("componentsMeasurementsActive")
@sync_to_async
def resolve_components_measurements_active(
repository: Repository, info: GraphQLResolveInfo
) -> bool:
if not settings.TIMESERIES_ENABLED:
return False

return Dataset.objects.filter(
name=MeasurementName.COMPONENT_COVERAGE.value,
repository_id=repository.pk,
).exists()


@coverage_analytics_bindable.field("componentsMeasurementsBackfilled")
@sync_to_async
def resolve_components_measurements_backfilled(
repository: Repository, info: GraphQLResolveInfo
) -> bool:
if not settings.TIMESERIES_ENABLED:
return False

dataset = Dataset.objects.filter(
name=MeasurementName.COMPONENT_COVERAGE.value,
repository_id=repository.pk,
).first()

if not dataset:
return False

return dataset.is_backfilled()


@coverage_analytics_bindable.field("componentsCount")
@sync_to_async
def resolve_components_count(repository: Repository, info: GraphQLResolveInfo) -> int:
repo_yaml_components = UserYaml.get_final_yaml(
owner_yaml=repository.author.yaml,
repo_yaml=repository.yaml,
ownerid=repository.author.ownerid,
).get_components()

return len(repo_yaml_components)


@coverage_analytics_bindable.field("components")
@convert_kwargs_to_snake_case
@sync_to_async
def resolve_component_measurements(
repository: Repository,
info: GraphQLResolveInfo,
interval: Interval,
before: datetime,
after: datetime,
branch: Optional[str] = None,
filters: Optional[Mapping] = None,
ordering_direction: Optional[OrderingDirection] = OrderingDirection.ASC,
):
components = UserYaml.get_final_yaml(
owner_yaml=repository.author.yaml,
repo_yaml=repository.yaml,
ownerid=repository.author.ownerid,
).get_components()

if not settings.TIMESERIES_ENABLED or not components:
return []

if filters and "components" in filters:
components = [c for c in components if c.component_id in filters["components"]]

component_ids = [c.component_id for c in components]
all_measurements = component_measurements(
repository, component_ids, interval, after, before, branch
)

last_measurements = component_measurements_last_uploaded(
owner_id=repository.author.ownerid,
repo_id=repository.repoid,
measurable_ids=component_ids,
branch=branch,
)
last_measurements_mapping = {
row["measurable_id"]: row["last_uploaded"] for row in last_measurements
}

components_mapping = {
component.component_id: component.name for component in components
}

queried_measurements = [
ComponentMeasurements(
raw_measurements=all_measurements.get(component_id, []),
component_id=component_id,
interval=interval,
after=after,
before=before,
last_measurement=last_measurements_mapping.get(component_id),
components_mapping=components_mapping,
)
for component_id in component_ids
]

return sorted(
queried_measurements,
key=lambda c: c.name,
reverse=ordering_direction == OrderingDirection.DESC,
)


@coverage_analytics_bindable.field("componentsYaml")
@convert_kwargs_to_snake_case
def resolve_component_yaml(
repository: Repository, info: GraphQLResolveInfo, term_id: Optional[str]
) -> List[str]:
components = UserYaml.get_final_yaml(
owner_yaml=repository.author.yaml,
repo_yaml=repository.yaml,
ownerid=repository.author.ownerid,
).get_components()

components = [
{
"id": c.component_id,
"name": c.name,
}
for c in components
]

if term_id:
components = filter(lambda c: term_id in c["id"], components)

return components
8 changes: 8 additions & 0 deletions graphql_api/types/repository/repository.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,17 @@ type Repository {
graphToken: String
yaml: String
bot: Owner
# flagsCount to be removed with #2282
flagsCount: Int!
# flagsMeasurementsActive to be removed with #2282
flagsMeasurementsActive: Boolean!
# flagsMeasurementsBackfilled to be removed with #2282
flagsMeasurementsBackfilled: Boolean!
# componentsMeasurementsActive to be removed with #2282
componentsMeasurementsActive: Boolean!
# componentsMeasurementsBackfilled to be removed with #2282
componentsMeasurementsBackfilled: Boolean!
# componentsCount to be removed with #2282
componentsCount: Int!
# (coverage) measurements to be removed with #2282
measurements(
Expand All @@ -82,6 +88,7 @@ type Repository {
languages: [String!]
bundleAnalysisEnabled: Boolean
coverageEnabled: Boolean
# components to be removed with #2282
components(
interval: MeasurementInterval!
before: DateTime!
Expand All @@ -90,6 +97,7 @@ type Repository {
filters: ComponentMeasurementsSetFilters
orderingDirection: OrderingDirection
): [ComponentMeasurements!]!
# componentsYaml to be removed with #2282
componentsYaml(termId: String): [ComponentsYaml]!
testAnalyticsEnabled: Boolean
isGithubRateLimited: Boolean
Expand Down

0 comments on commit 77596d8

Please sign in to comment.