Skip to content

Commit

Permalink
feat(alerts): add alerts models, admin, script and service (#237)
Browse files Browse the repository at this point in the history
* feat(alerts): add alerts models, admin, script and service

* fix: update migration
  • Loading branch information
ollz272 authored Apr 30, 2023
1 parent e4ee4a9 commit 4f50441
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 40 deletions.
Empty file added apps/alerts/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions apps/alerts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.contrib import admin

from alerts.models import Alert


# Register your models here.


@admin.register(Alert)
class AlertAdmin(admin.ModelAdmin):
list_filter = ["user", "plant", "sensor"]
readonly_fields = ["created", "updated"]
list_display = ["name", "user", "plant", "sensor"]

fieldsets = [
(
"Alert Info",
{
"fields": ["user", "plant", "sensor", "name", "upper_threshold", "lower_threshold"],
},
),
(
"Meta Data",
{
"classes": ["collapse"],
"fields": ["created", "updated"],
},
),
]
6 changes: 6 additions & 0 deletions apps/alerts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class AlertsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "alerts"
Empty file.
Empty file.
27 changes: 27 additions & 0 deletions apps/alerts/management/commands/scan_for_alerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import logging

from django.core.management.base import BaseCommand

from alerts.models import Alert, AlertLog

# Get an instance of a logger
logger = logging.getLogger(__name__)


class Command(BaseCommand):
"""Command for scanning alerts in the system, and creating any logs needed."""

def handle(self, *args, **options):
alert_logs = []
# TODO have a way of automatically resolving logs if condition is "unmet"
for alert in Alert.objects.all().select_related("user", "plant", "sensor"):
if alert.lower_threshold and alert.latest_data_point <= alert.latest_data_point.data:
if alert.alert_logs.order_by("created").addressed is True:
logger.info(f"Condition met for alert {alert}")
alert_logs.append(AlertLog(user=alert.user, alert=alert))
elif alert.lower_threshold and alert.latest_data_point >= alert.latest_data_point.data:
if alert.alert_logs.order_by("created").addressed is True:
logger.info(f"Condition met for alert {alert}")
alert_logs.append(AlertLog(user=alert.user, alert=alert))

AlertLog.objects.bulk_create(alert_logs)
62 changes: 62 additions & 0 deletions apps/alerts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Generated by Django 4.2 on 2023-04-30 09:19

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
initial = True

dependencies = [
("plants", "0009_alter_datapoint_options"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Alert",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=128)),
("upper_threshold", models.FloatField(blank=True, null=True)),
("lower_threshold", models.FloatField(blank=True, null=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"plant",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="alerts", to="plants.plant"
),
),
(
"sensor",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="alerts", to="plants.sensor"
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="alerts", to=settings.AUTH_USER_MODEL
),
),
],
),
migrations.CreateModel(
name="AlertLog",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("addressed", models.BooleanField(default=False)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"alert",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="alert_logs", to="alerts.alert"
),
),
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
46 changes: 46 additions & 0 deletions apps/alerts/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from functools import cached_property

from django.contrib.auth import get_user_model
from django.db import models

# Create your models here.


class Alert(models.Model):
"""An alert for a user.
An alert is a configurable model that users can create to get notified about changes to their plants.
"""

user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name="alerts")
plant = models.ForeignKey("plants.Plant", on_delete=models.CASCADE, related_name="alerts")
sensor = models.ForeignKey("plants.Sensor", on_delete=models.CASCADE, related_name="alerts")

name = models.CharField(max_length=128)

upper_threshold = models.FloatField(blank=True, null=True)
lower_threshold = models.FloatField(blank=True, null=True)

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

@cached_property
def latest_data_point(self):
return self.sensor.data_points.order_by("time").first()

def __str__(self):
return self.name


class AlertLog(models.Model):
"""An alert log from an alert.
Created when the conditions of an alert are met.
"""

user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
alert = models.ForeignKey(Alert, on_delete=models.CASCADE, related_name="alert_logs")
addressed = models.BooleanField(default=False)

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Empty file added apps/alerts/tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions project/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"api",
"accounts",
"weather",
"alerts",
]

INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + PROJECT_APPS
Expand Down
52 changes: 52 additions & 0 deletions services/alert_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
apiVersion: run.googleapis.com/v1
kind: Job
metadata:
name: alert-scanner
labels:
cloud.googleapis.com/location: europe-west1

spec:
template:
spec:
parallelism: 1
taskCount: 1
template:
spec:
containers:
- image: gcr.io/garden-server-381815/garden-server:1.6.2 # x-release-please-version
command:
- ./manage.py
- scan_for_alerts
env:
- name: DJANGO_SETTINGS_MODULE
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_SETTINGS_MODULE
- name: CSRF_TRUSTED_ORIGINS
valueFrom:
secretKeyRef:
key: latest
name: CSRF_TRUSTED_ORIGINS
- name: ALLOWED_HOST
valueFrom:
secretKeyRef:
key: latest
name: ALLOWED_HOST
- name: SECRET_KEY
valueFrom:
secretKeyRef:
key: latest
name: SECRET_KEY
- name: DATABASE_URL
valueFrom:
secretKeyRef:
key: latest
name: DATABASE_URL
resources:
limits:
cpu: 1000m
memory: 512Mi
maxRetries: 0
timeoutSeconds: '600'
serviceAccountName: 400668159616-compute@developer.gserviceaccount.com
65 changes: 25 additions & 40 deletions services/weather_service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,46 +18,31 @@ spec:
- ./manage.py
- populate_weather
env:
- name: DJANGO_SETTINGS_MODULE
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_SETTINGS_MODULE
- name: DJANGO_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_DATABASE_PASSWORD
- name: DJANGO_DATABASE_HOST
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_DATABASE_HOST
- name: DJANGO_DATABASE_NAME
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_DATABASE_NAME
- name: CSRF_TRUSTED_ORIGINS
valueFrom:
secretKeyRef:
key: latest
name: CSRF_TRUSTED_ORIGINS
- name: ALLOWED_HOST
valueFrom:
secretKeyRef:
key: latest
name: ALLOWED_HOST
- name: SECRET_KEY
valueFrom:
secretKeyRef:
key: latest
name: SECRET_KEY
- name: DJANGO_DATABASE_USER
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_DATABASE_USER
- name: DJANGO_SETTINGS_MODULE
valueFrom:
secretKeyRef:
key: latest
name: DJANGO_SETTINGS_MODULE
- name: CSRF_TRUSTED_ORIGINS
valueFrom:
secretKeyRef:
key: latest
name: CSRF_TRUSTED_ORIGINS
- name: ALLOWED_HOST
valueFrom:
secretKeyRef:
key: latest
name: ALLOWED_HOST
- name: SECRET_KEY
valueFrom:
secretKeyRef:
key: latest
name: SECRET_KEY
- name: DATABASE_URL
valueFrom:
secretKeyRef:
key: latest
name: DATABASE_URL
resources:
limits:
cpu: 1000m
Expand Down

0 comments on commit 4f50441

Please sign in to comment.