mirror of
https://github.com/bugsink/bugsink.git
synced 2025-12-19 19:49:44 -06:00
Message service backend setup: switch config form per-service in the UI
See #281, which this commit prepares for
This commit is contained in:
@@ -3,7 +3,7 @@ from django.forms import ModelForm
|
||||
from .models import MessagingServiceConfig
|
||||
|
||||
|
||||
class MessagingServiceConfigForm(ModelForm):
|
||||
class MessagingServiceConfigNewForm(ModelForm):
|
||||
|
||||
def __init__(self, project, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -19,3 +19,13 @@ class MessagingServiceConfigForm(ModelForm):
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class MessagingServiceConfigEditForm(ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = MessagingServiceConfig
|
||||
fields = ["display_name"]
|
||||
|
||||
@@ -14,7 +14,7 @@ class Migration(migrations.Migration):
|
||||
model_name="messagingserviceconfig",
|
||||
name="kind",
|
||||
field=models.CharField(
|
||||
choices=alerts.models.kind_choices, default="slack", max_length=20
|
||||
choices=alerts.models.get_alert_service_kind_choices, default="slack", max_length=20
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@ from .service_backends.mattermost import MattermostBackend
|
||||
from .service_backends.discord import DiscordBackend
|
||||
|
||||
|
||||
def kind_choices():
|
||||
def get_alert_service_kind_choices():
|
||||
# As a callable to avoid non-DB-affecting migrations for adding new kinds.
|
||||
# Messaging backends don't need translations since they are brand names.
|
||||
return [
|
||||
@@ -16,12 +16,22 @@ def kind_choices():
|
||||
]
|
||||
|
||||
|
||||
def get_alert_service_backend_class(kind):
|
||||
if kind == "discord":
|
||||
return DiscordBackend
|
||||
if kind == "mattermost":
|
||||
return MattermostBackend
|
||||
if kind == "slack":
|
||||
return SlackBackend
|
||||
raise ValueError(f"Unknown backend kind: {kind}")
|
||||
|
||||
|
||||
class MessagingServiceConfig(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.DO_NOTHING, related_name="service_configs")
|
||||
display_name = models.CharField(max_length=100, blank=False,
|
||||
help_text='For display in the UI, e.g. "#general on company Slack"')
|
||||
|
||||
kind = models.CharField(choices=kind_choices, max_length=20, default="slack")
|
||||
kind = models.CharField(choices=get_alert_service_kind_choices, max_length=20, default="slack")
|
||||
|
||||
config = models.TextField(blank=False)
|
||||
|
||||
@@ -40,13 +50,7 @@ class MessagingServiceConfig(models.Model):
|
||||
help_text="Error message from the exception")
|
||||
|
||||
def get_backend(self):
|
||||
if self.kind == "discord":
|
||||
return DiscordBackend(self)
|
||||
if self.kind == "mattermost":
|
||||
return MattermostBackend(self)
|
||||
if self.kind == "slack":
|
||||
return SlackBackend(self)
|
||||
raise ValueError(f"Unknown backend kind: {self.kind}")
|
||||
return get_alert_service_backend_class(self.kind)(self)
|
||||
|
||||
def clear_failure_status(self):
|
||||
"""Clear all failure tracking fields on successful operation"""
|
||||
|
||||
@@ -203,7 +203,8 @@ class DiscordBackend:
|
||||
def __init__(self, service_config):
|
||||
self.service_config = service_config
|
||||
|
||||
def get_form_class(self):
|
||||
@classmethod
|
||||
def get_form_class(cls):
|
||||
return DiscordConfigForm
|
||||
|
||||
def send_test_message(self):
|
||||
|
||||
@@ -185,7 +185,8 @@ class MattermostBackend:
|
||||
def __init__(self, service_config):
|
||||
self.service_config = service_config
|
||||
|
||||
def get_form_class(self):
|
||||
@classmethod
|
||||
def get_form_class(cls):
|
||||
return MattermostConfigForm
|
||||
|
||||
def send_test_message(self):
|
||||
|
||||
@@ -222,7 +222,8 @@ class SlackBackend:
|
||||
def __init__(self, service_config):
|
||||
self.service_config = service_config
|
||||
|
||||
def get_form_class(self):
|
||||
@classmethod
|
||||
def get_form_class(cls):
|
||||
return SlackConfigForm
|
||||
|
||||
def send_test_message(self):
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load tailwind_forms %}
|
||||
|
||||
{% block title %}Messaging Service · {{ project.name }} · {{ site_title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
|
||||
<div class="m-4 max-w-4xl flex-auto">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if messages %}
|
||||
<ul>
|
||||
{% for message in messages %}
|
||||
{# if we introduce different levels we can use{% message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} #}
|
||||
<li class="bg-cyan-50 dark:bg-cyan-900 border-2 border-cyan-800 dark:border-cyan-400 p-4 rounded-lg">{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<h1 class="text-4xl my-4 font-bold">Messaging Service | {{ project.name }}</h1>
|
||||
</div>
|
||||
|
||||
{% for field in form %}
|
||||
{% tailwind_formfield field %}
|
||||
{% endfor %}
|
||||
|
||||
{% for config_form_kind, config_form in config_forms.items %}
|
||||
<div
|
||||
class="config-form-block {% if config_form_kind == selected_config_form_kind %}block{% else %}hidden{% endif %}" data-config-kind="{{ config_form_kind }}">
|
||||
|
||||
{% for field in config_form %}
|
||||
{% tailwind_formfield field %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<button name="action" value="add" class="font-bold text-slate-800 dark:text-slate-100 border-slate-500 dark:border-slate-400 pl-4 pr-4 pb-2 pt-2 border-2 bg-cyan-200 dark:bg-cyan-700 hover:bg-cyan-400 dark:hover:bg-cyan-600 active:ring-3 rounded-md">Save</button>
|
||||
<a href="{% url "project_alerts_setup" project_pk=project.pk %}" class="font-bold text-slate-500 dark:text-slate-300 ml-4">Cancel</a>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const kindSelect = document.querySelector('select[name="kind"]');
|
||||
const blocks = document.querySelectorAll('.config-form-block');
|
||||
|
||||
function updateConfigForms() {
|
||||
const selectedKind = kindSelect.value;
|
||||
|
||||
blocks.forEach(block => {
|
||||
const blockKind = block.dataset.configKind;
|
||||
const isActive = (blockKind === selectedKind);
|
||||
|
||||
block.classList.toggle('hidden', !isActive);
|
||||
block.classList.toggle('block', isActive);
|
||||
block.querySelectorAll('input, select, textarea, button').forEach(element => {
|
||||
element.disabled = !isActive;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateConfigForms(); // Initialize on page load
|
||||
|
||||
kindSelect.addEventListener('change', updateConfigForms);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -20,9 +20,8 @@ from bugsink.app_settings import get_settings, CB_ANYBODY, CB_MEMBERS, CB_ADMINS
|
||||
from bugsink.decorators import login_exempt, atomic_for_request_method
|
||||
from bugsink.utils import assert_
|
||||
|
||||
from alerts.models import MessagingServiceConfig
|
||||
from alerts.forms import MessagingServiceConfigForm
|
||||
from alerts.service_backends.slack import SlackConfigForm
|
||||
from alerts.models import MessagingServiceConfig, get_alert_service_backend_class, get_alert_service_kind_choices
|
||||
from alerts.forms import MessagingServiceConfigNewForm, MessagingServiceConfigEditForm
|
||||
|
||||
from .models import Project, ProjectMembership, ProjectRole, ProjectVisibility
|
||||
from .forms import ProjectMembershipForm, MyProjectMembershipForm, ProjectMemberInviteForm, ProjectForm
|
||||
@@ -457,26 +456,35 @@ def project_messaging_service_add(request, project_pk):
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
config_forms = {
|
||||
kind: get_alert_service_backend_class(kind).get_form_class()()
|
||||
for (kind, _) in get_alert_service_kind_choices()
|
||||
}
|
||||
|
||||
if request.method == 'POST':
|
||||
form = MessagingServiceConfigForm(project, request.POST)
|
||||
config_form = SlackConfigForm(data=request.POST)
|
||||
form = MessagingServiceConfigNewForm(project, request.POST)
|
||||
kind = form.data.get('kind') or form.fields['kind'].initial
|
||||
config_form = get_alert_service_backend_class(kind).get_form_class()(data=request.POST)
|
||||
config_forms[kind] = config_form
|
||||
|
||||
if form.is_valid() and config_form.is_valid():
|
||||
service = form.save(commit=False)
|
||||
service.config = json.dumps(config_form.get_config())
|
||||
service.save()
|
||||
if form.is_valid():
|
||||
if config_form.is_valid():
|
||||
service = form.save(commit=False)
|
||||
service.config = json.dumps(config_form.get_config())
|
||||
service.save()
|
||||
|
||||
messages.success(request, "Messaging service added successfully.")
|
||||
return redirect('project_alerts_setup', project_pk=project_pk)
|
||||
messages.success(request, "Messaging service added successfully.")
|
||||
return redirect('project_alerts_setup', project_pk=project_pk)
|
||||
|
||||
else:
|
||||
form = MessagingServiceConfigForm(project)
|
||||
config_form = SlackConfigForm()
|
||||
form = MessagingServiceConfigNewForm(project)
|
||||
kind = form.fields['kind'].initial
|
||||
|
||||
return render(request, 'projects/project_messaging_service_edit.html', {
|
||||
return render(request, 'projects/project_messaging_service_new.html', {
|
||||
'project': project,
|
||||
'form': form,
|
||||
'config_form': config_form,
|
||||
'config_forms': config_forms,
|
||||
'selected_config_form_kind': kind,
|
||||
})
|
||||
|
||||
|
||||
@@ -486,10 +494,14 @@ def project_messaging_service_edit(request, project_pk, service_pk):
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
instance = project.service_configs.get(id=service_pk)
|
||||
# for editing, we don't allow for changing the kind; although it's probably possible to implement it, it would raise
|
||||
# questions on "how much are the various configs related (should data be transferred from one config to another).
|
||||
# and even though "it's possible" simply disallowing greatly simplifies the implementation.
|
||||
config_form_class = get_alert_service_backend_class(instance.kind).get_form_class()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = MessagingServiceConfigForm(project, request.POST, instance=instance)
|
||||
config_form = SlackConfigForm(data=request.POST)
|
||||
form = MessagingServiceConfigEditForm(request.POST, instance=instance)
|
||||
config_form = config_form_class(data=request.POST)
|
||||
|
||||
if form.is_valid() and config_form.is_valid():
|
||||
service = form.save(commit=False)
|
||||
@@ -500,8 +512,8 @@ def project_messaging_service_edit(request, project_pk, service_pk):
|
||||
return redirect('project_alerts_setup', project_pk=project_pk)
|
||||
|
||||
else:
|
||||
form = MessagingServiceConfigForm(project, instance=instance)
|
||||
config_form = SlackConfigForm(config=json.loads(instance.config))
|
||||
form = MessagingServiceConfigEditForm(instance=instance)
|
||||
config_form = config_form_class(config=json.loads(instance.config))
|
||||
|
||||
return render(request, 'projects/project_messaging_service_edit.html', {
|
||||
'project': project,
|
||||
|
||||
Reference in New Issue
Block a user