diff --git a/CHANGELOG.md b/CHANGELOG.md index e2f4629900..b991413fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Fix error when updating closed modal window in Slack by @vadimkerr ([#2019](https://github.com/grafana/oncall/pull/2019)) + ## v1.2.30 (2023-05-25) ### Fixed diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index 72a014e83e..e7316d40a9 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -13,6 +13,7 @@ from .step_mixins import CheckAlertIsUnarchivedMixin logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) class AddToResolutionNoteStep(CheckAlertIsUnarchivedMixin, scenario_step.ScenarioStep): @@ -418,12 +419,23 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da } if "update" in resolution_note_window_action: - self._slack_client.api_call( - "views.update", - trigger_id=payload["trigger_id"], - view=view, - view_id=payload["view"]["id"], - ) + try: + self._slack_client.api_call( + "views.update", + trigger_id=payload["trigger_id"], + view=view, + view_id=payload["view"]["id"], + ) + except SlackAPIException as e: + if e.response["error"] == "not_found": + # Ignore "not_found" error, it means that the view was closed by user before the update request. + # It doesn't disrupt the user experience. + logger.debug( + f"API call to views.update failed for alert group {alert_group_pk}, error: not_found. " + f"Most likely the view was closed by user before the request was processed. " + ) + else: + raise else: self._slack_client.api_call( "views.open", diff --git a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py index 75bf823f18..fb1ac4f3af 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py @@ -1,8 +1,11 @@ import json +from unittest.mock import patch import pytest from apps.slack.scenarios.scenario_step import ScenarioStep +from apps.slack.slack_client import SlackClientWithErrorHandling +from apps.slack.slack_client.exceptions import SlackAPIException from common.api_helpers.utils import create_engine_url @@ -177,3 +180,37 @@ def test_get_resolution_notes_blocks_latest_limit( ] assert blocks == expected_blocks + + +@pytest.mark.django_db +@patch.object( + SlackClientWithErrorHandling, + "api_call", + side_effect=SlackAPIException(response={"ok": False, "error": "not_found"}), +) +def test_resolution_notes_modal_closed_before_update( + mock_slack_api_call, make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group +): + ResolutionNoteModalStep = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") + + organization, _, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + payload = { + "trigger_id": "TEST", + "view": {"id": "TEST"}, + "actions": [ + {"value": json.dumps({"alert_group_pk": alert_group.pk, "resolution_note_window_action": "update"})} + ], + } + + # Check that no error is raised even if the Slack API call fails + step = ResolutionNoteModalStep(organization=organization, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # Check that "views.update" API call was made + call_args, _ = mock_slack_api_call.call_args + assert call_args[0] == "views.update"