Skip to content

Commit 1411f54

Browse files
khvn26emyllerpre-commit-ci[bot]
authored
feat: Process User-Agent strings (#5823)
Co-authored-by: Evandro Myller <22429+emyller@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 0d5acfc commit 1411f54

6 files changed

Lines changed: 62 additions & 5 deletions

File tree

api/app_analytics/constants.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import get_args
22

3-
from app_analytics.types import Label, PeriodType
3+
from app_analytics.types import InputLabel, Label, PeriodType
44

55
ANALYTICS_READ_BUCKET_SIZE = 15
66

@@ -15,9 +15,10 @@
1515
) = get_args(PeriodType)
1616

1717
# Optional headers sent from client SDK mapped to their respective labels.
18-
TRACK_HEADERS: dict[str, Label] = {
18+
TRACK_HEADERS: dict[str, InputLabel] = {
1919
"Flagsmith-Application-Name": "client_application_name",
2020
"Flagsmith-Application-Version": "client_application_version",
21+
"Flagsmith-SDK-User-Agent": "sdk_user_agent",
2122
"User-Agent": "user_agent",
2223
}
2324
LABELS: tuple[str, ...] = tuple(str(label) for label in get_args(Label))

api/app_analytics/mappers.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, Iterable
44

55
from django.http import HttpRequest
6+
from fastuaparser import parse_ua # type: ignore[import-untyped]
67
from influxdb_client.client.flux_table import FluxTable
78
from pydantic import Field, create_model
89
from pydantic.type_adapter import TypeAdapter
@@ -14,6 +15,7 @@
1415
AnnotatedAPIUsageBucket,
1516
AnnotatedAPIUsageKey,
1617
FeatureEvaluationCacheKey,
18+
InputLabels,
1719
Labels,
1820
TrackFeatureEvaluationsByEnvironmentData,
1921
TrackFeatureEvaluationsByEnvironmentKwargs,
@@ -112,19 +114,37 @@ def map_flux_tables_to_feature_evaluation_data(
112114
]
113115

114116

117+
def map_input_labels_to_labels(input_labels: InputLabels) -> Labels:
118+
labels: Labels = {}
119+
for label, value in input_labels.items():
120+
if label == "sdk_user_agent":
121+
labels["user_agent"] = value
122+
continue
123+
elif label == "user_agent":
124+
# fastuaparser classifies unrecognized UAs as "Other" — assume these to
125+
# represent server-side SDKs.
126+
parsed_ua_string: str = parse_ua(value)
127+
is_server_side_sdk = parsed_ua_string.startswith("Other")
128+
# Skip browser SDKs that don't send the special header.
129+
if not is_server_side_sdk:
130+
continue
131+
labels[label] = value
132+
return labels
133+
134+
115135
def map_request_to_labels(request: HttpRequest) -> Labels:
116136
if not (
117137
get_client("local", local_eval=True)
118138
.get_environment_flags()
119139
.is_feature_enabled("sdk_metrics_labels")
120140
):
121141
return {}
122-
result: Labels = _RequestHeaderLabelsModel.model_validate(
142+
labels: InputLabels = _RequestHeaderLabelsModel.model_validate(
123143
request.headers,
124144
).model_dump(
125145
exclude_unset=True,
126146
)
127-
return result
147+
return map_input_labels_to_labels(labels)
128148

129149

130150
def map_feature_evaluation_cache_to_track_feature_evaluations_by_environment_kwargs(

api/app_analytics/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,7 @@ class AnnotatedAPIUsageKey(NamedTuple):
5454
"user_agent",
5555
]
5656

57+
InputLabel = Label | Literal["sdk_user_agent"]
58+
5759
Labels: TypeAlias = dict[Label, str]
60+
InputLabels: TypeAlias = dict[InputLabel, str]

api/poetry.lock

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ djangorestframework-simplejwt = "^5.3.1"
167167
structlog = "^24.4.0"
168168
prometheus-client = "^0.21.1"
169169
django_cockroachdb = "~4.2"
170+
fastuaparser = "^0.1.4"
170171

171172
[tool.poetry.group.auth-controller]
172173
optional = true

api/tests/unit/app_analytics/test_unit_app_analytics_views.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,21 @@ def test_set_sdk_analytics_flags_with_identifier__influx__calls_expected(
565565
"user_agent": "python-requests/2.31.0",
566566
},
567567
),
568+
(
569+
{
570+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
571+
},
572+
{},
573+
),
574+
(
575+
{
576+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
577+
"Flagsmith-SDK-User-Agent": "flagsmith-js-sdk/1.0.0",
578+
},
579+
{
580+
"user_agent": "flagsmith-js-sdk/1.0.0",
581+
},
582+
),
568583
],
569584
)
570585
def test_sdk_analytics_flags_v1(

0 commit comments

Comments
 (0)