Skip to content

Commit

Permalink
Revert "feat(cirrus): V2 api" (#12056)
Browse files Browse the repository at this point in the history
Because
- Monitor is getting error on prod

This Commit 
- Reverts #12008
  • Loading branch information
yashikakhurana authored Jan 15, 2025
1 parent 3debe62 commit ee8f9da
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 762 deletions.
56 changes: 4 additions & 52 deletions cirrus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,16 @@ The following are the available commands for working with Cirrus:

[Cirrus Api Doc](/cirrus/server/cirrus/docs/apidoc.html) for the Cirrus API

## Endpoint: `POST /v1/features/`
## Endpoint

`POST /v1/features/`

- When making a POST request, please make sure to set headers content type as JSON
```javascript
headers: {
"Content-Type": "application/json",
}
```
# Endpoint: `POST /v2/features/`

The v2 endpoint extends the functionality of v1 by also returning enrollments data alongside features.

```javascript
headers: {
"Content-Type": "application/json",
}
```

## Input

Expand Down Expand Up @@ -211,7 +204,7 @@ curl -X POST "http://localhost:8001/v1/features/?nimbus_preview=true" -H 'Conten
}
}'
```
### Output
## Output

The output will be a JSON object with the following properties:

Expand All @@ -236,48 +229,7 @@ Example output:
}
```

```shell
curl -X POST "http://localhost:8001/v2/features/?nimbus_preview=true" -H 'Content-Type: application/json' -d '{
"client_id": "4a1d71ab-29a2-4c5f-9e1d-9d9df2e6e449",
"context": {
"language": "en",
"region": "US"
}
}'
```
### Output

The output will be a JSON object with the following properties:

- `features` (object): An object that contains the set of features. Each feature is represented as a sub-object with its own set of variables.
- `Enrollments` (array): An array of objects representing the client's enrollment into experiments. Each enrollment object contains details about the experiment, such as the experiment ID, branch, and type.

Example output:

```json
{
"Features": {
"Feature1": {"Variable1.1": "valueA", "Variable1.2": "valueB"},
"Feature2": {"Variable2.1": "valueC", "Variable2.2": "valueD"}
},
"Enrollments": [
{
"nimbus_user_id": "4a1d71ab-29a2-4c5f-9e1d-9d9df2e6e449",
"app_id": "test_app_id",
"experiment": "experiment-slug",
"branch": "control",
"experiment_type": "rollout",
"is_preview": false
}
]
}

```


## Notes

