Skip to content

Commit 99c498e

Browse files
committed
Merge branch 'main' into 20260724-fix-failing-feature_links-tasks
2 parents 8d2c7b5 + 21bfc07 commit 99c498e

24 files changed

Lines changed: 761 additions & 189 deletions

api/converters.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@
1717

1818
import datetime
1919
import re
20+
from enum import StrEnum
21+
from types import MappingProxyType
2022
from typing import Any, Optional, TypedDict
2123

24+
from chromestatus_openapi.models import (
25+
MilestoneCurationResponse,
26+
ReleaseNoteFeature,
27+
SummaryProgressStep,
28+
SummarySuggestion,
29+
)
2230
from google.cloud import ndb # type: ignore
2331

2432
import settings
@@ -31,6 +39,9 @@
3139
)
3240
from internals.core_models import (
3341
FeatureEntry,
42+
FeatureSummaryProgressStep,
43+
FeatureSummarySuggestion,
44+
MilestoneCuration,
3445
MilestoneSet,
3546
ReviewResultProperty,
3647
Stage,
@@ -847,3 +858,238 @@ def gate_value_to_json_dict(gate: Gate) -> dict[str, Any]:
847858
'self_certify_eligible': self_certify_eligible,
848859
'survey_answers': survey_answers,
849860
}
861+
862+
863+
# Default fallback category display name for unmapped FeatureEntry category IDs.
864+
DEFAULT_CATEGORY_NAME = core_enums.FEATURE_CATEGORIES[core_enums.MISC]
865+
866+
867+
# OpenAPI StrEnum definitions for Datastore-to-API status mappings.
868+
class OpenAPISummarySource(StrEnum):
869+
"""OpenAPI string constants for release note summary origins."""
870+
871+
HUMAN = 'HUMAN'
872+
AI_APPLIED = 'AI_APPLIED'
873+
874+
875+
class OpenAPISuggestionStatus(StrEnum):
876+
"""OpenAPI string constants for summary suggestion review statuses."""
877+
878+
PENDING = 'PENDING'
879+
APPLIED = 'APPLIED'
880+
REJECTED = 'REJECTED'
881+
DISCARDED = 'DISCARDED'
882+
883+
884+
class OpenAPIBaselineStatus(StrEnum):
885+
"""OpenAPI string constants for WebDX Baseline compatibility statuses."""
886+
887+
NONE = 'none'
888+
LIMITED = 'limited'
889+
NEWLY = 'newly'
890+
WIDELY = 'widely'
891+
892+
893+
class OpenAPIProgressStepId(StrEnum):
894+
"""OpenAPI string constants for AI summary progress step identifiers."""
895+
896+
UNKNOWN = 'UNKNOWN'
897+
SEARCH_MDN = 'SEARCH_MDN'
898+
VERIFY_DOC_LINK = 'VERIFY_DOC_LINK'
899+
READ_SPEC = 'READ_SPEC'
900+
READ_EXPLAINER = 'READ_EXPLAINER'
901+
902+
903+
class OpenAPIProgressStepStatus(StrEnum):
904+
"""OpenAPI string constants for AI summary progress step execution statuses."""
905+
906+
IN_PROGRESS = 'IN_PROGRESS'
907+
SUCCESS = 'SUCCESS'
908+
FAILED = 'FAILED'
909+
RETRYING = 'RETRYING'
910+
911+
912+
class OpenAPICurationStatus(StrEnum):
913+
"""OpenAPI string constants for milestone release note curation statuses."""
914+
915+
PENDING = 'PENDING'
916+
IN_REVIEW = 'IN_REVIEW'
917+
COMPLETED = 'COMPLETED'
918+
919+
920+
SUMMARY_SUGGESTION_STATUS_TO_API: MappingProxyType[
921+
core_enums.SummarySuggestionStatus, OpenAPISuggestionStatus
922+
] = MappingProxyType(
923+
{
924+
core_enums.SummarySuggestionStatus.UNKNOWN: OpenAPISuggestionStatus.PENDING,
925+
core_enums.SummarySuggestionStatus.PROPOSED: OpenAPISuggestionStatus.PENDING,
926+
core_enums.SummarySuggestionStatus.PENDING: OpenAPISuggestionStatus.PENDING,
927+
core_enums.SummarySuggestionStatus.APPLIED: OpenAPISuggestionStatus.APPLIED,
928+
core_enums.SummarySuggestionStatus.REJECTED: OpenAPISuggestionStatus.REJECTED,
929+
core_enums.SummarySuggestionStatus.DISCARDED: OpenAPISuggestionStatus.DISCARDED,
930+
core_enums.SummarySuggestionStatus.SKIPPED: OpenAPISuggestionStatus.DISCARDED,
931+
}
932+
)
933+
934+
BASELINE_STATUS_TO_API: MappingProxyType[
935+
core_enums.BaselineStatus, OpenAPIBaselineStatus
936+
] = MappingProxyType(
937+
{
938+
core_enums.BaselineStatus.NONE: OpenAPIBaselineStatus.NONE,
939+
core_enums.BaselineStatus.LIMITED: OpenAPIBaselineStatus.LIMITED,
940+
core_enums.BaselineStatus.NEWLY: OpenAPIBaselineStatus.NEWLY,
941+
core_enums.BaselineStatus.WIDELY: OpenAPIBaselineStatus.WIDELY,
942+
}
943+
)
944+
945+
PROGRESS_STEP_ID_TO_API: MappingProxyType[
946+
core_enums.ProgressStepId, OpenAPIProgressStepId
947+
] = MappingProxyType(
948+
{
949+
core_enums.ProgressStepId.UNKNOWN: OpenAPIProgressStepId.UNKNOWN,
950+
core_enums.ProgressStepId.START: OpenAPIProgressStepId.UNKNOWN,
951+
core_enums.ProgressStepId.SEARCH_MDN: OpenAPIProgressStepId.SEARCH_MDN,
952+
core_enums.ProgressStepId.VERIFY_DOC_LINK: OpenAPIProgressStepId.VERIFY_DOC_LINK,
953+
core_enums.ProgressStepId.READ_SPEC: OpenAPIProgressStepId.READ_SPEC,
954+
core_enums.ProgressStepId.READ_EXPLAINER: OpenAPIProgressStepId.READ_EXPLAINER,
955+
core_enums.ProgressStepId.LLM_GENERATION: OpenAPIProgressStepId.UNKNOWN,
956+
core_enums.ProgressStepId.EVALUATION: OpenAPIProgressStepId.UNKNOWN,
957+
core_enums.ProgressStepId.SUCCESS: OpenAPIProgressStepId.UNKNOWN,
958+
}
959+
)
960+
961+
PROGRESS_STEP_STATUS_TO_API: MappingProxyType[
962+
core_enums.ProgressStepStatus, OpenAPIProgressStepStatus
963+
] = MappingProxyType(
964+
{
965+
core_enums.ProgressStepStatus.UNKNOWN: OpenAPIProgressStepStatus.IN_PROGRESS,
966+
core_enums.ProgressStepStatus.START: OpenAPIProgressStepStatus.IN_PROGRESS,
967+
core_enums.ProgressStepStatus.IN_PROGRESS: OpenAPIProgressStepStatus.IN_PROGRESS,
968+
core_enums.ProgressStepStatus.SUCCESS: OpenAPIProgressStepStatus.SUCCESS,
969+
core_enums.ProgressStepStatus.FAILED: OpenAPIProgressStepStatus.FAILED,
970+
core_enums.ProgressStepStatus.RETRYING: OpenAPIProgressStepStatus.RETRYING,
971+
}
972+
)
973+
974+
MILESTONE_CURATION_STATUS_TO_API: MappingProxyType[
975+
core_enums.MilestoneCurationStatus, OpenAPICurationStatus
976+
] = MappingProxyType(
977+
{
978+
core_enums.MilestoneCurationStatus.PENDING: OpenAPICurationStatus.PENDING,
979+
core_enums.MilestoneCurationStatus.IN_REVIEW: OpenAPICurationStatus.IN_REVIEW,
980+
core_enums.MilestoneCurationStatus.COMPLETED: OpenAPICurationStatus.COMPLETED,
981+
}
982+
)
983+
984+
985+
def feature_summary_suggestion_to_dict(
986+
suggestion: FeatureSummarySuggestion,
987+
) -> dict[str, Any]:
988+
"""Converts a FeatureSummarySuggestion entity into a dict matching SummarySuggestion schema."""
989+
status_str = SUMMARY_SUGGESTION_STATUS_TO_API.get(
990+
suggestion.status, OpenAPISuggestionStatus.PENDING
991+
)
992+
baseline_str = BASELINE_STATUS_TO_API.get(
993+
suggestion.baseline_status, OpenAPIBaselineStatus.NONE
994+
)
995+
996+
model = SummarySuggestion(
997+
feature_id=suggestion.key.integer_id(),
998+
suggested_summary=suggestion.suggested_summary,
999+
original_summary=suggestion.original_summary,
1000+
status=status_str,
1001+
baseline_status=baseline_str,
1002+
reasoning=suggestion.generation_rationale,
1003+
suggested_doc_links=suggestion.suggested_doc_links or [],
1004+
version_token=suggestion.version_token,
1005+
created=_date_to_str(suggestion.created),
1006+
updated=_date_to_str(suggestion.updated),
1007+
)
1008+
return model.to_dict()
1009+
1010+
1011+
def summary_progress_step_to_dict(
1012+
step: FeatureSummaryProgressStep,
1013+
) -> dict[str, Any]:
1014+
"""Converts a FeatureSummaryProgressStep entity into a dict matching SummaryProgressStep schema."""
1015+
step_str = PROGRESS_STEP_ID_TO_API.get(
1016+
step.step_id, OpenAPIProgressStepId.UNKNOWN
1017+
)
1018+
status_str = PROGRESS_STEP_STATUS_TO_API.get(
1019+
step.status, OpenAPIProgressStepStatus.IN_PROGRESS
1020+
)
1021+
1022+
model = SummaryProgressStep(
1023+
step=step_str,
1024+
status=status_str,
1025+
message=step.message,
1026+
start_timestamp=_date_to_str(step.start_timestamp),
1027+
end_timestamp=_date_to_str(step.end_timestamp),
1028+
)
1029+
return model.to_dict()
1030+
1031+
1032+
def milestone_curation_to_dict(curation: MilestoneCuration) -> dict[str, Any]:
1033+
"""Converts a MilestoneCuration entity into a dict matching MilestoneCurationResponse schema."""
1034+
status_str = MILESTONE_CURATION_STATUS_TO_API.get(
1035+
curation.status, OpenAPICurationStatus.PENDING
1036+
)
1037+
1038+
model = MilestoneCurationResponse(
1039+
milestone=curation.milestone,
1040+
status=status_str,
1041+
curator_emails=curation.curator_emails or [],
1042+
last_reviewed=_date_to_str(curation.updated),
1043+
)
1044+
return model.to_dict()
1045+
1046+
1047+
def feature_entry_to_release_note_feature_dict(
1048+
fe: FeatureEntry,
1049+
applied_suggestion: FeatureSummarySuggestion | None = None,
1050+
baseline_status: core_enums.BaselineStatus | None = None,
1051+
has_applied_suggestion: bool | None = None,
1052+
) -> dict[str, Any]:
1053+
"""Converts a FeatureEntry into a dict matching the ReleaseNoteFeature schema."""
1054+
feature_id = fe.key.integer_id()
1055+
category_int = fe.category if fe.category is not None else 0
1056+
category_name = core_enums.FEATURE_CATEGORIES.get(
1057+
category_int, DEFAULT_CATEGORY_NAME
1058+
)
1059+
feature_type_int = fe.feature_type if fe.feature_type is not None else 0
1060+
1061+
if (
1062+
applied_suggestion is not None
1063+
and applied_suggestion.status
1064+
== core_enums.SummarySuggestionStatus.APPLIED
1065+
):
1066+
summary_text = applied_suggestion.suggested_summary or fe.summary or ''
1067+
summary_source = OpenAPISummarySource.AI_APPLIED
1068+
effective_baseline = (
1069+
applied_suggestion.baseline_status or baseline_status
1070+
)
1071+
elif has_applied_suggestion:
1072+
summary_text = fe.summary or ''
1073+
summary_source = OpenAPISummarySource.AI_APPLIED
1074+
effective_baseline = baseline_status
1075+
else:
1076+
summary_text = fe.summary or ''
1077+
summary_source = OpenAPISummarySource.HUMAN
1078+
effective_baseline = baseline_status
1079+
1080+
baseline_val = BASELINE_STATUS_TO_API.get(
1081+
effective_baseline or core_enums.BaselineStatus.NONE,
1082+
OpenAPIBaselineStatus.NONE,
1083+
)
1084+
1085+
model = ReleaseNoteFeature(
1086+
id=feature_id,
1087+
name=fe.name or '',
1088+
summary=summary_text,
1089+
category=category_int,
1090+
category_name=category_name,
1091+
feature_type=feature_type_int,
1092+
baseline_status=baseline_val,
1093+
summary_source=summary_source,
1094+
)
1095+
return model.to_dict()

0 commit comments

Comments
 (0)