[WEB-5282] chore: triage state in intake (#8135)
* chore: traige state in intake * chore: triage state changes * feat: implement intake state dropdown component and integrate into issue properties * chore: added the triage state validation * chore: added triage state filter * chore: added workspace filter * fix: migration file * chore: added triage group state check * chore: updated the filters * chore: updated the filters * chore: added variables for intake state * fix: import error * refactor: improve project intake state retrieval logic and update TriageGroupIcon component * chore: changed the intake validation logic * refactor: update intake state types and clean up unused interfaces * chore: changed the state color * chore: changed the update serializer * chore: updated with current instance * chore: update TriageGroupIcon color to match new intake state group color * chore: stringified value * chore: added validation in serializer * chore: added logger instead of print * fix: correct component closing syntax in ActiveProjectItem * chore: updated the migration file * chore: added noop in migation --------- Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
This commit is contained in:
parent
dbc5a6348d
commit
78fbdde165
36 changed files with 952 additions and 181 deletions
|
|
@ -103,6 +103,54 @@ class IntakeIssueUpdateSerializer(BaseSerializer):
|
|||
"updated_at",
|
||||
]
|
||||
|
||||
def validate(self, attrs):
|
||||
"""
|
||||
Validate that if status is being changed to accepted (1),
|
||||
the project has a default state to transition to.
|
||||
"""
|
||||
from plane.db.models import State
|
||||
|
||||
# Check if status is being updated to accepted
|
||||
if attrs.get("status") == 1:
|
||||
intake_issue = self.instance
|
||||
issue = intake_issue.issue
|
||||
|
||||
# Check if issue is in TRIAGE state
|
||||
if issue.state and issue.state.group == State.TRIAGE:
|
||||
# Verify default state exists before allowing the update
|
||||
default_state = State.objects.filter(
|
||||
workspace=intake_issue.workspace, project=intake_issue.project, default=True
|
||||
).first()
|
||||
|
||||
if not default_state:
|
||||
raise serializers.ValidationError(
|
||||
{"status": "Cannot accept intake issue: No default state found for the project"}
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""
|
||||
Update intake issue and transition associated issue state if accepted.
|
||||
"""
|
||||
from plane.db.models import State
|
||||
|
||||
# Update the intake issue with validated data
|
||||
instance = super().update(instance, validated_data)
|
||||
|
||||
# If status is accepted (1), update the associated issue state from TRIAGE to default
|
||||
if validated_data.get("status") == 1:
|
||||
issue = instance.issue
|
||||
if issue.state and issue.state.group == State.TRIAGE:
|
||||
# Get the default project state
|
||||
default_state = State.objects.filter(
|
||||
workspace=instance.workspace, project=instance.project, default=True
|
||||
).first()
|
||||
if default_state:
|
||||
issue.state = default_state
|
||||
issue.save()
|
||||
return instance
|
||||
|
||||
|
||||
class IssueDataSerializer(serializers.Serializer):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import State
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class StateSerializer(BaseSerializer):
|
||||
|
|
@ -15,6 +16,9 @@ class StateSerializer(BaseSerializer):
|
|||
# If the default is being provided then make all other states default False
|
||||
if data.get("default", False):
|
||||
State.objects.filter(project_id=self.context.get("project_id")).update(default=False)
|
||||
|
||||
if data.get("group", None) == State.TRIAGE:
|
||||
raise serializers.ValidationError("Cannot create triage state")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -165,6 +165,20 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView):
|
|||
]:
|
||||
return Response({"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# get the triage state
|
||||
triage_state = State.triage_objects.filter(project_id=project_id, workspace__slug=slug).first()
|
||||
|
||||
if not triage_state:
|
||||
triage_state = State.objects.create(
|
||||
name="Intake Triage",
|
||||
group=State.TRIAGE,
|
||||
project_id=project_id,
|
||||
workspace_id=project.workspace_id,
|
||||
color="#4E5355",
|
||||
sequence=65000,
|
||||
default=False,
|
||||
)
|
||||
|
||||
# create an issue
|
||||
issue = Issue.objects.create(
|
||||
name=request.data.get("issue", {}).get("name"),
|
||||
|
|
@ -172,6 +186,7 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView):
|
|||
description_html=request.data.get("issue", {}).get("description_html", "<p></p>"),
|
||||
priority=request.data.get("issue", {}).get("priority", "none"),
|
||||
project_id=project_id,
|
||||
state_id=triage_state.id,
|
||||
)
|
||||
|
||||
# create an intake issue
|
||||
|
|
@ -320,7 +335,10 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView):
|
|||
|
||||
# Get issue data
|
||||
issue_data = request.data.pop("issue", False)
|
||||
issue_serializer = None
|
||||
intake_serializer = None
|
||||
|
||||
# Validate issue data if provided
|
||||
if bool(issue_data):
|
||||
issue = Issue.objects.annotate(
|
||||
label_ids=Coalesce(
|
||||
|
|
@ -344,6 +362,7 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView):
|
|||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
).get(pk=issue_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# Only allow guests to edit name and description
|
||||
if project_member.role <= 5:
|
||||
issue_data = {
|
||||
|
|
@ -354,71 +373,55 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView):
|
|||
|
||||
issue_serializer = IssueSerializer(issue, data=issue_data, partial=True)
|
||||
|
||||
if issue_serializer.is_valid():
|
||||
current_instance = issue
|
||||
# Log all the updates
|
||||
requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder)
|
||||
if issue is not None:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=requested_data,
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
intake=(intake_issue.id),
|
||||
)
|
||||
issue_serializer.save()
|
||||
else:
|
||||
if not issue_serializer.is_valid():
|
||||
return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Only project admins and members can edit intake issue attributes
|
||||
if project_member.role > 15:
|
||||
serializer = IntakeIssueUpdateSerializer(intake_issue, data=request.data, partial=True)
|
||||
intake_serializer = IntakeIssueUpdateSerializer(intake_issue, data=request.data, partial=True)
|
||||
|
||||
if not intake_serializer.is_valid():
|
||||
return Response(intake_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Both serializers are valid, now save them
|
||||
if issue_serializer:
|
||||
current_instance = issue
|
||||
# Log all the updates
|
||||
requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder)
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=requested_data,
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
issue_serializer.save()
|
||||
|
||||
# Save intake issue (state transition happens in serializer's update method)
|
||||
if intake_serializer:
|
||||
current_instance = json.dumps(IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder)
|
||||
intake_serializer.save()
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
# Update the issue state if the issue is rejected or marked as duplicate
|
||||
if serializer.data["status"] in [-1, 2]:
|
||||
issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id)
|
||||
state = State.objects.filter(group="cancelled", workspace__slug=slug, project_id=project_id).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
issue.save()
|
||||
|
||||
# Update the issue state if it is accepted
|
||||
if serializer.data["status"] in [1]:
|
||||
issue = Issue.objects.get(pk=issue_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# Update the issue state only if it is in triage state
|
||||
if issue.state.is_triage:
|
||||
# Move to default state
|
||||
state = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
issue.save()
|
||||
|
||||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=current_instance,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=False,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
serializer = IntakeIssueSerializer(intake_issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=current_instance,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=False,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
return Response(IntakeIssueSerializer(intake_issue).data, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(IntakeIssueSerializer(intake_issue).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
|
|||
|
|
@ -265,6 +265,12 @@ class ProjectListCreateAPIEndpoint(BaseAPIView):
|
|||
"sequence": 55000,
|
||||
"group": "cancelled",
|
||||
},
|
||||
{
|
||||
"name": "Intake Triage",
|
||||
"color": "#4E5355",
|
||||
"sequence": 65000,
|
||||
"group": State.TRIAGE,
|
||||
},
|
||||
]
|
||||
|
||||
State.objects.bulk_create(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue