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.status = status
|
||||||
|
ticket._updater = request.user
|
||||||
ticket.save(update_fields=["status", "updated_at"])
|
ticket.save(update_fields=["status", "updated_at"])
|
||||||
|
|
||||||
TicketHistory.objects.create(
|
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.accounts.models import ADMIN_ROLES, Role
|
||||||
from src.epics.models import Epic
|
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 (
|
from src.tickets.models import (
|
||||||
Ticket,
|
Ticket,
|
||||||
TicketPriority,
|
TicketPriority,
|
||||||
@@ -145,6 +145,13 @@ def project_board(request, project_id=None):
|
|||||||
if selected_project
|
if selected_project
|
||||||
else []
|
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()
|
role_choices = Role.objects.all()
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
@@ -177,6 +184,7 @@ def project_board(request, project_id=None):
|
|||||||
"first_status_id": first_status_id,
|
"first_status_id": first_status_id,
|
||||||
"first_status_name": first_status_name,
|
"first_status_name": first_status_name,
|
||||||
"permissions": permissions,
|
"permissions": permissions,
|
||||||
|
"automation_rules": automation_rules,
|
||||||
"role_choices": role_choices,
|
"role_choices": role_choices,
|
||||||
"admin_roles": ADMIN_ROLES,
|
"admin_roles": ADMIN_ROLES,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .automation_views import *
|
||||||
from .board_permission_views import *
|
from .board_permission_views import *
|
||||||
from .epic_views import *
|
from .epic_views import *
|
||||||
from .external_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.accounts.models import ADMIN_ROLES, Role
|
||||||
from src.epics.models import Epic
|
from src.epics.models import Epic
|
||||||
from src.notes.models import ProjectNote
|
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 (
|
from src.tickets.models import (
|
||||||
Ticket,
|
Ticket,
|
||||||
TicketPriority,
|
TicketPriority,
|
||||||
@@ -297,6 +302,13 @@ def project_detail(request, project_id):
|
|||||||
.order_by("role"),
|
.order_by("role"),
|
||||||
"role_choices": Role.objects.all(),
|
"role_choices": Role.objects.all(),
|
||||||
"admin_roles": ADMIN_ROLES,
|
"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:
|
for ticket in tickets:
|
||||||
old_name = ticket.status.name
|
old_name = ticket.status.name
|
||||||
ticket.status = status
|
ticket.status = status
|
||||||
|
ticket._updater = request.user
|
||||||
ticket.save(update_fields=["status", "updated_at"])
|
ticket.save(update_fields=["status", "updated_at"])
|
||||||
|
|
||||||
TicketHistory.objects.create(
|
TicketHistory.objects.create(
|
||||||
|
|||||||
Reference in new issue
Block a user