Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend changes for API response work #494

Merged
merged 11 commits into from
May 21, 2020
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Select prerelease channel if current channel = release & probe only exists in
prerelease ([#492](https://github.com/mozilla/glam/pull/492))
- Change GLAM API response to be more tabular
([#494](https://github.com/mozilla/glam/pull/494))

## [2020.5.0](https://github.com/mozilla/glam/compare/2020.4.2...2020.5.0) (2020-05-12)

Expand Down
112 changes: 23 additions & 89 deletions glam/api/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from random import shuffle

import orjson
from django.core.cache import caches
from django.db.models import Max, Q, Value
from django.db.models.functions import Concat
Expand All @@ -9,18 +10,17 @@

from glam.api import constants
from glam.api.models import (
BetaAggregation,
DesktopBetaAggregationView,
DesktopNightlyAggregationView,
DesktopReleaseAggregationView,
FenixAggregation,
FirefoxCounts,
NightlyAggregation,
ReleaseAggregation,
Probe,
)


def get_firefox_aggregations(request, **kwargs):
# TODO: When glam starts sending "product", make it required.
# TODO: Consider combining product + channel for Firefox.
REQUIRED_QUERY_PARAMETERS = ["channel", "probe", "aggregationLevel"]
if any([k not in kwargs.keys() for k in REQUIRED_QUERY_PARAMETERS]):
# Figure out which query parameter is missing.
Expand All @@ -40,9 +40,9 @@ def get_firefox_aggregations(request, **kwargs):
raise PermissionDenied()

MODEL_MAP = {
"firefox-nightly": NightlyAggregation,
"firefox-beta": BetaAggregation,
"firefox-release": ReleaseAggregation,
"firefox-nightly": DesktopNightlyAggregationView,
"firefox-beta": DesktopBetaAggregationView,
"firefox-release": DesktopReleaseAggregationView,
}

try:
Expand Down Expand Up @@ -83,97 +83,35 @@ def get_firefox_aggregations(request, **kwargs):
counts = _get_firefox_counts(channel, os, versions, by_build=True)

if "process" in kwargs:
dimensions.append(Q(process=constants.PROCESS_IDS[kwargs["process"]]))

dimensions.append(Q(process=kwargs["process"]))
result = model.objects.filter(*dimensions)

response = {}
response = []

for row in result:

# We skip boolean percentiles.
if row.metric_type == "boolean":
if row.agg_type == constants.AGGREGATION_PERCENTILE:
continue

metadata = {
"channel": constants.CHANNEL_NAMES[row.channel],
data = {
"version": row.version,
"os": row.os,
"build_id": row.build_id,
"process": constants.PROCESS_NAMES[row.process],
"process": row.process,
"metric": row.metric,
"metric_key": row.metric_key,
"metric_type": row.metric_type,
"total_users": row.total_users,
"histogram": row.histogram and orjson.loads(row.histogram) or "",
"percentiles": row.percentiles and orjson.loads(row.percentiles) or "",
}
aggs = {d["key"]: round(d["value"], 4) for d in row.data}

# We use these keys to merge data dictionaries.
key = "{channel}-{version}-{metric}-{os}-{build_id}-{process}".format(
**metadata
)
sub_key = "{key}-{client_agg_type}".format(
key=row.metric_key, client_agg_type=row.client_agg_type
)

record = response.get(key, {})
if "metadata" not in record:
record["metadata"] = metadata

if sub_key not in record:
record[sub_key] = {}

new_data = {}

if row.agg_type == constants.AGGREGATION_HISTOGRAM:
new_data["total_users"] = row.total_users
# Check for labels.
labels = labels_cache.get(metadata["metric"])
if labels is not None:
# Replace the numeric indexes with their labels.
aggs_w_labels = {}
for k, v in aggs.items():
try:
aggs_w_labels[labels[int(k)]] = v
except IndexError:
pass
aggs = aggs_w_labels

# If boolean, we add a fake client_agg_type for the front-end.
if row.client_agg_type:
if row.metric_type == "boolean":
new_data["client_agg_type"] = "boolean-histogram"

new_data[constants.AGGREGATION_NAMES[row.agg_type]] = aggs
data["client_agg_type"] = "boolean-histogram"
else:
data["client_agg_type"] = row.client_agg_type

# Get the total distinct client IDs for this set of dimensions.
new_data["total_addressable_market"] = counts.get(
f"{row.version}-{row.build_id}"
)

if row.metric_key:
new_data["key"] = row.metric_key

if row.client_agg_type:
new_data["client_agg_type"] = row.client_agg_type

data = record[sub_key].get("data", {})
data.update(new_data)

record[sub_key]["data"] = data
response[key] = record

# Restructure data and remove keys only used for merging data.
response = [
{"metadata": r.pop("metadata"), "data": [d["data"] for d in r.values()]}
for r in response.values()
]
data["total_addressable_market"] = counts.get(f"{row.version}-{row.build_id}")

# Check for data with one of "histogram" or "percentiles", but not both.
for row in response:
if row["metadata"]["metric_type"] == "boolean":
continue
for data in row["data"]:
if "histogram" not in data or "percentiles" not in data:
raise NotFound("Incomplete data for probe")
response.append(data)

return response

Expand Down Expand Up @@ -391,12 +329,8 @@ def probes(request):
@api_view(["POST"])
def random_probes(request):
n = request.data.get("n", 3)
channel = request.data.get(
"channel", constants.CHANNEL_NAMES[constants.CHANNEL_NIGHTLY]
)
process = request.data.get(
"process", constants.PROCESS_NAMES[constants.PROCESS_CONTENT]
)
channel = request.data.get("channel", "nightly")
process = request.data.get("process", "parent")
os = request.data.get("os", "Windows")
try:
n = int(n)
Expand Down
4 changes: 2 additions & 2 deletions glam/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ class Core(Configuration):
"glam.auth.drf.OIDCTokenAuthentication",
],
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"drf_orjson_renderer.parsers.ORJSONParser",
],
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
"drf_orjson_renderer.renderers.ORJSONRenderer",
],
}

Expand Down
Loading