feat(page_views): add automation rule management UI
Add support for creating, toggling and deleting automation rules to streamline workflow and reduce manual effort
This commit is contained in:
6 files changed
+122
-2
No files matched your search
@@ -122,6 +122,7 @@ def move_ticket(request, project_id, ticket_id):
|
||||
)
|
||||
|
||||
ticket.status = status
|
||||
ticket._updater = request.user
|
||||
ticket.save(update_fields=["status", "updated_at"])
|
||||
|
||||
TicketHistory.objects.create(
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.views.decorators.http import require_http_methods
|
||||
|
||||
from src.accounts.models import ADMIN_ROLES, Role
|
||||
from src.epics.models import Epic
|
||||
from src.projects.models import BoardRolePermission, Project
|
||||
from src.projects.models import AutomationRule, BoardRolePermission, Project
|
||||
from src.tickets.models import (
|
||||
Ticket,
|
||||
TicketPriority,
|
||||
@@ -145,6 +145,13 @@ def project_board(request, project_id=None):
|
||||
if selected_project
|
||||
else []
|
||||
)
|
||||
automation_rules = (
|
||||
AutomationRule.objects.filter(project=selected_project)
|
||||
.select_related("trigger_status")
|
||||
.order_by("-created_at")
|
||||
if selected_project
|
||||
else []
|
||||
)
|
||||
role_choices = Role.objects.all()
|
||||
|
||||
return render(
|
||||
@@ -177,6 +184,7 @@ def project_board(request, project_id=None):
|
||||
"first_status_id": first_status_id,
|
||||
"first_status_name": first_status_name,
|
||||
"permissions": permissions,
|
||||
"automation_rules": automation_rules,
|
||||
"role_choices": role_choices,
|
||||
"admin_roles": ADMIN_ROLES,
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .automation_views import *
|
||||
from .board_permission_views import *
|
||||
from .epic_views import *
|
||||
from .external_views import *
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import json
|
||||
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from src.projects.models import AutomationRule, Project
|
||||
|
||||
VALID_ACTION_TYPES = frozenset(
|
||||
{
|
||||
"assign_user",
|
||||
"change_status",
|
||||
"notify_in_app",
|
||||
"send_email",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate_actions(actions):
|
||||
if not isinstance(actions, list):
|
||||
return False, "Actions must be a list."
|
||||
for i, action in enumerate(actions):
|
||||
if not isinstance(action, dict):
|
||||
return False, f"Action at index {i} must be an object."
|
||||
atype = action.get("type")
|
||||
if atype not in VALID_ACTION_TYPES:
|
||||
return False, f"Action at index {i}: unknown type '{atype}'."
|
||||
return True, None
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def automation_rules(request, project_id):
|
||||
project = get_object_or_404(Project, pk=project_id)
|
||||
|
||||
if not request.user.is_admin:
|
||||
messages.error(request, "Permission denied.")
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
if request.method == "POST":
|
||||
name = request.POST.get("name", "").strip()
|
||||
trigger_event = request.POST.get("trigger_event", "status_changed")
|
||||
trigger_status_id = request.POST.get("trigger_status") or None
|
||||
actions_raw = request.POST.get("actions", "[]")
|
||||
|
||||
if not name:
|
||||
messages.error(request, "Rule name is required.")
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
try:
|
||||
actions = json.loads(actions_raw)
|
||||
except json.JSONDecodeError:
|
||||
messages.error(request, "Invalid actions JSON.")
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
valid, error = _validate_actions(actions)
|
||||
if not valid:
|
||||
messages.error(request, error)
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
AutomationRule.objects.create(
|
||||
project=project,
|
||||
name=name,
|
||||
trigger_event=trigger_event,
|
||||
trigger_status_id=trigger_status_id,
|
||||
actions=actions,
|
||||
created_by=request.user,
|
||||
)
|
||||
messages.success(request, f"Automation rule '{name}' created.")
|
||||
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def automation_rule_toggle(request, project_id, rule_id):
|
||||
project = get_object_or_404(Project, pk=project_id)
|
||||
if not request.user.is_admin:
|
||||
return JsonResponse({"error": "Permission denied"}, status=403)
|
||||
|
||||
rule = get_object_or_404(AutomationRule, pk=rule_id, project=project)
|
||||
rule.is_active = not rule.is_active
|
||||
rule.save(update_fields=["is_active"])
|
||||
return JsonResponse({"is_active": rule.is_active})
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def automation_rule_delete(request, project_id, rule_id):
|
||||
project = get_object_or_404(Project, pk=project_id)
|
||||
|
||||
if not request.user.is_admin:
|
||||
messages.error(request, "Permission denied.")
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
|
||||
rule = get_object_or_404(AutomationRule, pk=rule_id, project=project)
|
||||
rule.delete()
|
||||
messages.success(request, f"Automation rule '{rule.name}' deleted.")
|
||||
return redirect("project-board-project", project_id=project_id)
|
||||
@@ -13,7 +13,12 @@ from django.views.decorators.http import require_http_methods
|
||||
from src.accounts.models import ADMIN_ROLES, Role
|
||||
from src.epics.models import Epic
|
||||
from src.notes.models import ProjectNote
|
||||
from src.projects.models import BoardRolePermission, Project, ProjectMember
|
||||
from src.projects.models import (
|
||||
AutomationRule,
|
||||
BoardRolePermission,
|
||||
Project,
|
||||
ProjectMember,
|
||||
)
|
||||
from src.tickets.models import (
|
||||
Ticket,
|
||||
TicketPriority,
|
||||
@@ -297,6 +302,13 @@ def project_detail(request, project_id):
|
||||
.order_by("role"),
|
||||
"role_choices": Role.objects.all(),
|
||||
"admin_roles": ADMIN_ROLES,
|
||||
"columns": [
|
||||
{"status": s}
|
||||
for s in project.ticket_statuses.all().order_by("sort_order")
|
||||
],
|
||||
"automation_rules": AutomationRule.objects.filter(project=project)
|
||||
.select_related("trigger_status")
|
||||
.order_by("-created_at"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -792,6 +792,7 @@ def tickets_bulk_status(request):
|
||||
for ticket in tickets:
|
||||
old_name = ticket.status.name
|
||||
ticket.status = status
|
||||
ticket._updater = request.user
|
||||
ticket.save(update_fields=["status", "updated_at"])
|
||||
|
||||
TicketHistory.objects.create(
|
||||
|
||||
Reference in new issue
Block a user