diff --git a/scanpipe/migrations/0057_remove_webhooksubscription_delivery_error_and_more.py b/scanpipe/migrations/0057_remove_webhooksubscription_delivery_error_and_more.py new file mode 100644 index 000000000..d305791c3 --- /dev/null +++ b/scanpipe/migrations/0057_remove_webhooksubscription_delivery_error_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.0.4 on 2024-05-07 13:24 + +import django.db.models.deletion +import scanpipe.models +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("scanpipe", "0056_alter_run_scancodeio_version"), + ] + + operations = [ + migrations.RemoveField( + model_name="webhooksubscription", + name="delivery_error", + ), + migrations.RemoveField( + model_name="webhooksubscription", + name="response_status_code", + ), + migrations.RemoveField( + model_name="webhooksubscription", + name="response_text", + ), + migrations.CreateModel( + name="WebhookDelivery", + fields=[ + ( + "uuid", + models.UUIDField( + db_index=True, + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + verbose_name="UUID", + ), + ), + ("sent_date", models.DateTimeField(auto_now_add=True)), + ("payload", models.JSONField()), + ( + "response_status_code", + models.PositiveIntegerField(blank=True, null=True), + ), + ("response_text", models.TextField(blank=True)), + ("delivery_error", models.TextField(blank=True)), + ( + "project", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)ss", + to="scanpipe.project", + ), + ), + ( + "webhook_subscription", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="deliveries", + to="scanpipe.webhooksubscription", + ), + ), + ], + options={ + "abstract": False, + }, + bases=(scanpipe.models.UpdateMixin, models.Model), + ), + ] diff --git a/scanpipe/models.py b/scanpipe/models.py index 0278cbfb0..a30707757 100644 --- a/scanpipe/models.py +++ b/scanpipe/models.py @@ -3499,11 +3499,8 @@ def as_spdx(self): class WebhookSubscription(UUIDPKModel, ProjectRelatedModel): - target_url = models.URLField(_("Target URL"), max_length=1024) + target_url = models.URLField(_("Target URL"), max_length=1024, blank=False) created_date = models.DateTimeField(auto_now_add=True, editable=False) - response_status_code = models.PositiveIntegerField(null=True, blank=True) - response_text = models.TextField(blank=True) - delivery_error = models.TextField(blank=True) def __str__(self): return str(self.uuid) @@ -3523,35 +3520,52 @@ def get_payload(self, pipeline_run): }, } - def deliver(self, pipeline_run): + def deliver(self, pipeline_run, timeout=10): """Deliver this Webhook by sending a POST request to the `target_url`.""" + delivery = WebhookDelivery.objects.create(webhook_subscription=self) payload = self.get_payload(pipeline_run) - logger.info(f"Sending Webhook uuid={self.uuid}.") + logger.info(f"Posting Webhook uuid={self.uuid}.") try: response = requests.post( url=self.target_url, data=json.dumps(payload, cls=DjangoJSONEncoder), headers={"Content-Type": "application/json"}, - timeout=10, + timeout=timeout, ) except requests.exceptions.RequestException as exception: logger.info(exception) - self.update(delivery_error=str(exception)) + delivery.update(delivery_error=str(exception)) return False - self.update( + delivery.update( response_status_code=response.status_code, response_text=response.text, ) - if self.success: - logger.info(f"Webhook uuid={self.uuid} delivered and received.") + if delivery.success: + logger.info(f"Webhook uuid={self.uuid} delivered successfully.") else: logger.info(f"Webhook uuid={self.uuid} returned a {response.status_code}.") return True + +class WebhookDelivery(UUIDPKModel, ProjectRelatedModel): + webhook_subscription = models.ForeignKey( + WebhookSubscription, + related_name="deliveries", + on_delete=models.CASCADE, + ) + sent_date = models.DateTimeField(auto_now_add=True, editable=False) + payload = models.JSONField() + response_status_code = models.PositiveIntegerField(null=True, blank=True) + response_text = models.TextField(blank=True) + delivery_error = models.TextField(blank=True) + + def __str__(self): + return f"Webhook uuid={self.uuid} posted at {self.sent_date}" + @property def delivered(self): return bool(self.response_status_code)