Send demo alert with dynamic payload and get demo payload example on private api (#1700)
# What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
e93347e75b
commit
f825fdf1a3
8 changed files with 69 additions and 18 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- Helm chart: add the option to use a helm hook for the migration job ([1386](https://github.com/grafana/oncall/pull/1386))
|
||||
- Send demo alert with dynamic payload and get demo payload example on private api ([1700](https://github.com/grafana/oncall/pull/1700))
|
||||
|
||||
## v1.2.11 (2023-04-14)
|
||||
|
||||
|
|
|
|||
|
|
@ -501,19 +501,26 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject):
|
|||
return getattr(heartbeat, self.INTEGRATIONS_TO_REVERSE_URL_MAP[self.integration], None)
|
||||
|
||||
# Demo alerts
|
||||
def send_demo_alert(self, force_route_id=None):
|
||||
def send_demo_alert(self, force_route_id=None, payload=None):
|
||||
logger.info(f"send_demo_alert integration={self.pk} force_route_id={force_route_id}")
|
||||
if payload is None:
|
||||
payload = self.config.example_payload
|
||||
if self.is_demo_alert_enabled:
|
||||
if self.has_alertmanager_payload_structure:
|
||||
for alert in self.config.example_payload.get("alerts", []):
|
||||
create_alertmanager_alerts.apply_async(
|
||||
[],
|
||||
{
|
||||
"alert_receive_channel_pk": self.pk,
|
||||
"alert": alert,
|
||||
"is_demo": True,
|
||||
"force_route_id": force_route_id,
|
||||
},
|
||||
if (alerts := payload.get("alerts", None)) and type(alerts) == list and len(alerts):
|
||||
for alert in alerts:
|
||||
create_alertmanager_alerts.apply_async(
|
||||
[],
|
||||
{
|
||||
"alert_receive_channel_pk": self.pk,
|
||||
"alert": alert,
|
||||
"is_demo": True,
|
||||
"force_route_id": force_route_id,
|
||||
},
|
||||
)
|
||||
else:
|
||||
raise UnableToSendDemoAlert(
|
||||
"Unable to send demo alert as payload has no 'alerts' key, it is not array, or it is empty."
|
||||
)
|
||||
else:
|
||||
create_alert.apply_async(
|
||||
|
|
@ -525,7 +532,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject):
|
|||
"link_to_upstream_details": None,
|
||||
"alert_receive_channel_pk": self.pk,
|
||||
"integration_unique_data": None,
|
||||
"raw_request_data": self.config.example_payload,
|
||||
"raw_request_data": payload,
|
||||
"is_demo": True,
|
||||
"force_route_id": force_route_id,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ class ChannelFilter(OrderedModel):
|
|||
@classmethod
|
||||
def select_filter(cls, alert_receive_channel, raw_request_data, force_route_id=None):
|
||||
# Try to find force route first if force_route_id is given
|
||||
# Force route was used to send demo alerts to specific route.
|
||||
# It is deprecated and may be used by older versions of the plugins
|
||||
if force_route_id is not None:
|
||||
logger.info(
|
||||
f"start select_filter with force_route_id={force_route_id} alert_receive_channel={alert_receive_channel.pk}."
|
||||
|
|
@ -164,6 +166,7 @@ class ChannelFilter(OrderedModel):
|
|||
raise Exception("Unknown filtering term")
|
||||
|
||||
def send_demo_alert(self):
|
||||
"""Deprecated. May be used in the older versions of the plugin"""
|
||||
integration = self.alert_receive_channel
|
||||
integration.send_demo_alert(force_route_id=self.pk)
|
||||
|
||||
|
|
|
|||
|
|
@ -90,14 +90,25 @@ def test_get_default_template_attribute_fallback_to_web(make_organization, make_
|
|||
|
||||
@mock.patch("apps.integrations.tasks.create_alert.apply_async", return_value=None)
|
||||
@pytest.mark.django_db
|
||||
def test_send_demo_alert(mocked_create_alert, make_organization, make_alert_receive_channel):
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
None,
|
||||
{"foo": "bar"},
|
||||
],
|
||||
)
|
||||
def test_send_demo_alert(mocked_create_alert, make_organization, make_alert_receive_channel, payload):
|
||||
organization = make_organization()
|
||||
alert_receive_channel = make_alert_receive_channel(
|
||||
organization, integration=AlertReceiveChannel.INTEGRATION_WEBHOOK
|
||||
)
|
||||
alert_receive_channel.send_demo_alert()
|
||||
alert_receive_channel.send_demo_alert(payload=payload)
|
||||
assert mocked_create_alert.called
|
||||
assert mocked_create_alert.call_args.args[1]["is_demo"]
|
||||
assert (
|
||||
mocked_create_alert.call_args.args[1]["raw_request_data"] == payload
|
||||
or alert_receive_channel.config.example_payload
|
||||
)
|
||||
assert mocked_create_alert.call_args.args[1]["force_route_id"] is None
|
||||
|
||||
|
||||
|
|
@ -111,14 +122,26 @@ def test_send_demo_alert(mocked_create_alert, make_organization, make_alert_rece
|
|||
AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
None,
|
||||
{"alerts": [{"foo": "bar"}]},
|
||||
],
|
||||
)
|
||||
def test_send_demo_alert_alertmanager_payload_shape(
|
||||
mocked_create_alert, make_organization, make_alert_receive_channel, integration
|
||||
mocked_create_alert, make_organization, make_alert_receive_channel, integration, payload
|
||||
):
|
||||
organization = make_organization()
|
||||
alert_receive_channel = make_alert_receive_channel(organization, integration=integration)
|
||||
alert_receive_channel.send_demo_alert()
|
||||
alert_receive_channel.send_demo_alert(payload=payload)
|
||||
assert mocked_create_alert.called
|
||||
assert mocked_create_alert.call_args.args[1]["is_demo"]
|
||||
assert (
|
||||
mocked_create_alert.call_args.args[1]["alert"] == payload["alerts"][0]
|
||||
if payload
|
||||
else alert_receive_channel.config.example_payload["alerts"][0]
|
||||
)
|
||||
assert mocked_create_alert.call_args.args[1]["force_route_id"] is None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ
|
|||
maintenance_till = serializers.ReadOnlyField(source="till_maintenance_timestamp")
|
||||
heartbeat = serializers.SerializerMethodField()
|
||||
allow_delete = serializers.SerializerMethodField()
|
||||
demo_alert_payload = serializers.SerializerMethodField()
|
||||
|
||||
# integration heartbeat is in PREFETCH_RELATED not by mistake.
|
||||
# With using of select_related ORM builds strange join
|
||||
|
|
@ -82,6 +83,7 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ
|
|||
"heartbeat",
|
||||
"is_available_for_integration_heartbeat",
|
||||
"allow_delete",
|
||||
"demo_alert_payload",
|
||||
]
|
||||
read_only_fields = [
|
||||
"created_at",
|
||||
|
|
@ -92,6 +94,7 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ
|
|||
"instructions",
|
||||
"demo_alert_enabled",
|
||||
"maintenance_mode",
|
||||
"demo_alert_payload",
|
||||
]
|
||||
extra_kwargs = {"integration": {"required": True}}
|
||||
|
||||
|
|
@ -153,6 +156,9 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ
|
|||
def get_alert_groups_count(self, obj):
|
||||
return 0
|
||||
|
||||
def get_demo_alert_payload(self, obj):
|
||||
return obj.config.example_payload
|
||||
|
||||
|
||||
class AlertReceiveChannelUpdateSerializer(AlertReceiveChannelSerializer):
|
||||
class Meta(AlertReceiveChannelSerializer.Meta):
|
||||
|
|
|
|||
|
|
@ -149,9 +149,19 @@ class AlertReceiveChannelView(
|
|||
|
||||
@action(detail=True, methods=["post"], throttle_classes=[DemoAlertThrottler])
|
||||
def send_demo_alert(self, request, pk):
|
||||
instance = AlertReceiveChannel.objects.get(public_primary_key=pk)
|
||||
alert_receive_channel = AlertReceiveChannel.objects.get(public_primary_key=pk)
|
||||
demo_alert_payload = request.data.get("demo_alert_payload", None)
|
||||
|
||||
if not demo_alert_payload:
|
||||
# If no payload provided, use the demo payload for backword compatibility
|
||||
payload = alert_receive_channel.config.example_payload
|
||||
else:
|
||||
if type(demo_alert_payload) != dict:
|
||||
raise BadRequest(detail="Payload for demo alert must be a valid json object")
|
||||
payload = demo_alert_payload
|
||||
|
||||
try:
|
||||
instance.send_demo_alert()
|
||||
alert_receive_channel.send_demo_alert(payload=payload)
|
||||
except UnableToSendDemoAlert as e:
|
||||
raise BadRequest(detail=str(e))
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ class ChannelFilterView(
|
|||
|
||||
@action(detail=True, methods=["post"], throttle_classes=[DemoAlertThrottler])
|
||||
def send_demo_alert(self, request, pk):
|
||||
"""Deprecated action. May be used in the older version of the plugin."""
|
||||
instance = ChannelFilter.objects.get(public_primary_key=pk)
|
||||
try:
|
||||
instance.send_demo_alert()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
class OperationCouldNotBePerformedError(Exception):
|
||||
"""
|
||||
Indicates that operation could not be performed due to to application logic.
|
||||
Indicates that operation could not be performed due to application logic.
|
||||
E.g. you can't ack resolved AlertGroup
|
||||
"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue