Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions api/shipping_features_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@
)
from internals.core_models import FeatureEntry, Stage
from internals.review_models import Gate, Vote
from internals.data_types import VerboseFeatureDict


class GetShippingFeaturesResponse(TypedDict):
complete_features: list[VerboseFeatureDict]
incomplete_features: list[tuple[VerboseFeatureDict, list[str]]]
complete_features: list[str]
incomplete_features: list[tuple[str, list[str]]]


class ShippingFeaturesAPI(basehandlers.EntitiesAPIHandler):
Expand Down Expand Up @@ -59,19 +58,21 @@ def do_get(self, **kwargs) -> GetShippingFeaturesResponse:

shipping_stages = self._get_shipping_stages(milestone)

complete_features: list[VerboseFeatureDict] = []
incomplete_features: list[tuple[VerboseFeatureDict, list[str]]] = []
complete_features: list[str] = []
incomplete_features: list[tuple[str, list[str]]] = []
for stage in shipping_stages:
criteria_missing: list[str] = []
feature = FeatureEntry.get_by_id(stage.feature_id)
feature: FeatureEntry | None = FeatureEntry.get_by_id(stage.feature_id)
if feature is None:
logging.warning(f'Feature {stage.feature_id} not found.')
continue

chromestatus_url = (f'{self.request.scheme}://{self.request.host}'
f'/feature/{feature.key.integer_id()}')

if feature.feature_type == FEATURE_TYPE_CODE_CHANGE_ID:
# PSA features do not require intents or approvals.
feature_dict = converters.feature_entry_to_json_verbose(feature)
complete_features.append(feature_dict)
complete_features.append(chromestatus_url)
continue

api_owner_gate: Gate | None = Gate.query(
Expand All @@ -84,11 +85,10 @@ def do_get(self, **kwargs) -> GetShippingFeaturesResponse:
if not feature.finch_name and not feature.non_finch_justification:
criteria_missing.append('finch_name')

feature_dict = converters.feature_entry_to_json_verbose(feature)
if criteria_missing:
incomplete_features.append((feature_dict, criteria_missing))
incomplete_features.append((chromestatus_url, criteria_missing))
else:
complete_features.append(feature_dict)
complete_features.append(chromestatus_url)

return {
'complete_features': complete_features,
Expand Down
13 changes: 10 additions & 3 deletions api/shipping_features_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,26 @@ def test_do_get__success(self, mock_logging):
response = self.handler.do_get(mstone=self.milestone)

# Verify Complete Features
# The response now contains a list of URLs.
complete_features = response['complete_features']
self.assertEqual(len(complete_features), 2)
complete_ids = {f['id'] for f in complete_features}
# Feature 1 is complete, Feature 5 is a PSA that bypasses checks.
self.assertEqual(complete_ids, {self.feature_1.key.integer_id(), self.feature_5.key.integer_id()})
expected_complete_urls = {
'http://localhost/feature/1',
'http://localhost/feature/5'
}
self.assertEqual(set(complete_features), expected_complete_urls)

# Verify Incomplete Features
# The response is a list of (URL, reasons) tuples.
incomplete_features = response['incomplete_features']
self.assertEqual(len(incomplete_features), 3)

# Use a dictionary for easy, order-independent lookup and assertion.
# Parse the feature ID from the URL to use as the dictionary key.
incomplete_map = {
item[0]['id']: item[1] for item in incomplete_features
int(url.split('/')[-1]): reasons
for url, reasons in incomplete_features
}
self.assertIn(self.feature_2.key.integer_id(), incomplete_map)
self.assertEqual(incomplete_map[self.feature_2.key.integer_id()], ['lgtms'])
Expand Down
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class Route:
intents_api.IntentsAPI),
Route(f'{API_BASE}/features/<int:feature_id>/<int:stage_id>/<int:gate_id>/intent',
intents_api.IntentsAPI),
Route(f'{API_BASE}/features/shipping/<int:mstone>',
Route(f'{API_BASE}/features/shipping',
shipping_features_api.ShippingFeaturesAPI),
Route(f'{API_BASE}/features/stale',
stale_features_api.StaleFeaturesAPI),
Expand Down