diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..f2f7fa38
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include LICENSE
+include README.md
+recursive-include response/ui/static *
+recursive-include response/ui/templates *
diff --git a/README.md b/README.md
index 17b2cceb..b6991bc5 100644
--- a/README.md
+++ b/README.md
@@ -19,145 +19,9 @@ If you're interested in how we use this tool at Monzo, there's an overview in [t
---
-# Quick Start
+# Try it out
-The following steps explain how to create a Slack app, run Response locally, and configure everything to develop and test locally.
-
-Broadly speaking, this sets things up as below:
-
-
-
-
-## 1. Create a Slack App
-
-- Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and click `Create New App`.
-- Give it a name, e.g. 'Response', and select the relevant workspace.
-
-- In the OAuth and Permissions page, scroll down to scopes.
-
-- Add the following scopes:
- - `channels:history`
- - `channels:read`
- - `channels:write`
- - `chat:write:bot`
- - `chat:write:user`
- - `users:read`
-
-- At the top of the page, the `Install App to Workspace` button is now available. Click it!
-
-## 2. Configure Response
-
-Response is configured using environment variables in a `.env` file. Create your own:
-```
-$ cp env.example .env
-```
-and update the variables in it:
-
-### OAuth Access Token (`SLACK_TOKEN`)
-
-Response needs an OAuth access token to use the Slack API.
-
-- Copy the token that starts `xoxp-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable.
-
-**Note:** Since some of the APIs commands we use require a _user_ token, we only need the token starting with `xoxp-...`. If/when Slack allow these actions to be controlled by Bots, we can use the _bot_ token, starting `xoxb-...`.
-
-### Signing Secret (`SIGNING_SECRET`)
-
-Response uses the Slack signing secret to restrict access to public endpoints.
-
-- Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable.
-
-### Incident Channel (`INCIDENT_CHANNEL_NAME`)
-
-When an incident is declared, a 'headline' post is sent to a central channel.
-
-- The default channel is `incidents` - change `INCIDENT_CHANNEL_NAME` if you want them to be sent somewhere else (note: do not include the #).
-
-### Bot Name (`INCIDENT_BOT_NAME`)
-
-We want to invite the Bot to all Incident Channels, so need to know its ID.
-
-- The default bot name is `incident` - change the `INCIDENT_BOT_NAME` if your app uses something different.
-
-### Database encrypted field key(`ENCRYPTED_FIELD_KEY`)
-
-Used to encrypt potentially sensitive values stored in the database for workflows.
-
-- This can be any value but keep it secure and don't lose it. You will be unable to decrypt values from the database without it.
-
-## 3. Run Response
-
-From the root of the Response directory run:
-
-```
-docker-compose up
-```
-
-This starts the following containers:
-
-- response: the main Response app
-- postgres: the DB used by the app to store incident data
-- cron: a container running cron, configured to hit an endpoint in Response every minute
-- ngrok: ngrok in a container, providing a public URL pointed at Response.
-
-Ngrok establishes a new, random, URL any time it starts. You'll need this to complete the Slack app setup, so look for an entry like this and make note of the https://abc123.ngrok.io address - this is your public URL.
-
-```
-ngrok | The ngrok tunnel is active
-ngrok | https://6bb315c8.ngrok.io ---> response:8000
-```
-
-If everything has started successfully, you should see logs resembling the following:
-
-```
-response | Django version 2.1.7, using settings 'response.settings.dev'
-response | Starting development server at http://0.0.0.0:8000/
-response | Quit the server with CONTROL-C.
-```
-
-## 4. Complete the Slack App Setup
-
-### Slash Command
-
-- In the Slash commands page click `Create New Command`.
-
-- Enter the following info:
- - Command: `/incident`
- - Request URL: `https:///slack/slash_command`
- - Short Description: `Trigger an incident`
- - Usage Hint: `What's the problem?`
-
-### Event Subscriptions
-
-In the Event Subscriptions page we need to configure the following:
-
-- Toggle `Enable Events` to On
-- In the Request URL enter: `https:///slack/event`
-- You need to have the server running and available as Slack sends a challenge to this address and expects a specific response.
-
-- Under the Subscribe to Bot Events section, add the following:
- - `app_mention`
- - `pin_added`
- - `pin_removed`
- - `message.channels`
-
-### Configure interactive components
-
-- In the Interactive Components page, enable and set the URL to `https:///slack/action`.
-
-### Bot Users
-
-- In the Bot Users page, configure the Display Name and Default Username to `incident`.
-- Toggle 'Always Show My Bot as Online' to On.
-
-
-## 5. Test it's working!
-
-In Slack, start an incident with `/incident Something's happened`. You should see a post in your incidents channel!
-
-- Visit the incident doc by clicking the Doc link.
-- Create a comms channel by clicking the button.
-- In the comms channel check out the `@incident` commands. You can find the ones available by entering `@incident help`.
+Follow the instructions in [demo/README](demo/README.md) to set up an example Django app that uses Response that you can run locally!
---
diff --git a/core/apps.py b/core/apps.py
deleted file mode 100644
index 26f78a8e..00000000
--- a/core/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class CoreConfig(AppConfig):
- name = 'core'
diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py
deleted file mode 100644
index 5a9c87f2..00000000
--- a/core/migrations/0001_initial.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Generated by Django 2.1.7 on 2019-04-30 11:45
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Incident',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('report', models.CharField(max_length=200)),
- ('reporter', models.CharField(default='', max_length=50)),
- ('report_time', models.DateTimeField()),
- ('start_time', models.DateTimeField()),
- ('end_time', models.DateTimeField(blank=True, null=True)),
- ('summary', models.TextField(blank=True, help_text="What's the high level summary?")),
- ('impact', models.TextField(blank=True, help_text='What impact is this having?')),
- ('lead', models.CharField(blank=True, help_text='Who is leading?', max_length=50)),
- ('severity', models.CharField(blank=True, choices=[('1', 'critical'), ('2', 'major'), ('3', 'minor'), ('4', 'trivial')], max_length=10, null=True)),
- ],
- ),
- ]
diff --git a/core/migrations/0002_auto_20190507_2049.py b/core/migrations/0002_auto_20190507_2049.py
deleted file mode 100644
index f92439ce..00000000
--- a/core/migrations/0002_auto_20190507_2049.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Generated by Django 2.1.7 on 2019-05-07 20:49
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('core', '0001_initial'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='incident',
- name='impact',
- field=models.TextField(blank=True, help_text='What impact is this having?', null=True),
- ),
- migrations.AlterField(
- model_name='incident',
- name='lead',
- field=models.CharField(blank=True, help_text='Who is leading?', max_length=50, null=True),
- ),
- migrations.AlterField(
- model_name='incident',
- name='summary',
- field=models.TextField(blank=True, help_text="What's the high level summary?", null=True),
- ),
- ]
diff --git a/core/migrations/0003_incidentextension.py b/core/migrations/0003_incidentextension.py
deleted file mode 100644
index 6d1cfb6b..00000000
--- a/core/migrations/0003_incidentextension.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 2.2.2 on 2019-06-17 11:59
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('core', '0002_auto_20190507_2049'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='IncidentExtension',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('key', models.CharField(max_length=50)),
- ('value', models.CharField(max_length=100)),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- options={
- 'unique_together': {('incident', 'key')},
- },
- ),
- ]
diff --git a/core/migrations/0004_create_ExternalUser.py b/core/migrations/0004_create_ExternalUser.py
deleted file mode 100644
index e719f50c..00000000
--- a/core/migrations/0004_create_ExternalUser.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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')},
- },
- ),
- ]
diff --git a/core/migrations/0005_alter_use_ExternalUser.py b/core/migrations/0005_alter_use_ExternalUser.py
deleted file mode 100644
index 4f796c94..00000000
--- a/core/migrations/0005_alter_use_ExternalUser.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# 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')),
- ],
- ),
- ]
diff --git a/Dockerfile.cron b/demo/Dockerfile.cron
similarity index 100%
rename from Dockerfile.cron
rename to demo/Dockerfile.cron
diff --git a/Dockerfile.response b/demo/Dockerfile.response
similarity index 100%
rename from Dockerfile.response
rename to demo/Dockerfile.response
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 00000000..b0906dea
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,149 @@
+# Response Demo App
+
+This is an example Django project using the django-incident-response package that you can use to test drive `response` locally. You'll need access to be able to add and configure apps in a Slack workspace of your choosing - you can sign up for a free account, if necessary.
+
+All commands should be run from this directory (`demo`).
+
+---
+
+# Quick Start
+
+The following steps explain how to create a Slack app, run Response locally, and configure everything to develop and test locally.
+
+Broadly speaking, this sets things up as below:
+
+
+
+
+## 1. Create a Slack App
+
+- Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and click `Create New App`.
+- Give it a name, e.g. 'Response', and select the relevant workspace.
+
+- In the OAuth and Permissions page, scroll down to scopes.
+
+- Add the following scopes:
+ - `channels:history`
+ - `channels:read`
+ - `channels:write`
+ - `chat:write:bot`
+ - `chat:write:user`
+ - `users:read`
+
+- At the top of the page, the `Install App to Workspace` button is now available. Click it!
+
+## 2. Configure the demo app
+
+The demo app is configured using environment variables in a `.env` file. Create your own:
+```
+$ cp env.example .env
+```
+and update the variables in it:
+
+### OAuth Access Token (`SLACK_TOKEN`)
+
+Response needs an OAuth access token to use the Slack API.
+
+- Copy the token that starts `xoxp-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable.
+
+**Note:** Since some of the APIs commands we use require a _user_ token, we only need the token starting with `xoxp-...`. If/when Slack allow these actions to be controlled by Bots, we can use the _bot_ token, starting `xoxb-...`.
+
+### Signing Secret (`SIGNING_SECRET`)
+
+Response uses the Slack signing secret to restrict access to public endpoints.
+
+- Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable.
+
+### Incident Channel (`INCIDENT_CHANNEL_NAME`)
+
+When an incident is declared, a 'headline' post is sent to a central channel.
+
+- The default channel is `incidents` - change `INCIDENT_CHANNEL_NAME` if you want them to be sent somewhere else (note: do not include the #).
+
+### Bot Name (`INCIDENT_BOT_NAME`)
+
+We want to invite the Bot to all Incident Channels, so need to know its ID.
+
+- The default bot name is `incident` - change the `INCIDENT_BOT_NAME` if your app uses something different.
+
+### Database encrypted field key(`ENCRYPTED_FIELD_KEY`)
+
+Used to encrypt potentially sensitive values stored in the database for workflows.
+
+- This can be any value but keep it secure and don't lose it. You will be unable to decrypt values from the database without it.
+
+## 3. Run Response
+
+From the root of the Response directory run:
+
+```
+docker-compose up
+```
+
+This starts the following containers:
+
+- response: the main Response app
+- postgres: the DB used by the app to store incident data
+- cron: a container running cron, configured to hit an endpoint in Response every minute
+- ngrok: ngrok in a container, providing a public URL pointed at Response.
+
+Ngrok establishes a new, random, URL any time it starts. You'll need this to complete the Slack app setup, so look for an entry like this and make note of the https://abc123.ngrok.io address - this is your public URL.
+
+```
+ngrok | The ngrok tunnel is active
+ngrok | https://6bb315c8.ngrok.io ---> response:8000
+```
+
+If everything has started successfully, you should see logs resembling the following:
+
+```
+response | Django version 2.1.7, using settings 'response.settings.dev'
+response | Starting development server at http://0.0.0.0:8000/
+response | Quit the server with CONTROL-C.
+```
+
+## 4. Complete the Slack App Setup
+
+### Slash Command
+
+- In the Slash commands page click `Create New Command`.
+
+- Enter the following info:
+ - Command: `/incident`
+ - Request URL: `https:///slack/slash_command`
+ - Short Description: `Trigger an incident`
+ - Usage Hint: `What's the problem?`
+
+### Event Subscriptions
+
+In the Event Subscriptions page we need to configure the following:
+
+- Toggle `Enable Events` to On
+- In the Request URL enter: `https:///slack/event`
+- You need to have the server running and available as Slack sends a challenge to this address and expects a specific response.
+
+- Under the Subscribe to Bot Events section, add the following:
+ - `app_mention`
+ - `pin_added`
+ - `pin_removed`
+ - `message.channels`
+
+### Configure interactive components
+
+- In the Interactive Components page, enable and set the URL to `https:///slack/action`.
+
+### Bot Users
+
+- In the Bot Users page, configure the Display Name and Default Username to `incident`.
+- Toggle 'Always Show My Bot as Online' to On.
+
+
+## 5. Test it's working!
+
+In Slack, start an incident with `/incident Something's happened`. You should see a post in your incidents channel!
+
+- Visit the incident doc by clicking the Doc link.
+- Create a comms channel by clicking the button.
+- In the comms channel check out the `@incident` commands. You can find the ones available by entering `@incident help`.
+
+
diff --git a/core/__init__.py b/demo/demo/__init__.py
similarity index 100%
rename from core/__init__.py
rename to demo/demo/__init__.py
diff --git a/response/settings/base.py b/demo/demo/settings/base.py
similarity index 87%
rename from response/settings/base.py
rename to demo/demo/settings/base.py
index d5e27ab6..b55b3b38 100644
--- a/response/settings/base.py
+++ b/demo/demo/settings/base.py
@@ -1,13 +1,13 @@
"""
-Django settings for incident project.
+Django settings for demo project.
-Generated by 'django-admin startproject' using Django 2.2.
+Generated by 'django-admin startproject' using Django 2.2.3.
For more information on this file, see
-https://docs.djangoproject.com/en/dev/topics/settings/
+https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
-https://docs.djangoproject.com/en/dev/ref/settings/
+https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
@@ -17,16 +17,15 @@
logger = logging.getLogger(__name__)
-
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'c+*z3&f$!v@am35()o57_l885=t$2vlw*w#*jusz0qiyi#h_iz'
+SECRET_KEY = '5m*pc(gb!k+!913@qh2u5z7^s-bbp2ytw28gdvcbly5ayw1i5+'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@@ -43,13 +42,11 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
- 'ui.apps.UiConfig',
+ 'response.ui.apps.UiConfig',
'after_response',
'rest_framework',
'bootstrap4',
- 'core.apps.CoreConfig',
- 'slack.apps.SlackConfig',
- 'pagerduty.apps.PagerdutyConfig',
+ 'response.apps.ResponseConfig',
]
MIDDLEWARE = [
@@ -62,14 +59,12 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
-ROOT_URLCONF = 'response.urls'
+ROOT_URLCONF = 'demo.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
- 'ui/templates'
- ],
+ 'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -82,12 +77,11 @@
},
]
-
-WSGI_APPLICATION = 'response.wsgi.application'
+WSGI_APPLICATION = 'demo.wsgi.application'
# Database
-# https://docs.djangoproject.com/en/dev/ref/settings/#databases
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
@@ -98,7 +92,7 @@
# Password validation
-# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
@@ -117,20 +111,24 @@
# Internationalization
-# https://docs.djangoproject.com/en/dev/topics/i18n/
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
+
TIME_ZONE = 'UTC'
+
USE_I18N = True
+
USE_L10N = True
-USE_TZ = False
+
+USE_TZ = True
# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/dev/howto/static-files/
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
-STATIC_ROOT = 'static'
STATIC_URL = '/static/'
+STATIC_ROOT = 'static'
# Django Rest Framework
diff --git a/response/settings/dev.py b/demo/demo/settings/dev.py
similarity index 100%
rename from response/settings/dev.py
rename to demo/demo/settings/dev.py
diff --git a/response/settings/prod.py b/demo/demo/settings/prod.py
similarity index 100%
rename from response/settings/prod.py
rename to demo/demo/settings/prod.py
diff --git a/demo/demo/urls.py b/demo/demo/urls.py
new file mode 100644
index 00000000..4972637b
--- /dev/null
+++ b/demo/demo/urls.py
@@ -0,0 +1,24 @@
+"""demo URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/2.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('slack/', include('response.slack.urls')),
+ path('core/', include('response.core.urls')),
+ path('', include('response.ui.urls')),
+]
diff --git a/response/wsgi.py b/demo/demo/wsgi.py
similarity index 57%
rename from response/wsgi.py
rename to demo/demo/wsgi.py
index 41aff11c..815db6d8 100644
--- a/response/wsgi.py
+++ b/demo/demo/wsgi.py
@@ -1,17 +1,16 @@
"""
-WSGI config for incident project.
+WSGI config for demo project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
-https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'response.settings.prod')
-
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings.dev')
application = get_wsgi_application()
diff --git a/docker-compose.yaml b/demo/docker-compose.yaml
similarity index 97%
rename from docker-compose.yaml
rename to demo/docker-compose.yaml
index 777764d5..5db0755d 100644
--- a/docker-compose.yaml
+++ b/demo/docker-compose.yaml
@@ -18,6 +18,7 @@ services:
env_file: .env
volumes:
- ./:/app
+ - ../:/response
- pypd:/app/pypd
stdin_open: true
tty: true
diff --git a/demo/env.example b/demo/env.example
new file mode 100644
index 00000000..4f178e31
--- /dev/null
+++ b/demo/env.example
@@ -0,0 +1,8 @@
+SLACK_TOKEN=
+SLACK_SIGNING_SECRET=
+INCIDENT_CHANNEL_NAME=incidents
+INCIDENT_BOT_NAME=incident
+PAGERDUTY_ENABLED=True|False
+ENCRYPTED_FIELD_KEY=
+
+DJANGO_SETTINGS_MODULE=demo.settings.prod
diff --git a/demo/env.prod.example b/demo/env.prod.example
new file mode 100644
index 00000000..eb28ba42
--- /dev/null
+++ b/demo/env.prod.example
@@ -0,0 +1,17 @@
+SLACK_TOKEN=
+SLACK_SIGNING_SECRET=
+INCIDENT_CHANNEL_NAME=incidents
+INCIDENT_BOT_NAME=incident
+DB_HOST=
+DB_NAME=
+DB_USER=
+DB_PORT=
+DB_SSL_MODE=
+DB_PASSWORD=
+SITE_URL=
+DJANGO_SETTINGS_MODULE=demo.settings.prod
+ENCRYPTED_FIELD_KEY=
+PAGERDUTY_ENABLED=
+PAGERDUTY_API_KEY=
+PAGERDUTY_SERVICE=
+PAGERDUTY_DEFAULT_EMAIL=
diff --git a/manage.py b/demo/manage.py
similarity index 79%
rename from manage.py
rename to demo/manage.py
index 66869c76..88986662 100755
--- a/manage.py
+++ b/demo/manage.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'response.settings.dev')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
diff --git a/requirements.txt b/demo/requirements.txt
similarity index 100%
rename from requirements.txt
rename to demo/requirements.txt
diff --git a/startup.sh b/demo/startup.sh
old mode 100755
new mode 100644
similarity index 90%
rename from startup.sh
rename to demo/startup.sh
index a7f913fc..810e479d
--- a/startup.sh
+++ b/demo/startup.sh
@@ -1,5 +1,7 @@
#! /bin/bash
+pip install /response
+
wait_for_db()
{
while ! nc -z ${DB_HOST:-db} ${DB_PORT:-5432};
@@ -31,4 +33,4 @@ echo "[INFO] Creating Admin User"
create_admin_user
echo "[INFO] Starting Response Dev Server"
-python3 manage.py runserver 0.0.0.0:8000
\ No newline at end of file
+python3 manage.py runserver 0.0.0.0:8000
diff --git a/env.example b/env.example
deleted file mode 100644
index 6f424e4c..00000000
--- a/env.example
+++ /dev/null
@@ -1,6 +0,0 @@
-SLACK_TOKEN=""
-SLACK_SIGNING_SECRET=""
-INCIDENT_CHANNEL_NAME="incidents"
-INCIDENT_BOT_NAME="incident"
-PAGERDUTY_ENABLED="True|False"
-ENCRYPTED_FIELD_KEY=""
\ No newline at end of file
diff --git a/env.prod.example b/env.prod.example
deleted file mode 100644
index 8f33a933..00000000
--- a/env.prod.example
+++ /dev/null
@@ -1,17 +0,0 @@
-SLACK_TOKEN=""
-SLACK_SIGNING_SECRET=""
-INCIDENT_CHANNEL_NAME="incidents"
-INCIDENT_BOT_NAME="incident"
-DB_HOST=""
-DB_NAME=""
-DB_USER=""
-DB_PORT=""
-DB_SSL_MODE=""
-DB_PASSWORD=""
-SITE_URL=""
-DJANGO_SETTINGS_MODULE="response.settings.prod"
-ENCRYPTED_FIELD_KEY=""
-PAGERDUTY_ENABLED=""
-PAGERDUTY_API_KEY=""
-PAGERDUTY_SERVICE=""
-PAGERDUTY_DEFAULT_EMAIL=""
diff --git a/pagerduty/apps.py b/pagerduty/apps.py
deleted file mode 100644
index 5fae1412..00000000
--- a/pagerduty/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class PagerdutyConfig(AppConfig):
- name = 'pagerduty'
diff --git a/pagerduty/migrations/0001_initial.py b/pagerduty/migrations/0001_initial.py
deleted file mode 100644
index 70dbee93..00000000
--- a/pagerduty/migrations/0001_initial.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1.7 on 2019-05-19 16:01
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Escalation',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=100, unique=True)),
- ('summary', models.TextField(max_length=1000)),
- ('escalation_policy', models.CharField(max_length=10)),
- ],
- ),
- ]
diff --git a/response/admin.py b/response/admin.py
new file mode 100644
index 00000000..c33a1a17
--- /dev/null
+++ b/response/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from .core import admin
+from .pagerduty import admin
+from .slack import admin
diff --git a/response/apps.py b/response/apps.py
new file mode 100644
index 00000000..ea31a1e2
--- /dev/null
+++ b/response/apps.py
@@ -0,0 +1,18 @@
+from django.apps import AppConfig
+
+
+class ResponseConfig(AppConfig):
+ name = 'response'
+
+ def ready(self):
+ from .slack import (settings,
+ signals,
+ action_handlers,
+ event_handlers,
+ incident_commands,
+ keyword_handlers,
+ incident_notifications,
+ dialog_handlers,
+ workflows)
+ if settings.PAGERDUTY_ENABLED:
+ from .slack.workflows import pagerduty
diff --git a/core/migrations/__init__.py b/response/core/__init__.py
similarity index 100%
rename from core/migrations/__init__.py
rename to response/core/__init__.py
diff --git a/core/admin.py b/response/core/admin.py
similarity index 66%
rename from core/admin.py
rename to response/core/admin.py
index 018658ef..c3c72a64 100644
--- a/core/admin.py
+++ b/response/core/admin.py
@@ -1,5 +1,5 @@
from django.contrib import admin
-from core.models import Incident, Action, ExternalUser
+from response.core.models import Incident, Action, ExternalUser
admin.site.register(Action)
admin.site.register(Incident)
diff --git a/core/models/__init__.py b/response/core/models/__init__.py
similarity index 100%
rename from core/models/__init__.py
rename to response/core/models/__init__.py
diff --git a/core/models/action.py b/response/core/models/action.py
similarity index 80%
rename from core/models/action.py
rename to response/core/models/action.py
index d263221d..49543814 100644
--- a/core/models/action.py
+++ b/response/core/models/action.py
@@ -1,7 +1,7 @@
from datetime import datetime
from django.db import models
-from core.models.incident import Incident
-from core.models.user_external import ExternalUser
+from response.core.models.incident import Incident
+from response.core.models.user_external import ExternalUser
class Action(models.Model):
diff --git a/core/models/incident.py b/response/core/models/incident.py
similarity index 98%
rename from core/models/incident.py
rename to response/core/models/incident.py
index b8de1f30..1bde81c1 100644
--- a/core/models/incident.py
+++ b/response/core/models/incident.py
@@ -1,6 +1,6 @@
from datetime import datetime
from django.db import models
-from core.models.user_external import ExternalUser
+from response.core.models.user_external import ExternalUser
class IncidentManager(models.Manager):
def create_incident(self, report, reporter, report_time, summary=None, impact=None, lead=None, severity=None):
diff --git a/core/models/user_external.py b/response/core/models/user_external.py
similarity index 100%
rename from core/models/user_external.py
rename to response/core/models/user_external.py
diff --git a/core/serializers.py b/response/core/serializers.py
similarity index 90%
rename from core/serializers.py
rename to response/core/serializers.py
index 1c5f968b..859fa4ba 100644
--- a/core/serializers.py
+++ b/response/core/serializers.py
@@ -1,9 +1,9 @@
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 response.core.models.incident import Incident
+from response.core.models.action import Action
+from response.core.models.user_external import ExternalUser
from django.contrib.auth.models import User
diff --git a/core/tests.py b/response/core/tests.py
similarity index 100%
rename from core/tests.py
rename to response/core/tests.py
diff --git a/core/urls.py b/response/core/urls.py
similarity index 91%
rename from core/urls.py
rename to response/core/urls.py
index e3341486..7529e3b7 100644
--- a/core/urls.py
+++ b/response/core/urls.py
@@ -2,14 +2,14 @@
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 response.core.models.incident import Incident
+from response.core.models.action import Action
+from response.core.models.user_external import ExternalUser
from datetime import datetime
from calendar import monthrange
-from core.serializers import *
+from response.core.serializers import *
class ExternalUserViewSet(viewsets.ModelViewSet):
# ViewSets define the view behavior.
diff --git a/core/views.py b/response/core/views.py
similarity index 100%
rename from core/views.py
rename to response/core/views.py
diff --git a/response/migrations/0001_initial.py b/response/migrations/0001_initial.py
new file mode 100644
index 00000000..122bacfc
--- /dev/null
+++ b/response/migrations/0001_initial.py
@@ -0,0 +1,154 @@
+# Generated by Django 2.2.3 on 2019-07-12 11:24
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.db.models.fields
+import response.slack.models.workflow
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CommsChannel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('channel_id', models.CharField(max_length=20)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Escalation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('summary', models.TextField(max_length=1000)),
+ ('escalation_policy', models.CharField(max_length=10)),
+ ],
+ ),
+ 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')},
+ },
+ ),
+ migrations.CreateModel(
+ name='Incident',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('report', models.CharField(max_length=200)),
+ ('report_time', models.DateTimeField()),
+ ('start_time', models.DateTimeField()),
+ ('end_time', models.DateTimeField(blank=True, null=True)),
+ ('summary', models.TextField(blank=True, help_text="What's the high level summary?", null=True)),
+ ('impact', models.TextField(blank=True, help_text='What impact is this having?', null=True)),
+ ('severity', models.CharField(blank=True, choices=[('1', 'critical'), ('2', 'major'), ('3', 'minor'), ('4', 'trivial')], max_length=10, null=True)),
+ ('lead', models.ForeignKey(blank=True, help_text='Who is leading?', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='lead', to='response.ExternalUser')),
+ ('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to='response.ExternalUser')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Workflow',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('enabled', models.BooleanField(default=False)),
+ ('name', models.CharField(max_length=50)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='WorkflowParameters',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('value', response.slack.models.workflow.EncryptedField(blank=True, field_type=django.db.models.fields.CharField, max_length=500, null=True)),
+ ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters', to='response.Workflow')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PinnedMessage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('message_ts', models.CharField(max_length=50)),
+ ('text', models.TextField()),
+ ('timestamp', models.DateTimeField()),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='response.ExternalUser')),
+ ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='HeadlinePost',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('message_ts', models.CharField(max_length=20, null=True)),
+ ('comms_channel', models.OneToOneField(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='response.CommsChannel')),
+ ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='commschannel',
+ name='incident',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident'),
+ ),
+ 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='response.Incident')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.ExternalUser')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserStats',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('join_time', models.DateTimeField(null=True)),
+ ('message_count', models.IntegerField(default=0)),
+ ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.ExternalUser')),
+ ],
+ options={
+ 'unique_together': {('incident', 'user')},
+ },
+ ),
+ migrations.CreateModel(
+ name='Notification',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('key', models.CharField(max_length=30)),
+ ('time', models.DateTimeField()),
+ ('repeat_count', models.IntegerField(default=0)),
+ ('completed', models.BooleanField(default=False)),
+ ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident')),
+ ],
+ options={
+ 'unique_together': {('incident', 'key')},
+ },
+ ),
+ migrations.CreateModel(
+ name='IncidentExtension',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('key', models.CharField(max_length=50)),
+ ('value', models.CharField(max_length=100)),
+ ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='response.Incident')),
+ ],
+ options={
+ 'unique_together': {('incident', 'key')},
+ },
+ ),
+ ]
diff --git a/pagerduty/__init__.py b/response/migrations/__init__.py
similarity index 100%
rename from pagerduty/__init__.py
rename to response/migrations/__init__.py
diff --git a/response/models.py b/response/models.py
new file mode 100644
index 00000000..236c4e50
--- /dev/null
+++ b/response/models.py
@@ -0,0 +1,5 @@
+from django.db import models
+
+from .core.models import *
+from .pagerduty.models import *
+from .slack.models import *
diff --git a/pagerduty/README.md b/response/pagerduty/README.md
similarity index 100%
rename from pagerduty/README.md
rename to response/pagerduty/README.md
diff --git a/pagerduty/migrations/__init__.py b/response/pagerduty/__init__.py
similarity index 100%
rename from pagerduty/migrations/__init__.py
rename to response/pagerduty/__init__.py
diff --git a/pagerduty/admin.py b/response/pagerduty/admin.py
similarity index 66%
rename from pagerduty/admin.py
rename to response/pagerduty/admin.py
index b493b2f7..f54c85c8 100644
--- a/pagerduty/admin.py
+++ b/response/pagerduty/admin.py
@@ -1,6 +1,6 @@
from django.contrib import admin
-from pagerduty.models import Escalation
+from response.pagerduty.models import Escalation
# Register your models here.
admin.site.register(Escalation)
diff --git a/pagerduty/incident.py b/response/pagerduty/incident.py
similarity index 100%
rename from pagerduty/incident.py
rename to response/pagerduty/incident.py
diff --git a/pagerduty/models.py b/response/pagerduty/models.py
similarity index 100%
rename from pagerduty/models.py
rename to response/pagerduty/models.py
diff --git a/pagerduty/views.py b/response/pagerduty/views.py
similarity index 100%
rename from pagerduty/views.py
rename to response/pagerduty/views.py
diff --git a/response/serializers.py b/response/serializers.py
new file mode 100644
index 00000000..2fd7ce68
--- /dev/null
+++ b/response/serializers.py
@@ -0,0 +1 @@
+from .core.serializers import *
diff --git a/response/settings/__init__.py b/response/slack/__init__.py
similarity index 100%
rename from response/settings/__init__.py
rename to response/slack/__init__.py
diff --git a/slack/action_handlers.py b/response/slack/action_handlers.py
similarity index 83%
rename from slack/action_handlers.py
rename to response/slack/action_handlers.py
index c12404f9..ad3eb6dd 100644
--- a/slack/action_handlers.py
+++ b/response/slack/action_handlers.py
@@ -2,14 +2,14 @@
from datetime import datetime
from django.conf import settings
-from core.models.incident import Incident
+from response.core.models.incident import Incident
-from slack.settings import INCIDENT_EDIT_DIALOG
-from slack.dialog_builder import Dialog, Text, TextArea, SelectWithOptions, SelectFromUsers
-from slack.models import HeadlinePost, CommsChannel
-from slack.slack_utils import invite_user_to_channel, get_slack_token_owner, leave_channel
+from response.slack.settings import INCIDENT_EDIT_DIALOG
+from response.slack.dialog_builder import Dialog, Text, TextArea, SelectWithOptions, SelectFromUsers
+from response.slack.models import HeadlinePost, CommsChannel
+from response.slack.slack_utils import invite_user_to_channel, get_slack_token_owner, leave_channel
-from slack.decorators import action_handler, ActionContext
+from response.slack.decorators import action_handler, ActionContext
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/admin.py b/response/slack/admin.py
similarity index 68%
rename from slack/admin.py
rename to response/slack/admin.py
index 9a402577..8eb69bd9 100644
--- a/slack/admin.py
+++ b/response/slack/admin.py
@@ -1,5 +1,5 @@
from django.contrib import admin
-from slack.models import HeadlinePost, CommsChannel, Notification, UserStats, PinnedMessage, Workflow, WorkflowAdmin
+from response.slack.models import HeadlinePost, CommsChannel, Notification, UserStats, PinnedMessage, Workflow, WorkflowAdmin
# Register your models here.
diff --git a/slack/authentication.py b/response/slack/authentication.py
similarity index 100%
rename from slack/authentication.py
rename to response/slack/authentication.py
diff --git a/slack/block_kit.py b/response/slack/block_kit.py
similarity index 100%
rename from slack/block_kit.py
rename to response/slack/block_kit.py
diff --git a/slack/decorators/__init__.py b/response/slack/decorators/__init__.py
similarity index 100%
rename from slack/decorators/__init__.py
rename to response/slack/decorators/__init__.py
diff --git a/slack/decorators/action_handler.py b/response/slack/decorators/action_handler.py
similarity index 95%
rename from slack/decorators/action_handler.py
rename to response/slack/decorators/action_handler.py
index 4662c366..a55f42c1 100644
--- a/slack/decorators/action_handler.py
+++ b/response/slack/decorators/action_handler.py
@@ -1,7 +1,7 @@
import after_response
-from core.models.incident import Incident
-from slack.models import CommsChannel
+from response.core.models.incident import Incident
+from response.slack.models import CommsChannel
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/decorators/dialog_handler.py b/response/slack/decorators/dialog_handler.py
similarity index 100%
rename from slack/decorators/dialog_handler.py
rename to response/slack/decorators/dialog_handler.py
diff --git a/slack/decorators/event_handler.py b/response/slack/decorators/event_handler.py
similarity index 95%
rename from slack/decorators/event_handler.py
rename to response/slack/decorators/event_handler.py
index 9e24ca4c..78671ad1 100644
--- a/slack/decorators/event_handler.py
+++ b/response/slack/decorators/event_handler.py
@@ -3,8 +3,8 @@
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
-from core.models.incident import Incident
-from slack.models import CommsChannel
+from response.core.models.incident import Incident
+from response.slack.models import CommsChannel
logger = logging.getLogger(__name__)
diff --git a/slack/decorators/incident_command.py b/response/slack/decorators/incident_command.py
similarity index 93%
rename from slack/decorators/incident_command.py
rename to response/slack/decorators/incident_command.py
index 4cb9c5d7..64c0769e 100644
--- a/slack/decorators/incident_command.py
+++ b/response/slack/decorators/incident_command.py
@@ -1,8 +1,8 @@
import logging
-from core.models import Incident
-from slack.models import CommsChannel
-from slack.slack_utils import add_reaction, remove_reaction, send_ephemeral_message, send_message, SlackError
+from response.core.models import Incident
+from response.slack.models import CommsChannel
+from response.slack.slack_utils import add_reaction, remove_reaction, send_ephemeral_message, send_message, SlackError
logger = logging.getLogger(__name__)
diff --git a/slack/decorators/incident_notification.py b/response/slack/decorators/incident_notification.py
similarity index 97%
rename from slack/decorators/incident_notification.py
rename to response/slack/decorators/incident_notification.py
index 59acdf4f..5df8137d 100644
--- a/slack/decorators/incident_notification.py
+++ b/response/slack/decorators/incident_notification.py
@@ -4,8 +4,8 @@
from datetime import datetime
-from core.models import Incident
-from slack.models import CommsChannel, Notification
+from response.core.models import Incident
+from response.slack.models import CommsChannel, Notification
logger = logging.getLogger(__name__)
diff --git a/slack/decorators/keyword_handler.py b/response/slack/decorators/keyword_handler.py
similarity index 87%
rename from slack/decorators/keyword_handler.py
rename to response/slack/decorators/keyword_handler.py
index a5b015ea..b0fd39f2 100644
--- a/slack/decorators/keyword_handler.py
+++ b/response/slack/decorators/keyword_handler.py
@@ -1,5 +1,5 @@
-from core.models.incident import Incident
-from slack.models import CommsChannel
+from response.core.models.incident import Incident
+from response.slack.models import CommsChannel
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/decorators/workflow.py b/response/slack/decorators/workflow.py
similarity index 86%
rename from slack/decorators/workflow.py
rename to response/slack/decorators/workflow.py
index 23676469..db7b0c6a 100644
--- a/slack/decorators/workflow.py
+++ b/response/slack/decorators/workflow.py
@@ -2,11 +2,10 @@
logger = logging.getLogger('__init__')
from django.db.models.signals import post_save
-from django.db.utils import ProgrammingError
+from django.db.utils import ProgrammingError, OperationalError
from django.dispatch import receiver
-from slack.apps import SlackConfig
-from slack.models import Workflow, WorkflowParameters, CommsChannel, workflow_post_save
+from response.slack.models import Workflow, WorkflowParameters, CommsChannel, workflow_post_save
RESPONSE_WORKFLOWS = {}
@@ -26,7 +25,7 @@ def _wrapper(workflow_class):
else:
workflow_object.disable(db_workflow)
return db_workflow
- except ProgrammingError as ex:
+ except (ProgrammingError, OperationalError) as ex:
logger.warn(f'This will error before workflow migration has been completed (first run) \n{ex}')
return _wrapper
diff --git a/slack/dialog_builder.py b/response/slack/dialog_builder.py
similarity index 98%
rename from slack/dialog_builder.py
rename to response/slack/dialog_builder.py
index dec4597d..d736ca8c 100644
--- a/slack/dialog_builder.py
+++ b/response/slack/dialog_builder.py
@@ -1,7 +1,7 @@
from django.conf import settings
from slackclient import SlackClient
-from slack.slack_utils import SlackError
+from response.slack.slack_utils import SlackError
slack_token = settings.SLACK_TOKEN
diff --git a/slack/dialog_handlers.py b/response/slack/dialog_handlers.py
similarity index 85%
rename from slack/dialog_handlers.py
rename to response/slack/dialog_handlers.py
index 46858c51..e33b667e 100644
--- a/slack/dialog_handlers.py
+++ b/response/slack/dialog_handlers.py
@@ -3,11 +3,11 @@
from django.conf import settings
-from slack.settings import INCIDENT_EDIT_DIALOG, INCIDENT_REPORT_DIALOG
-from core.models.incident import Incident, ExternalUser
-from slack.models import HeadlinePost, CommsChannel
-from slack.decorators import dialog_handler
-from slack.slack_utils import send_ephemeral_message, channel_reference, get_user_profile, GetOrCreateSlackExternalUser
+from response.slack.settings import INCIDENT_EDIT_DIALOG, INCIDENT_REPORT_DIALOG
+from response.core.models.incident import Incident, ExternalUser
+from response.slack.models import HeadlinePost, CommsChannel
+from response.slack.decorators import dialog_handler
+from response.slack.slack_utils import send_ephemeral_message, channel_reference, get_user_profile, GetOrCreateSlackExternalUser
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/event_handlers.py b/response/slack/event_handlers.py
similarity index 91%
rename from slack/event_handlers.py
rename to response/slack/event_handlers.py
index 9f94c5b2..50e15493 100644
--- a/slack/event_handlers.py
+++ b/response/slack/event_handlers.py
@@ -1,10 +1,10 @@
import json
import re
-from core.models.incident import Incident
-from slack.models import HeadlinePost, CommsChannel, UserStats, PinnedMessage
+from response.core.models.incident import Incident
+from response.slack.models import HeadlinePost, CommsChannel, UserStats, PinnedMessage
-from slack.decorators import slack_event, handle_incident_command, handle_keywords
+from response.slack.decorators import slack_event, handle_incident_command, handle_keywords
def decode_app_mention(payload):
diff --git a/slack/incident_commands.py b/response/slack/incident_commands.py
similarity index 91%
rename from slack/incident_commands.py
rename to response/slack/incident_commands.py
index d8ef907f..e25a3ada 100644
--- a/slack/incident_commands.py
+++ b/response/slack/incident_commands.py
@@ -1,7 +1,7 @@
-from core.models import Incident, Action, ExternalUser
-from slack.models import CommsChannel
-from slack.decorators import incident_command, get_help
-from slack.slack_utils import reference_to_id, get_user_profile, rename_channel, SlackError, GetOrCreateSlackExternalUser
+from response.core.models import Incident, Action, ExternalUser
+from response.slack.models import CommsChannel
+from response.slack.decorators import incident_command, get_help
+from response.slack.slack_utils import reference_to_id, get_user_profile, rename_channel, SlackError, GetOrCreateSlackExternalUser
from datetime import datetime
@incident_command(['help'], helptext='Display a list of commands and usage')
diff --git a/slack/incident_notifications.py b/response/slack/incident_notifications.py
similarity index 88%
rename from slack/incident_notifications.py
rename to response/slack/incident_notifications.py
index 0d6ef880..77fefeb7 100644
--- a/slack/incident_notifications.py
+++ b/response/slack/incident_notifications.py
@@ -1,6 +1,6 @@
-from core.models import Incident
-from slack.models import CommsChannel
-from slack .decorators import recurring_notification, single_notification
+from response.core.models import Incident
+from response.slack.models import CommsChannel
+from response.slack .decorators import recurring_notification, single_notification
@recurring_notification(interval_mins=5, max_notifications=5)
diff --git a/slack/keyword_handlers.py b/response/slack/keyword_handlers.py
similarity index 68%
rename from slack/keyword_handlers.py
rename to response/slack/keyword_handlers.py
index 0c2b6d5f..7574911c 100644
--- a/slack/keyword_handlers.py
+++ b/response/slack/keyword_handlers.py
@@ -1,7 +1,7 @@
-from core.models.incident import Incident
+from response.core.models.incident import Incident
-from slack.models import CommsChannel
-from slack.decorators import keyword_handler
+from response.slack.models import CommsChannel
+from response.slack.decorators import keyword_handler
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/models/__init__.py b/response/slack/models/__init__.py
similarity index 100%
rename from slack/models/__init__.py
rename to response/slack/models/__init__.py
diff --git a/slack/models/comms_channel.py b/response/slack/models/comms_channel.py
similarity index 87%
rename from slack/models/comms_channel.py
rename to response/slack/models/comms_channel.py
index 1199c772..0f98c509 100644
--- a/slack/models/comms_channel.py
+++ b/response/slack/models/comms_channel.py
@@ -4,10 +4,10 @@
from django.urls import reverse
from urllib.parse import urljoin
-from core.models.incident import Incident
+from response.core.models.incident import Incident
-from slack.slack_utils import get_or_create_channel, set_channel_topic, send_message, SlackError, rename_channel
-from slack.block_kit import *
+from response.slack.slack_utils import get_or_create_channel, set_channel_topic, send_message, SlackError, rename_channel
+from response.slack.block_kit import *
import logging
logger = logging.getLogger(__name__)
diff --git a/slack/models/headline_post.py b/response/slack/models/headline_post.py
similarity index 94%
rename from slack/models/headline_post.py
rename to response/slack/models/headline_post.py
index d24278a9..5d5f8186 100644
--- a/slack/models/headline_post.py
+++ b/response/slack/models/headline_post.py
@@ -3,11 +3,11 @@
from django.urls import reverse
from urllib.parse import urljoin
-from core.models.incident import Incident
-from slack.models.comms_channel import CommsChannel
+from response.core.models.incident import Incident
+from response.slack.models.comms_channel import CommsChannel
-from slack.block_kit import *
-from slack.slack_utils import user_reference, channel_reference
+from response.slack.block_kit import *
+from response.slack.slack_utils import user_reference, channel_reference
class HeadlinePostManager(models.Manager):
diff --git a/slack/models/notification.py b/response/slack/models/notification.py
similarity index 92%
rename from slack/models/notification.py
rename to response/slack/models/notification.py
index c5538864..fbe054f2 100644
--- a/slack/models/notification.py
+++ b/response/slack/models/notification.py
@@ -1,6 +1,6 @@
from django.db import models
-from core.models import Incident
+from response.core.models import Incident
class Notification(models.Model):
diff --git a/slack/models/pinned_message.py b/response/slack/models/pinned_message.py
similarity index 89%
rename from slack/models/pinned_message.py
rename to response/slack/models/pinned_message.py
index 56fbad58..64adfa82 100644
--- a/slack/models/pinned_message.py
+++ b/response/slack/models/pinned_message.py
@@ -1,8 +1,8 @@
from datetime import datetime
from django.db import models
-from slack.slack_utils import get_user_profile, GetOrCreateSlackExternalUser
-from core.models import Incident, ExternalUser
+from response.slack.slack_utils import get_user_profile, GetOrCreateSlackExternalUser
+from response.core.models import Incident, ExternalUser
class PinnedMessageManager(models.Manager):
diff --git a/slack/models/user_stats.py b/response/slack/models/user_stats.py
similarity index 87%
rename from slack/models/user_stats.py
rename to response/slack/models/user_stats.py
index 385ab547..b4b0aa81 100644
--- a/slack/models/user_stats.py
+++ b/response/slack/models/user_stats.py
@@ -1,8 +1,8 @@
from datetime import datetime
from django.db import models
-from core.models import Incident, ExternalUser
-from slack.slack_utils import get_user_profile, GetOrCreateSlackExternalUser
+from response.core.models import Incident, ExternalUser
+from response.slack.slack_utils import get_user_profile, GetOrCreateSlackExternalUser
class UserStats(models.Model):
user = models.ForeignKey(ExternalUser, on_delete=models.CASCADE, blank=False, null=False)
diff --git a/slack/models/workflow.py b/response/slack/models/workflow.py
similarity index 100%
rename from slack/models/workflow.py
rename to response/slack/models/workflow.py
diff --git a/response/slack/settings.py b/response/slack/settings.py
new file mode 100644
index 00000000..af5443c4
--- /dev/null
+++ b/response/slack/settings.py
@@ -0,0 +1,6 @@
+from django.conf import settings
+
+INCIDENT_REPORT_DIALOG = "incident-report-dialog"
+INCIDENT_EDIT_DIALOG = "incident-edit-dialog"
+
+PAGERDUTY_ENABLED = getattr(settings, 'PAGERDUTY_ENABLED', False)
diff --git a/slack/signals.py b/response/slack/signals.py
similarity index 91%
rename from slack/signals.py
rename to response/slack/signals.py
index 7cd854de..403df2bc 100644
--- a/slack/signals.py
+++ b/response/slack/signals.py
@@ -2,8 +2,8 @@
from django.core.signals import request_finished
from django.dispatch import receiver
-from core.models import Incident
-from slack.models import HeadlinePost
+from response.core.models import Incident
+from response.slack.models import HeadlinePost
from time import sleep
diff --git a/slack/slack_utils.py b/response/slack/slack_utils.py
similarity index 99%
rename from slack/slack_utils.py
rename to response/slack/slack_utils.py
index aeed1eab..471d313e 100644
--- a/slack/slack_utils.py
+++ b/response/slack/slack_utils.py
@@ -5,7 +5,7 @@
from slugify import slugify
from slackclient import SlackClient
from functools import partial
-from core.models.incident import ExternalUser
+from response.core.models.incident import ExternalUser
slack_token = settings.SLACK_TOKEN
slack_client = SlackClient(slack_token)
diff --git a/slack/tests.py b/response/slack/tests.py
similarity index 100%
rename from slack/tests.py
rename to response/slack/tests.py
diff --git a/slack/urls.py b/response/slack/urls.py
similarity index 89%
rename from slack/urls.py
rename to response/slack/urls.py
index b3c3c5bb..fbdbd890 100644
--- a/slack/urls.py
+++ b/response/slack/urls.py
@@ -1,7 +1,7 @@
from django.conf.urls import url, include
from django.urls import path
-import slack.views as views
+import response.slack.views as views
urlpatterns = [
path('slash_command', views.slash_command, name='slash_command'),
diff --git a/slack/views.py b/response/slack/views.py
similarity index 88%
rename from slack/views.py
rename to response/slack/views.py
index ed4f83d2..cafa0397 100644
--- a/slack/views.py
+++ b/response/slack/views.py
@@ -8,12 +8,12 @@
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
-from core.models.incident import Incident
-from slack.decorators import handle_action, handle_event, handle_notifications, handle_dialog
-from slack.authentication import slack_authenticate
-from slack.dialog_builder import Dialog, Text, TextArea, SelectWithOptions, SelectFromUsers
-from slack.settings import INCIDENT_REPORT_DIALOG
-from slack.slack_utils import channel_reference
+from response.core.models.incident import Incident
+from response.slack.decorators import handle_action, handle_event, handle_notifications, handle_dialog
+from response.slack.authentication import slack_authenticate
+from response.slack.dialog_builder import Dialog, Text, TextArea, SelectWithOptions, SelectFromUsers
+from response.slack.settings import INCIDENT_REPORT_DIALOG
+from response.slack.slack_utils import channel_reference
logger = logging.getLogger(__name__)
diff --git a/slack/workflows/__init__.py b/response/slack/workflows/__init__.py
similarity index 100%
rename from slack/workflows/__init__.py
rename to response/slack/workflows/__init__.py
diff --git a/slack/workflows/pagerduty.py b/response/slack/workflows/pagerduty.py
similarity index 86%
rename from slack/workflows/pagerduty.py
rename to response/slack/workflows/pagerduty.py
index c13d239a..0ae96d05 100644
--- a/slack/workflows/pagerduty.py
+++ b/response/slack/workflows/pagerduty.py
@@ -1,19 +1,19 @@
import json
import after_response
-from core.models import Incident
+from response.core.models import Incident
-from slack.block_kit import Message, Section, Divider, Text, Button
-from slack.dialog_builder import Dialog, Text as DialogText
-from slack.action_handlers import action_handler, ActionContext
-from slack.dialog_handlers import dialog_handler
-from slack.incident_commands import incident_command
-from slack.slack_utils import user_reference
-from slack.models import CommsChannel, HeadlinePost
+from response.slack.block_kit import Message, Section, Divider, Text, Button
+from response.slack.dialog_builder import Dialog, Text as DialogText
+from response.slack.action_handlers import action_handler, ActionContext
+from response.slack.dialog_handlers import dialog_handler
+from response.slack.incident_commands import incident_command
+from response.slack.slack_utils import user_reference
+from response.slack.models import CommsChannel, HeadlinePost
-from pagerduty.models import Escalation
-from pagerduty.incident import trigger_incident
+from response.pagerduty.models import Escalation
+from response.pagerduty.incident import trigger_incident
import pypd
from django.conf import settings
diff --git a/slack/workflows/statuspage/__init__.py b/response/slack/workflows/statuspage/__init__.py
similarity index 100%
rename from slack/workflows/statuspage/__init__.py
rename to response/slack/workflows/statuspage/__init__.py
diff --git a/slack/workflows/statuspage/action_handler.py b/response/slack/workflows/statuspage/action_handler.py
similarity index 89%
rename from slack/workflows/statuspage/action_handler.py
rename to response/slack/workflows/statuspage/action_handler.py
index 2ec63419..974b1e81 100644
--- a/slack/workflows/statuspage/action_handler.py
+++ b/response/slack/workflows/statuspage/action_handler.py
@@ -1,11 +1,11 @@
import requests
-import slack.dialog_builder as dialog_bld
+import response.slack.dialog_builder as dialog_bld
-from slack.workflows.statuspage.connections import get_status_page_conn
+from response.slack.workflows.statuspage.connections import get_status_page_conn
-from slack.decorators import ActionContext
-from core.models import IncidentExtension
-from slack.block_kit import Text
+from response.slack.decorators import ActionContext
+from response.core.models import IncidentExtension
+from response.slack.block_kit import Text
OPEN_STATUS_PAGE_DIALOG = 'open-status-page-dialog'
STATUS_PAGE_UPDATE = 'status-page-update'
diff --git a/slack/workflows/statuspage/connections.py b/response/slack/workflows/statuspage/connections.py
similarity index 100%
rename from slack/workflows/statuspage/connections.py
rename to response/slack/workflows/statuspage/connections.py
diff --git a/slack/workflows/statuspage/constants.py b/response/slack/workflows/statuspage/constants.py
similarity index 100%
rename from slack/workflows/statuspage/constants.py
rename to response/slack/workflows/statuspage/constants.py
diff --git a/slack/workflows/statuspage/dialog_handler.py b/response/slack/workflows/statuspage/dialog_handler.py
similarity index 82%
rename from slack/workflows/statuspage/dialog_handler.py
rename to response/slack/workflows/statuspage/dialog_handler.py
index d0d9afa4..0f853800 100644
--- a/slack/workflows/statuspage/dialog_handler.py
+++ b/response/slack/workflows/statuspage/dialog_handler.py
@@ -1,10 +1,10 @@
import json
-from slack.workflows.statuspage.connections import get_status_page_conn
+from response.slack.workflows.statuspage.connections import get_status_page_conn
-from slack.models import Incident
-from core.models import IncidentExtension
-from slack.slack_utils import send_ephemeral_message
+from response.slack.models import Incident
+from response.core.models import IncidentExtension
+from response.slack.slack_utils import send_ephemeral_message
def handle_status_page_update(user_id: str, channel_id: str, submission: json, response_url: str, state: json):
diff --git a/slack/workflows/statuspage/incident_command.py b/response/slack/workflows/statuspage/incident_command.py
similarity index 67%
rename from slack/workflows/statuspage/incident_command.py
rename to response/slack/workflows/statuspage/incident_command.py
index f29d2f0c..386f29bc 100644
--- a/slack/workflows/statuspage/incident_command.py
+++ b/response/slack/workflows/statuspage/incident_command.py
@@ -1,8 +1,8 @@
-from slack.workflows.statuspage.constants import *
+from response.slack.workflows.statuspage.constants import *
-from core.models import Incident
-from slack.block_kit import Message, Button, Section, Actions, Text
-from slack.models import CommsChannel
+from response.core.models import Incident
+from response.slack.block_kit import Message, Button, Section, Actions, Text
+from response.slack.models import CommsChannel
def handle_statuspage(incident: Incident, user_id: str, message: str):
diff --git a/slack/workflows/statuspage/workflow.py b/response/slack/workflows/statuspage/workflow.py
similarity index 64%
rename from slack/workflows/statuspage/workflow.py
rename to response/slack/workflows/statuspage/workflow.py
index 6e9e0783..5132d4c9 100644
--- a/slack/workflows/statuspage/workflow.py
+++ b/response/slack/workflows/statuspage/workflow.py
@@ -3,18 +3,18 @@
import logging
logger = logging.getLogger(name="statuspage init")
-from slack.workflows.statuspage.action_handler import *
-from slack.workflows.statuspage.incident_command import *
-from slack.workflows.statuspage.dialog_handler import *
-from slack.workflows.statuspage.constants import *
-
-from slack.workflows.statuspage.connections import set_status_page_conn
-
-from slack.models import Workflow
-from slack.decorators import incident_command, remove_incident_command
-from slack.decorators import action_handler, remove_action_handler, ActionContext
-from slack.decorators import dialog_handler, remove_dialog_handler
-from slack.decorators.workflow import register_workflow, WorkflowClass
+from response.slack.workflows.statuspage.action_handler import *
+from response.slack.workflows.statuspage.incident_command import *
+from response.slack.workflows.statuspage.dialog_handler import *
+from response.slack.workflows.statuspage.constants import *
+
+from response.slack.workflows.statuspage.connections import set_status_page_conn
+
+from response.slack.models import Workflow
+from response.slack.decorators import incident_command, remove_incident_command
+from response.slack.decorators import action_handler, remove_action_handler, ActionContext
+from response.slack.decorators import dialog_handler, remove_dialog_handler
+from response.slack.decorators.workflow import register_workflow, WorkflowClass
@register_workflow('statuspage')
diff --git a/ui/tests.py b/response/tests.py
similarity index 100%
rename from ui/tests.py
rename to response/tests.py
diff --git a/slack/__init__.py b/response/ui/__init__.py
similarity index 100%
rename from slack/__init__.py
rename to response/ui/__init__.py
diff --git a/ui/admin.py b/response/ui/admin.py
similarity index 100%
rename from ui/admin.py
rename to response/ui/admin.py
diff --git a/ui/apps.py b/response/ui/apps.py
similarity index 71%
rename from ui/apps.py
rename to response/ui/apps.py
index c218b05b..234f5749 100644
--- a/ui/apps.py
+++ b/response/ui/apps.py
@@ -2,4 +2,4 @@
class UiConfig(AppConfig):
- name = 'ui'
+ name = 'response.ui'
diff --git a/slack/migrations/__init__.py b/response/ui/migrations/__init__.py
similarity index 100%
rename from slack/migrations/__init__.py
rename to response/ui/migrations/__init__.py
diff --git a/ui/models.py b/response/ui/models.py
similarity index 100%
rename from ui/models.py
rename to response/ui/models.py
diff --git a/ui/static/bootstrap/css/bootstrap.css b/response/ui/static/bootstrap/css/bootstrap.css
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/css/bootstrap.css
rename to response/ui/static/bootstrap/css/bootstrap.css
diff --git a/ui/static/bootstrap/css/bootstrap.css.map b/response/ui/static/bootstrap/css/bootstrap.css.map
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/css/bootstrap.css.map
rename to response/ui/static/bootstrap/css/bootstrap.css.map
diff --git a/ui/static/bootstrap/js/bootstrap.bundle.js b/response/ui/static/bootstrap/js/bootstrap.bundle.js
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.bundle.js
rename to response/ui/static/bootstrap/js/bootstrap.bundle.js
diff --git a/ui/static/bootstrap/js/bootstrap.bundle.js.map b/response/ui/static/bootstrap/js/bootstrap.bundle.js.map
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.bundle.js.map
rename to response/ui/static/bootstrap/js/bootstrap.bundle.js.map
diff --git a/ui/static/bootstrap/js/bootstrap.bundle.min.js b/response/ui/static/bootstrap/js/bootstrap.bundle.min.js
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.bundle.min.js
rename to response/ui/static/bootstrap/js/bootstrap.bundle.min.js
diff --git a/ui/static/bootstrap/js/bootstrap.bundle.min.js.map b/response/ui/static/bootstrap/js/bootstrap.bundle.min.js.map
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.bundle.min.js.map
rename to response/ui/static/bootstrap/js/bootstrap.bundle.min.js.map
diff --git a/ui/static/bootstrap/js/bootstrap.js b/response/ui/static/bootstrap/js/bootstrap.js
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.js
rename to response/ui/static/bootstrap/js/bootstrap.js
diff --git a/ui/static/bootstrap/js/bootstrap.js.map b/response/ui/static/bootstrap/js/bootstrap.js.map
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.js.map
rename to response/ui/static/bootstrap/js/bootstrap.js.map
diff --git a/ui/static/bootstrap/js/bootstrap.min.js b/response/ui/static/bootstrap/js/bootstrap.min.js
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.min.js
rename to response/ui/static/bootstrap/js/bootstrap.min.js
diff --git a/ui/static/bootstrap/js/bootstrap.min.js.map b/response/ui/static/bootstrap/js/bootstrap.min.js.map
old mode 100755
new mode 100644
similarity index 100%
rename from ui/static/bootstrap/js/bootstrap.min.js.map
rename to response/ui/static/bootstrap/js/bootstrap.min.js.map
diff --git a/ui/static/common.css b/response/ui/static/common.css
similarity index 100%
rename from ui/static/common.css
rename to response/ui/static/common.css
diff --git a/ui/static/images/favicon.png b/response/ui/static/images/favicon.png
similarity index 100%
rename from ui/static/images/favicon.png
rename to response/ui/static/images/favicon.png
diff --git a/ui/static/images/response.png b/response/ui/static/images/response.png
similarity index 100%
rename from ui/static/images/response.png
rename to response/ui/static/images/response.png
diff --git a/ui/static/incident_doc.css b/response/ui/static/incident_doc.css
similarity index 100%
rename from ui/static/incident_doc.css
rename to response/ui/static/incident_doc.css
diff --git a/ui/static/manifest.json b/response/ui/static/manifest.json
similarity index 100%
rename from ui/static/manifest.json
rename to response/ui/static/manifest.json
diff --git a/ui/templates/base.html b/response/ui/templates/base.html
old mode 100755
new mode 100644
similarity index 100%
rename from ui/templates/base.html
rename to response/ui/templates/base.html
diff --git a/ui/templates/incident_doc.html b/response/ui/templates/incident_doc.html
similarity index 100%
rename from ui/templates/incident_doc.html
rename to response/ui/templates/incident_doc.html
diff --git a/ui/__init__.py b/response/ui/templatetags/__init__.py
similarity index 100%
rename from ui/__init__.py
rename to response/ui/templatetags/__init__.py
diff --git a/ui/templatetags/markdown_filter.py b/response/ui/templatetags/markdown_filter.py
similarity index 100%
rename from ui/templatetags/markdown_filter.py
rename to response/ui/templatetags/markdown_filter.py
diff --git a/ui/templatetags/unslackify.py b/response/ui/templatetags/unslackify.py
similarity index 87%
rename from ui/templatetags/unslackify.py
rename to response/ui/templatetags/unslackify.py
index 8fb26d96..e18099fc 100644
--- a/ui/templatetags/unslackify.py
+++ b/response/ui/templatetags/unslackify.py
@@ -4,7 +4,7 @@
import emoji_data_python
from django import template
-from slack.slack_utils import slack_to_human_readable, get_user_profile
+from response.slack.slack_utils import slack_to_human_readable, get_user_profile
register = template.Library()
diff --git a/response/ui/tests.py b/response/ui/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/response/ui/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/ui/urls.py b/response/ui/urls.py
similarity index 87%
rename from ui/urls.py
rename to response/ui/urls.py
index 9fde7950..76dd0d7b 100644
--- a/ui/urls.py
+++ b/response/ui/urls.py
@@ -1,7 +1,7 @@
from django.conf.urls import url, include
from django.urls import path
-import ui.views as views
+from . import views
urlpatterns = [
path('incident//', views.incident_doc, name='incident_doc'),
diff --git a/ui/views.py b/response/ui/views.py
similarity index 86%
rename from ui/views.py
rename to response/ui/views.py
index aadad6d0..d694b3a3 100644
--- a/ui/views.py
+++ b/response/ui/views.py
@@ -1,8 +1,8 @@
from django.shortcuts import render
from django.http import HttpRequest, HttpResponse, Http404
-from core.models import Incident
-from slack.models import PinnedMessage, UserStats
+from response.core.models import Incident
+from response.slack.models import PinnedMessage, UserStats
def incident_doc(request: HttpRequest, incident_id: str):
diff --git a/response/urls.py b/response/urls.py
index ebdcfe26..d9e7e9b2 100644
--- a/response/urls.py
+++ b/response/urls.py
@@ -1,24 +1,3 @@
-"""incident URL Configuration
-The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/dev/topics/http/urls/
-Examples:
-Function views
- 1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: path('', views.home, name='home')
-Class-based views
- 1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
-Including another URLconf
- 1. Import the include() function: from django.urls import include, path
- 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
-"""
-from django.contrib import admin
-from django.urls import path, include
-
-urlpatterns = [
- path('admin/', admin.site.urls),
- path('slack/', include('slack.urls')),
- path('core/', include('core.urls')),
- path('', include('ui.urls')),
-]
+from .core.urls import *
+from .slack.urls import *
diff --git a/response/views.py b/response/views.py
new file mode 100644
index 00000000..e2970cfc
--- /dev/null
+++ b/response/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+from .slack.views import *
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..d890b034
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,35 @@
+import os
+from setuptools import find_packages, setup
+
+
+
+# allow setup.py to be run from any path
+os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
+
+
+setup(
+ name='django-incident-response',
+ version='0.0.1-prerelease',
+ packages=find_packages(exclude="demo"),
+ package_dir={"response": "response"},
+ include_package_data=True,
+ license='MIT License', # example license
+ description='A real-time incident response and reporting tool',
+ url='https://github.com/monzo/response',
+ author='Chris Evans',
+ classifiers=[
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Framework :: Django :: 2.2', # replace "X.Y" as appropriate
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License', # example license
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ ],
+)
+
diff --git a/slack/apps.py b/slack/apps.py
deleted file mode 100644
index 7ad96f14..00000000
--- a/slack/apps.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.apps import AppConfig
-from django.conf import settings
-
-
-class SlackConfig(AppConfig):
- name = 'slack'
-
- def ready(self):
- import slack.settings
- import slack.signals
- import slack.action_handlers
- import slack.event_handlers
- import slack.incident_commands
- import slack.keyword_handlers
- import slack.incident_notifications
- import slack.dialog_handlers
- import slack.workflows
- if settings.PAGERDUTY_ENABLED:
- import slack.workflows.pagerduty
diff --git a/slack/migrations/0001_initial.py b/slack/migrations/0001_initial.py
deleted file mode 100644
index 1f181420..00000000
--- a/slack/migrations/0001_initial.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Generated by Django 2.1.7 on 2019-04-30 11:45
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('core', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='CommsChannel',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('channel_id', models.CharField(max_length=20)),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- ),
- migrations.CreateModel(
- name='HeadlinePost',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('message_ts', models.CharField(max_length=20, null=True)),
- ('comms_channel', models.OneToOneField(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='slack.CommsChannel')),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- ),
- migrations.CreateModel(
- name='Notification',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('key', models.CharField(max_length=30)),
- ('time', models.DateTimeField()),
- ('repeat_count', models.IntegerField(default=0)),
- ('completed', models.BooleanField(default=False)),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- ),
- migrations.CreateModel(
- name='PinnedMessage',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('author_id', models.CharField(max_length=50)),
- ('message_ts', models.CharField(max_length=50)),
- ('text', models.TextField()),
- ('timestamp', models.DateTimeField()),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- ),
- migrations.CreateModel(
- name='UserStats',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('user_id', models.CharField(max_length=50)),
- ('join_time', models.DateTimeField(null=True)),
- ('message_count', models.IntegerField(default=0)),
- ('incident', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Incident')),
- ],
- ),
- migrations.AlterUniqueTogether(
- name='userstats',
- unique_together={('incident', 'user_id')},
- ),
- migrations.AlterUniqueTogether(
- name='notification',
- unique_together={('incident', 'key')},
- ),
- ]
diff --git a/slack/migrations/0002_workflow_workflowparameters.py b/slack/migrations/0002_workflow_workflowparameters.py
deleted file mode 100644
index 779e4d24..00000000
--- a/slack/migrations/0002_workflow_workflowparameters.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Generated by Django 2.2.2 on 2019-06-17 10:10
-
-from django.db import migrations, models
-import django.db.models.deletion
-import django.db.models.fields
-import slack.models.workflow
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('slack', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Workflow',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('enabled', models.BooleanField(default=False)),
- ('name', models.CharField(max_length=50)),
- ],
- ),
- migrations.CreateModel(
- name='WorkflowParameters',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=50)),
- ('value', slack.models.workflow.EncryptedField(blank=True, field_type=django.db.models.fields.CharField, max_length=500, null=True)),
- ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters', to='slack.Workflow')),
- ],
- ),
- ]
diff --git a/slack/migrations/0003_auto_20190624_1422.py b/slack/migrations/0003_auto_20190624_1422.py
deleted file mode 100644
index 923203ba..00000000
--- a/slack/migrations/0003_auto_20190624_1422.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Generated by Django 2.2.2 on 2019-06-24 14:22
-
-import django.db.models.deletion
-from django.db import migrations, models
-from slack.slack_utils import get_user_profile
-from django.db.models import F
-
-
-def PopulateExternalUser(apps, schema_editor):
- Incident = apps.get_model('core', 'Incident')
- ExternalUser = apps.get_model('core', 'ExternalUser')
-
- UserStats = apps.get_model('slack', 'UserStats')
- PinnedMessage = apps.get_model('slack', 'PinnedMessage')
-
- user_id_list = [ x['userid'] for x in Incident.objects.all().values(userid=F('reporter'))
- .union(Incident.objects.all().values(userid=F('lead')))
- .union(UserStats.objects.all().values(userid=F('user_id')))
- .union(PinnedMessage.objects.all().values(userid=F('author_id'))) if x['userid'] ]
-
- for user_id in user_id_list:
- ExternalID, created = ExternalUser.objects.get_or_create(app_id='slack',
- external_id=user_id,
- display_name=get_user_profile(user_id)['name'])
- ExternalID.save()
-
-
-def move_userstats_user_id_forward(apps, schema_editor):
- ExternalUser = apps.get_model('core', 'ExternalUser')
- UserStats = apps.get_model('slack', 'UserStats')
-
- for userStat in UserStats.objects.all():
- userStat.user = ExternalUser.objects.get(external_id=userStat.user_id_tmp)
- userStat.save()
-
-
-def move_pinnedmessage_user_id_forward(apps, schema_editor):
- ExternalUser = apps.get_model('core', 'ExternalUser')
- PinnedMessage = apps.get_model('slack', 'PinnedMessage')
-
- for pm in PinnedMessage.objects.all():
- pm.author = ExternalUser.objects.get(external_id=pm.author_id_tmp)
- pm.save()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('core', '0004_create_ExternalUser'),
- ('slack', '0002_workflow_workflowparameters'),
- ]
-
- operations = [
- migrations.RunPython(PopulateExternalUser),
- migrations.RenameField(
- model_name='userstats',
- old_name='user_id',
- new_name='user_id_tmp',
- ),
- migrations.AddField(
- model_name='userstats',
- name='user',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.ExternalUser'),
- preserve_default=False,
- ),
- migrations.RunPython(move_userstats_user_id_forward),
- migrations.AlterField(
- model_name='userstats',
- name='user',
- field=models.ForeignKey(null=False, on_delete=django.db.models.deletion.CASCADE, to='core.ExternalUser'),
- ),
- migrations.AlterUniqueTogether(
- name='userstats',
- unique_together={('incident', 'user')},
- ),
- migrations.RemoveField(
- model_name='userstats',
- name='user_id_tmp',
- ),
- migrations.RenameField(
- model_name='pinnedmessage',
- old_name='author_id',
- new_name='author_id_tmp',
- ),
- migrations.AddField(
- model_name='pinnedmessage',
- name='author',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='core.ExternalUser'),
- preserve_default=False,
- ),
- migrations.RunPython(move_pinnedmessage_user_id_forward),
- migrations.AlterField(
- model_name='pinnedmessage',
- name='author',
- field=models.ForeignKey(null=False, on_delete=django.db.models.deletion.PROTECT, to='core.ExternalUser'),
- ),
- migrations.RemoveField(
- model_name='pinnedmessage',
- name='author_id_tmp',
- ),
- ]
diff --git a/slack/settings.py b/slack/settings.py
deleted file mode 100644
index 7e62653b..00000000
--- a/slack/settings.py
+++ /dev/null
@@ -1,2 +0,0 @@
-INCIDENT_REPORT_DIALOG = "incident-report-dialog"
-INCIDENT_EDIT_DIALOG = "incident-edit-dialog"
diff --git a/ui/migrations/__init__.py b/ui/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000