Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add actions #1

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion core/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib import admin
from core.models import Incident
from core.models import Incident, Action, ExternalUser

admin.site.register(Action)
admin.site.register(Incident)
admin.site.register(ExternalUser)
29 changes: 29 additions & 0 deletions core/migrations/0004_create_ExternalUser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 2.2.2 on 2019-06-24 14:22

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


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('core', '0003_incidentextension'),
]

operations = [
migrations.CreateModel(
name='ExternalUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('app_id', models.CharField(max_length=50)),
('external_id', models.CharField(max_length=50)),
('display_name', models.CharField(max_length=50)),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('owner', 'app_id')},
},
),
]
69 changes: 69 additions & 0 deletions core/migrations/0005_alter_use_ExternalUser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Generated by Django 2.2.2 on 2019-06-24 14:22

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


def MoveToExternalID(apps, schema_editor):
Incident = apps.get_model('core', 'Incident')
ExternalUser = apps.get_model('core', 'ExternalUser')

for inc in Incident.objects.all():
inc.reporter = ExternalUser.objects.get(external_id=inc.reporter_tmp)
if inc.lead_tmp:
inc.lead = ExternalUser.objects.get(external_id=inc.lead_tmp)
inc.save()


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('core', '0004_create_ExternalUser'),
('slack','0003_auto_20190624_1422'),
]

operations = [
migrations.RenameField(
model_name='incident',
old_name='lead',
new_name='lead_tmp',
),
migrations.RenameField(
model_name='incident',
old_name='reporter',
new_name='reporter_tmp',
),
migrations.AddField(
model_name='incident',
name='lead',
field=models.ForeignKey(blank=True, help_text='Who is leading?', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='lead', to='core.ExternalUser'),
preserve_default=False,
),
migrations.AddField(
model_name='incident',
name='reporter',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to='core.ExternalUser'),
preserve_default=False,
),
migrations.RunPython(MoveToExternalID),
migrations.RemoveField(
model_name='incident',
name='reporter_tmp',
),
migrations.RemoveField(
model_name='incident',
name='lead_tmp',
),
migrations.CreateModel(
name='Action',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('details', models.TextField(blank=True, default='')),
('done', models.BooleanField(default=False)),
('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.ExternalUser')),
],
),
]
14 changes: 14 additions & 0 deletions core/migrations/0006_merge_20190703_0812.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.2.2 on 2019-07-03 08:12

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0005_alter_use_ExternalUser'),
('core', '0004_auto_20190627_0907'),
]

operations = [
]
2 changes: 2 additions & 0 deletions core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .action import *
from .incident import *
from .user_external import *
17 changes: 17 additions & 0 deletions core/models/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import datetime
from django.db import models
from core.models.incident import Incident
from core.models.user_external import ExternalUser


class Action(models.Model):
details = models.TextField(blank=True, default="")
done = models.BooleanField(default=False)
incident = models.ForeignKey(Incident, on_delete=models.CASCADE)
user = models.ForeignKey(ExternalUser, on_delete=models.CASCADE, blank=False, null=False)

def icon(self):
return "🔜️"

def __str__(self):
return f"{self.details}"
7 changes: 4 additions & 3 deletions core/models/incident.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from django.db import models

from core.models.user_external import ExternalUser

class IncidentManager(models.Manager):
def create_incident(self, report, reporter, report_time, summary=None, impact=None, squad=None, lead=None):
Expand All @@ -24,7 +24,7 @@ class Incident(models.Model):

# Reporting info
report = models.CharField(max_length=200)
reporter = models.CharField(max_length=50, default="")
reporter = models.ForeignKey(ExternalUser, related_name='reporter', on_delete=models.PROTECT, blank=False, null=True,)
report_time = models.DateTimeField()

start_time = models.DateTimeField(null=False)
Expand All @@ -33,7 +33,8 @@ class Incident(models.Model):
# Additional info
summary = models.TextField(blank=True, null=True, help_text="What's the high level summary?")
impact = models.TextField(blank=True, null=True, help_text="What impact is this having?")
lead = models.CharField(max_length=50, blank=True, null=True, help_text="Who is leading?")
lead = models.ForeignKey(ExternalUser, related_name='lead', on_delete=models.PROTECT, blank=True, null=True, help_text="Who is leading?")


# Severity
# SEVERITIES = (
Expand Down
16 changes: 16 additions & 0 deletions core/models/user_external.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User


class ExternalUser(models.Model):
class Meta:
unique_together = ("owner", "app_id")

owner = models.ForeignKey(User, on_delete=models.PROTECT, null=True, blank=True)
app_id = models.CharField(max_length=50, blank=False, null=False)
external_id = models.CharField(max_length=50, blank=False, null=False)
display_name = models.CharField(max_length=50, blank=False, null=False)

def __str__(self):
return f'{self.display_name or self.external_id} ({self.app_id})'
43 changes: 43 additions & 0 deletions core/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from rest_framework import serializers
from rest_framework.decorators import action

from core.models.incident import Incident
from core.models.action import Action
from core.models.user_external import ExternalUser

from django.contrib.auth.models import User


class ExternalUserSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)

class Meta:
model = ExternalUser
fields = ('app_id', 'external_id', 'owner', 'display_name')


