From 0735fc89bcf98e30c0c879922667835134efdf64 Mon Sep 17 00:00:00 2001 From: Athul Date: Thu, 11 Jun 2026 13:31:29 +0530 Subject: [PATCH 1/3] UN-2190 Auto-capture execution_id in API deployment Postman collection Add a post-response script to the 'Process document' request that stores message.execution_id into a collection variable, and point the 'Execution status' request's execution_id query param at that variable. Users no longer copy-paste execution IDs between requests (mirrors the LLMWhisperer collection's whisper_hash pattern). Co-Authored-By: Claude Fable 5 --- .../api_v2/postman_collection/constants.py | 2 + backend/api_v2/postman_collection/dto.py | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/backend/api_v2/postman_collection/constants.py b/backend/api_v2/postman_collection/constants.py index 7bcbe988e0..0786d6893a 100644 --- a/backend/api_v2/postman_collection/constants.py +++ b/backend/api_v2/postman_collection/constants.py @@ -6,4 +6,6 @@ class CollectionKey: EXECUTE_PIPELINE_API_KEY = "Process pipeline" STATUS_API_KEY = "Execution status" STATUS_EXEC_ID_DEFAULT = "REPLACE_WITH_EXECUTION_ID" + EXEC_ID_VARIABLE_NAME = "execution_id" + STATUS_EXEC_ID_VARIABLE = "{{execution_id}}" AUTH_QUERY_PARAM_DEFAULT = "REPLACE_WITH_API_KEY" diff --git a/backend/api_v2/postman_collection/dto.py b/backend/api_v2/postman_collection/dto.py index 17a6865eea..1b1164c4a3 100644 --- a/backend/api_v2/postman_collection/dto.py +++ b/backend/api_v2/postman_collection/dto.py @@ -18,6 +18,24 @@ class HeaderItem: value: str +@dataclass +class ScriptItem: + exec: list[str] + type: str = "text/javascript" + + +@dataclass +class EventItem: + listen: str + script: ScriptItem + + +@dataclass +class VariableItem: + key: str + value: str + + @dataclass class FormDataItem: key: str @@ -55,6 +73,7 @@ class RequestItem: class PostmanItem: name: str request: RequestItem + event: list[EventItem] | None = None @dataclass @@ -137,20 +156,39 @@ def get_api_endpoint(self) -> str: def _get_status_api_request(self) -> RequestItem: header_list = [HeaderItem(key="Authorization", value=f"Bearer {self.api_key}")] status_query_param = { - "execution_id": CollectionKey.STATUS_EXEC_ID_DEFAULT, + "execution_id": CollectionKey.STATUS_EXEC_ID_VARIABLE, ApiExecution.INCLUDE_METADATA: "False", ApiExecution.INCLUDE_METRICS: "False", } - status_query_str = urlencode(status_query_param) + # Keep {{...}} unescaped so Postman resolves the collection variable + status_query_str = urlencode(status_query_param, safe="{}") abs_api_endpoint = urljoin(settings.WEB_APP_ORIGIN_URL, self.api_endpoint) status_url = urljoin(abs_api_endpoint, "?" + status_query_str) return RequestItem(method=HTTPMethod.GET, header=header_list, url=status_url) + def _get_execute_capture_event(self) -> EventItem: + """Post-response script that stores the execution_id from the execute + response into a collection variable, so the status request can use it + without manual copy-pasting. + """ + return EventItem( + listen="test", + script=ScriptItem( + exec=[ + "const response = pm.response.json();", + "if (response && response.message && response.message.execution_id) {", # noqa: E501 + f' pm.collectionVariables.set("{CollectionKey.EXEC_ID_VARIABLE_NAME}", response.message.execution_id);', # noqa: E501 + "}", + ] + ), + ) + def get_postman_items(self) -> list[PostmanItem]: postman_item_list = [ PostmanItem( name=CollectionKey.EXECUTE_API_KEY, request=self.get_create_api_request(), + event=[self._get_execute_capture_event()], ), PostmanItem( name=CollectionKey.STATUS_API_KEY, @@ -192,6 +230,7 @@ def get_postman_items(self) -> list[PostmanItem]: class PostmanCollection: info: PostmanInfo item: list[PostmanItem] = field(default_factory=list) + variable: list[VariableItem] = field(default_factory=list) @classmethod def create( @@ -228,7 +267,13 @@ def create( ) postman_info: PostmanInfo = data_object.get_postman_info() postman_item_list = data_object.get_postman_items() - return cls(info=postman_info, item=postman_item_list) + variables = [ + VariableItem( + key=CollectionKey.EXEC_ID_VARIABLE_NAME, + value=CollectionKey.STATUS_EXEC_ID_DEFAULT, + ) + ] + return cls(info=postman_info, item=postman_item_list, variable=variables) def to_dict(self) -> dict[str, Any]: """Convert PostmanCollection instance to a dict. @@ -237,4 +282,8 @@ def to_dict(self) -> dict[str, Any]: dict[str, Any]: PostmanCollection as a dict """ collection_dict = asdict(self) + # Drop null event blocks; Postman expects "event" to be a list + for item in collection_dict.get("item", []): + if item.get("event") is None: + item.pop("event", None) return collection_dict From fe248a4fb8d48dcbca54939a0342afc2e07c5347 Mon Sep 17 00:00:00 2001 From: Athul Date: Thu, 11 Jun 2026 13:41:06 +0530 Subject: [PATCH 2/3] UN-2190 Address review: guard non-JSON responses, scope variable to API deployments - Wrap pm.response.json() in try/catch so error pages (non-JSON) don't surface a Postman test error - Move collection variables behind APIBase.get_collection_variables() so Pipeline collections (no status request) stay variable-free Co-Authored-By: Claude Fable 5 --- backend/api_v2/postman_collection/dto.py | 33 ++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/backend/api_v2/postman_collection/dto.py b/backend/api_v2/postman_collection/dto.py index 1b1164c4a3..681adb9ef2 100644 --- a/backend/api_v2/postman_collection/dto.py +++ b/backend/api_v2/postman_collection/dto.py @@ -88,6 +88,12 @@ class APIBase(ABC): def get_form_data_items(self) -> list[FormDataItem]: pass + def get_collection_variables(self) -> list["VariableItem"]: + """Collection-level variables; only needed when a request + references them. + """ + return [] + @abstractmethod def get_api_endpoint(self) -> str: pass @@ -166,6 +172,14 @@ def _get_status_api_request(self) -> RequestItem: status_url = urljoin(abs_api_endpoint, "?" + status_query_str) return RequestItem(method=HTTPMethod.GET, header=header_list, url=status_url) + def get_collection_variables(self) -> list[VariableItem]: + return [ + VariableItem( + key=CollectionKey.EXEC_ID_VARIABLE_NAME, + value=CollectionKey.STATUS_EXEC_ID_DEFAULT, + ) + ] + def _get_execute_capture_event(self) -> EventItem: """Post-response script that stores the execution_id from the execute response into a collection variable, so the status request can use it @@ -175,7 +189,12 @@ def _get_execute_capture_event(self) -> EventItem: listen="test", script=ScriptItem( exec=[ - "const response = pm.response.json();", + "let response = null;", + "try {", + " response = pm.response.json();", + "} catch (error) {", + " // Non-JSON response (e.g. gateway error); nothing to capture", + "}", "if (response && response.message && response.message.execution_id) {", # noqa: E501 f' pm.collectionVariables.set("{CollectionKey.EXEC_ID_VARIABLE_NAME}", response.message.execution_id);', # noqa: E501 "}", @@ -267,13 +286,11 @@ def create( ) postman_info: PostmanInfo = data_object.get_postman_info() postman_item_list = data_object.get_postman_items() - variables = [ - VariableItem( - key=CollectionKey.EXEC_ID_VARIABLE_NAME, - value=CollectionKey.STATUS_EXEC_ID_DEFAULT, - ) - ] - return cls(info=postman_info, item=postman_item_list, variable=variables) + return cls( + info=postman_info, + item=postman_item_list, + variable=data_object.get_collection_variables(), + ) def to_dict(self) -> dict[str, Any]: """Convert PostmanCollection instance to a dict. From 4bafbf72bfa162700d4523d2847216b78a02632d Mon Sep 17 00:00:00 2001 From: Athul Date: Thu, 11 Jun 2026 13:50:14 +0530 Subject: [PATCH 3/3] UN-2190 Expose execution_id in the API execution response The ExecutionResponse DTO carries execution_id but APIExecutionResponseSerializer dropped it, so the Postman capture script (and any API consumer) had to parse it out of status_api. Add it as a first-class response field; the collection script's message.execution_id lookup now matches the real payload. Co-Authored-By: Claude Fable 5 --- backend/api_v2/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/api_v2/serializers.py b/backend/api_v2/serializers.py index 5bc5aedb7f..3a6f627a88 100644 --- a/backend/api_v2/serializers.py +++ b/backend/api_v2/serializers.py @@ -520,6 +520,7 @@ class DeploymentResponseSerializer(Serializer): class APIExecutionResponseSerializer(Serializer): + execution_id = CharField() execution_status = CharField() status_api = CharField() error = CharField()