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
12 changes: 8 additions & 4 deletions src/firetower/integrations/services/statuspage.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,16 @@ def update_incident(
if components:
# component_ids and components must be in the same payload or Statuspage
# treats them as separate actions and suppresses one webhook.
# Only list non-operational components in component_ids so the
# incident page shows affected components, but send all statuses
# in components so resets to operational are still applied.
# Keep components already attached to the incident in component_ids
# even once they recover: detaching them stops component subscribers
# from receiving this and any later updates, including the resolve.
existing = self.get_incident(incident_id) or {}
affected_ids = [
cid for cid, status in components.items() if status != "operational"
c["id"] for c in existing.get("components") or [] if c.get("id")
]
for cid, status in components.items():
if status != "operational" and cid not in affected_ids:
affected_ids.append(cid)
incident_data["component_ids"] = affected_ids
incident_data["components"] = components

Expand Down
87 changes: 79 additions & 8 deletions src/firetower/integrations/tests/test_statuspage.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,13 @@ def test_update_incident_with_components(self):
with patch.object(settings, "STATUSPAGE", MOCK_STATUSPAGE_CONFIG):
service = StatuspageService()

with patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch:
with (
patch.object(service, "get_incident", return_value={"components": []}),
patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch,
):
service.update_incident(
incident_id="inc123",
components={"comp1": "degraded_performance"},
Expand All @@ -334,10 +337,13 @@ def test_update_incident_filters_operational_from_component_ids(self):
with patch.object(settings, "STATUSPAGE", MOCK_STATUSPAGE_CONFIG):
service = StatuspageService()

with patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch:
with (
patch.object(service, "get_incident", return_value={"components": []}),
patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch,
):
service.update_incident(
incident_id="inc123",
components={
Expand All @@ -358,6 +364,71 @@ def test_update_incident_filters_operational_from_component_ids(self):
"comp3": "partial_outage",
}

def test_update_incident_keeps_existing_components_when_operational(self):
mock_response = MagicMock()
mock_response.json.return_value = {"id": "inc123"}
mock_response.raise_for_status = MagicMock()

with patch.object(settings, "STATUSPAGE", MOCK_STATUSPAGE_CONFIG):
service = StatuspageService()

existing = {"components": [{"id": "comp1"}, {"id": "comp2"}]}
with (
patch.object(
service, "get_incident", return_value=existing
) as mock_get_incident,
patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch,
):
service.update_incident(
incident_id="inc123",
status="resolved",
components={
"comp1": "operational",
"comp2": "operational",
"comp3": "operational",
},
)

mock_get_incident.assert_called_once_with("inc123")
payload = mock_patch.call_args[1]["json"]
assert payload["incident"]["component_ids"] == ["comp1", "comp2"]
assert payload["incident"]["components"] == {
"comp1": "operational",
"comp2": "operational",
"comp3": "operational",
}

def test_update_incident_adds_new_components_to_existing(self):
mock_response = MagicMock()
mock_response.json.return_value = {"id": "inc123"}
mock_response.raise_for_status = MagicMock()

with patch.object(settings, "STATUSPAGE", MOCK_STATUSPAGE_CONFIG):
service = StatuspageService()

existing = {"components": [{"id": "comp1"}]}
with (
patch.object(service, "get_incident", return_value=existing),
patch(
"firetower.integrations.services.statuspage.requests.patch",
return_value=mock_response,
) as mock_patch,
):
service.update_incident(
incident_id="inc123",
components={
"comp1": "operational",
"comp2": "major_outage",
"comp3": "operational",
},
)

payload = mock_patch.call_args[1]["json"]
assert payload["incident"]["component_ids"] == ["comp1", "comp2"]

def test_update_incident_minimal(self):
mock_response = MagicMock()
mock_response.json.return_value = {"id": "inc123"}
Expand Down
Loading