class ActionSerializer(serializers.HyperlinkedModelSerializer):
# Serializers define the API representation.
incident = serializers.PrimaryKeyRelatedField(queryset=Incident.objects.all(), required=False)
user = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False)

class Meta:
model = Action
fields = ('pk', 'details', 'done', 'incident', 'user')


class IncidentSerializer(serializers.HyperlinkedModelSerializer):
reporter = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False)
lead = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False)

class Meta:
model = Incident
fields = ('pk','report', 'reporter', 'lead', 'start_time', 'end_time', 'report_time', 'action_set')

def __init__(self, *args, **kwargs):
super(IncidentSerializer, self).__init__(*args, **kwargs)
request = kwargs['context']['request']
expand = request.GET.get('expand', "").split(',')

if 'actions' in expand:
self.fields['action_set'] = ActionSerializer(many=True, read_only=True)
48 changes: 40 additions & 8 deletions core/urls.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
from django.conf.urls import url, include
from rest_framework import routers, serializers, viewsets
from rest_framework import routers, viewsets, pagination
from rest_framework.decorators import action

from core.models.incident import Incident
from core.models.action import Action
from core.models.user_external import ExternalUser

from datetime import datetime
from calendar import monthrange

class IncidentSerializer(serializers.HyperlinkedModelSerializer):
# Serializers define the API representation.
class Meta:
model = Incident
fields = ('report', 'reporter', 'report_time')
from core.serializers import *

class ExternalUserViewSet(viewsets.ModelViewSet):
# ViewSets define the view behavior.
queryset = ExternalUser.objects.all()
serializer_class = ExternalUserSerializer


class ActionViewSet(viewsets.ModelViewSet):
# ViewSets define the view behavior.
queryset = Action.objects.all()
serializer_class = ActionSerializer


# Will return the incidents of the current month
# Can pass ?start=2019-05-28&end=2019-06-03 to change range
class IncidentViewSet(viewsets.ModelViewSet):
# ViewSets define the view behavior.
queryset = Incident.objects.all()

serializer_class = IncidentSerializer
pagination_class = None # Remove pagination

def get_queryset(self):
# Same query is used to get single items so we check if pk is passed
# incident/2/ if we use the filter below we would have to have correct time range
if 'pk' in self.kwargs:
return Incident.objects.filter(pk=self.kwargs['pk'])

today = datetime.today()
first_day_of_current_month = datetime(today.year, today.month, 1)
days_in_month = monthrange(today.year, today.month)[1]
last_day_of_current_month = datetime(today.year, today.month, days_in_month)

start = self.request.GET.get('start', first_day_of_current_month)
end = self.request.GET.get('end', last_day_of_current_month)

return Incident.objects.filter(start_time__gte=start, start_time__lte=end)

# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'incidents', IncidentViewSet)
router.register(r'incidents', IncidentViewSet, base_name='Incidents')
router.register(r'actions', ActionViewSet)
router.register(r'ExternalUser', ExternalUserViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ certifi==2019.3.9
cffi==1.12.3
chardet==3.0.4
cryptography==2.7
Django==2.2.2
Django==2.2.3
django-after-response==0.2.2
django-bootstrap4==0.0.7
django-filter==2.1.0
Expand Down
6 changes: 4 additions & 2 deletions response/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
SECRET_KEY = 'c+*z3&f$!v@am35()o57_l885=t$2vlw*w#*jusz0qiyi#h_iz'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False

ALLOWED_HOSTS = ["*"]

Expand Down Expand Up @@ -137,10 +137,12 @@
# https://www.django-rest-framework.org/

REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100,
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}

Expand Down
9 changes: 7 additions & 2 deletions slack/action_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

from slack.decorators import action_handler, ActionContext

import logging
logger = logging.getLogger(__name__)

@action_handler(HeadlinePost.CLOSE_INCIDENT_BUTTON)
def handle_close_incident(ac: ActionContext):
Expand All @@ -26,7 +28,10 @@ def handle_create_comms_channel(ac: ActionContext):
comms_channel = CommsChannel.objects.create_comms_channel(ac.incident)

# Invite the bot to the channel
invite_user_to_channel(settings.INCIDENT_BOT_ID, comms_channel.channel_id)
try:
invite_user_to_channel(settings.INCIDENT_BOT_ID, comms_channel.channel_id)
except Exception as ex:
logger.error(ex)

# Un-invite the user who owns the Slack token,
# otherwise they'll be added to every incident channel
Expand All @@ -52,7 +57,7 @@ def handle_edit_incident_button(ac: ActionContext):
Text(label="Report", name="report", value=ac.incident.report),
TextArea(label="Summary", name="summary", value=ac.incident.summary, optional=True, placeholder="Can you share any more details?"),
TextArea(label="Impact", name="impact", value=ac.incident.impact, optional=True, placeholder="Who or what might be affected?", hint="Think about affected people, systems, and processes"),
SelectFromUsers(label="Lead", name="lead", value=ac.incident.lead, optional=True),
SelectFromUsers(label="Lead", name="lead", value=ac.incident.lead.external_id if ac.incident.lead else None, optional=True),
SelectWithOptions([(i, s) for i, s in Incident.SQUADS], label="Squad", name="squad", optional=True)
# SelectWithOptions([(i, s.capitalize()) for i, s in Incident.SEVERITIES], value=ac.incident.severity, label="Severity", name="severity", optional=True)
]
Expand Down
Loading