- This API only accepts POST requests.
- All parameters should be supplied in the body as JSON.
- `v2 Endpoint`: Returns both features and enrollments. Use this if you need detailed enrollment data.
- Query Parameter: Use nimbus_preview=true to compute enrollments based on preview experiments.
2 changes: 1 addition & 1 deletion cirrus/server/cirrus/docs/apidoc.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js">
</script>
<script>
var spec = {"openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {"/": {"get": {"summary": "Read Root", "operationId": "read_root__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/v1/features/": {"post": {"summary": "Compute Features V1", "operationId": "compute_features_v1_v1_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/v2/features/": {"post": {"summary": "Compute Features Enrollments V2", "operationId": "compute_features_enrollments_v2_v2_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/__lbheartbeat__": {"get": {"summary": "Health Check Lbheartbeat", "operationId": "health_check_lbheartbeat___lbheartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/__heartbeat__": {"get": {"summary": "Health Check Heartbeat", "operationId": "health_check_heartbeat___heartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}}, "components": {"schemas": {"FeatureRequest": {"properties": {"client_id": {"type": "string", "title": "Client Id"}, "context": {"type": "object", "title": "Context"}}, "type": "object", "required": ["client_id", "context"], "title": "FeatureRequest"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}};
var spec = {"openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {"/": {"get": {"summary": "Read Root", "operationId": "read_root__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/v1/features/": {"post": {"summary": "Compute Features", "operationId": "compute_features_v1_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/__lbheartbeat__": {"get": {"summary": "Health Check Lbheartbeat", "operationId": "health_check_lbheartbeat___lbheartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/__heartbeat__": {"get": {"summary": "Health Check Heartbeat", "operationId": "health_check_heartbeat___heartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}}, "components": {"schemas": {"FeatureRequest": {"properties": {"client_id": {"type": "string", "title": "Client Id"}, "context": {"type": "object", "title": "Context"}}, "type": "object", "required": ["client_id", "context"], "title": "FeatureRequest"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}};
Redoc.init(spec, {}, document.getElementById("redoc-container"));
</script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion cirrus/server/cirrus/docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {"/": {"get": {"summary": "Read Root", "operationId": "read_root__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/v1/features/": {"post": {"summary": "Compute Features V1", "operationId": "compute_features_v1_v1_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/v2/features/": {"post": {"summary": "Compute Features Enrollments V2", "operationId": "compute_features_enrollments_v2_v2_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/__lbheartbeat__": {"get": {"summary": "Health Check Lbheartbeat", "operationId": "health_check_lbheartbeat___lbheartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/__heartbeat__": {"get": {"summary": "Health Check Heartbeat", "operationId": "health_check_heartbeat___heartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}}, "components": {"schemas": {"FeatureRequest": {"properties": {"client_id": {"type": "string", "title": "Client Id"}, "context": {"type": "object", "title": "Context"}}, "type": "object", "required": ["client_id", "context"], "title": "FeatureRequest"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}}
{"openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {"/": {"get": {"summary": "Read Root", "operationId": "read_root__get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/v1/features/": {"post": {"summary": "Compute Features", "operationId": "compute_features_v1_features__post", "parameters": [{"name": "nimbus_preview", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Nimbus Preview"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeatureRequest"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/__lbheartbeat__": {"get": {"summary": "Health Check Lbheartbeat", "operationId": "health_check_lbheartbeat___lbheartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}, "/__heartbeat__": {"get": {"summary": "Health Check Heartbeat", "operationId": "health_check_heartbeat___heartbeat___get", "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {}}}}}}}}, "components": {"schemas": {"FeatureRequest": {"properties": {"client_id": {"type": "string", "title": "Client Id"}, "context": {"type": "object", "title": "Context"}}, "type": "object", "required": ["client_id", "context"], "title": "FeatureRequest"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}}}}
4 changes: 3 additions & 1 deletion cirrus/server/cirrus/feature_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ def compute_feature_configurations(
"enrolledFeatureConfigMap" # slug, featureid, value,
].items()
}
merged_res: MergedJsonWithErrors = self.fml_client.merge(feature_configs)
merged_res: MergedJsonWithErrors = self.fml_client.merge( # type: ignore
feature_configs
)
self.merge_errors = merged_res.errors

if self.merge_errors:
Expand Down
106 changes: 32 additions & 74 deletions cirrus/server/cirrus/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any, List, NamedTuple, TypedDict
from typing import Any, List, NamedTuple

import sentry_sdk
from apscheduler.schedulers.asyncio import AsyncIOScheduler # type: ignore
Expand Down Expand Up @@ -161,59 +161,53 @@ def initialize_glean():


class EnrollmentMetricData(NamedTuple):
nimbus_user_id: str
app_id: str
experiment_slug: str
branch_slug: str
experiment_type: str
is_preview: bool


class ComputeFeaturesEnrollmentResult(TypedDict):
features: dict[str, dict[str, Any]]
enrollments: list[EnrollmentMetricData]


def collate_enrollment_metric_data(
enrolled_partial_configuration: dict[str, Any],
client_id: str,
nimbus_preview_flag: bool,
enrolled_partial_configuration: dict[str, Any], nimbus_preview_flag: bool
) -> list[EnrollmentMetricData]:
events: list[dict[str, Any]] = enrolled_partial_configuration.get("events", [])
remote_settings = (
app.state.remote_setting_preview
if nimbus_preview_flag
else app.state.remote_setting_live
)
data: list[EnrollmentMetricData] = []
for event in events:
if event.get("change") == "Enrollment":
experiment_slug = event.get("experiment_slug", "")
branch_slug = event.get("branch_slug", "")
experiment_type = None
remote_settings = app.state.remote_setting_live
if nimbus_preview_flag:
remote_settings = app.state.remote_setting_preview
experiment_type = remote_settings.get_recipe_type(experiment_slug)
data.append(
EnrollmentMetricData(
nimbus_user_id=client_id,
app_id=app_id,
experiment_slug=experiment_slug,
branch_slug=branch_slug,
experiment_type=experiment_type,
is_preview=nimbus_preview_flag,
)
)
return data


async def record_metrics(enrollment_data: list[EnrollmentMetricData]):
for enrollment in enrollment_data:
async def record_metrics(
enrolled_partial_configuration: dict[str, Any],
client_id: str,
nimbus_preview_flag: bool,
):
metrics = collate_enrollment_metric_data(
enrolled_partial_configuration=enrolled_partial_configuration,
nimbus_preview_flag=nimbus_preview_flag,
)
for experiment_slug, branch_slug, experiment_type in metrics:
app.state.metrics.cirrus_events.enrollment.record(
app.state.metrics.cirrus_events.EnrollmentExtra(
user_id=enrollment.nimbus_user_id,
app_id=enrollment.app_id,
experiment=enrollment.experiment_slug,
branch=enrollment.branch_slug,
experiment_type=enrollment.experiment_type,
is_preview=enrollment.is_preview,
user_id=client_id,
app_id=app_id,
experiment=experiment_slug,
branch=branch_slug,
experiment_type=experiment_type,
is_preview=nimbus_preview_flag,
)
)
app.state.pings.enrollment.submit()
Expand All @@ -227,10 +221,11 @@ def read_root():
return {"Hello": "World"}


async def compute_features_enrollments(
@app.post("/v1/features/", status_code=status.HTTP_200_OK)
async def compute_features(
request_data: FeatureRequest,
nimbus_preview: bool = Query(default=False, alias="nimbus_preview"),
) -> ComputeFeaturesEnrollmentResult:
):
if not request_data.client_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
Expand All @@ -251,8 +246,9 @@ async def compute_features_enrollments(
"clientId": request_data.client_id,
"requestContext": request_data.context,
}

sdk = app.state.sdk_preview if nimbus_preview else app.state.sdk_live
sdk = app.state.sdk_live
if nimbus_preview:
sdk = app.state.sdk_preview
enrolled_partial_configuration: dict[str, Any] = sdk.compute_enrollments(
targeting_context
)
Expand All @@ -261,51 +257,13 @@ async def compute_features_enrollments(
app.state.fml.compute_feature_configurations(enrolled_partial_configuration)
)

# Enrollments data
enrollment_data = collate_enrollment_metric_data(
enrolled_partial_configuration,
await record_metrics(
enrolled_partial_configuration=enrolled_partial_configuration,
client_id=request_data.client_id,
nimbus_preview_flag=nimbus_preview,
nimbus_preview_flag=nimbus_preview or False,
)

# Record metrics
await record_metrics(enrollment_data)

return {
"features": client_feature_configuration,
"enrollments": enrollment_data,
}


@app.post("/v1/features/", status_code=status.HTTP_200_OK)
async def compute_features_v1(
request_data: FeatureRequest,
nimbus_preview: bool = Query(default=False, alias="nimbus_preview"),
):
result = await compute_features_enrollments(request_data, nimbus_preview)
return result["features"]


@app.post("/v2/features/", status_code=status.HTTP_200_OK)
async def compute_features_enrollments_v2(
request_data: FeatureRequest,
nimbus_preview: bool = Query(default=False, alias="nimbus_preview"),
):
result = await compute_features_enrollments(request_data, nimbus_preview)
return {
"Features": result["features"],
"Enrollments": [
{
"nimbus_user_id": enrollment.nimbus_user_id,
"app_id": enrollment.app_id,
"experiment": enrollment.experiment_slug,
"branch": enrollment.branch_slug,
"experiment_type": enrollment.experiment_type,
"is_preview": enrollment.is_preview,
}
for enrollment in result["enrollments"]
],
}
return client_feature_configuration


async def fetch_schedule_recipes() -> None:
Expand Down
Loading

0 comments on commit ee8f9da

Please sign in to comment.