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:
sankalp committed 2026-06-29 07:50:27 +05:45
1 parent 2f1df8ab03
commit 25554feac5
6 files changed
+122 -2

No files matched your search

+1
View File
@@ -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(
+9 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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"),
},
)
+1
View File
@@ -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(