[WEB-4281] chore: error code on project updation endpoint (#7218)

This commit is contained in:
Sangeetha 2025-07-17 13:05:24 +05:30 committed by GitHub
parent 9523c28c3e
commit ec0ef98c1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 295 additions and 259 deletions

View file

@ -24,55 +24,51 @@ class ProjectSerializer(BaseSerializer):
fields = "__all__"
read_only_fields = ["workspace", "deleted_at"]
def validate_name(self, name):
project_id = self.instance.id if self.instance else None
workspace_id = self.context["workspace_id"]
project = Project.objects.filter(name=name, workspace_id=workspace_id)
if project_id:
project = project.exclude(id=project_id)
if project.exists():
raise serializers.ValidationError(
detail="PROJECT_NAME_ALREADY_EXIST",
)
return name
def validate_identifier(self, identifier):
project_id = self.instance.id if self.instance else None
workspace_id = self.context["workspace_id"]
project = Project.objects.filter(
identifier=identifier, workspace_id=workspace_id
)
if project_id:
project = project.exclude(id=project_id)
if project.exists():
raise serializers.ValidationError(
detail="PROJECT_IDENTIFIER_ALREADY_EXIST",
)
return identifier
def create(self, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
if identifier == "":
raise serializers.ValidationError(detail="Project Identifier is required")
workspace_id = self.context["workspace_id"]
if ProjectIdentifier.objects.filter(
name=identifier, workspace_id=self.context["workspace_id"]
).exists():
raise serializers.ValidationError(detail="Project Identifier is taken")
project = Project.objects.create(
**validated_data, workspace_id=self.context["workspace_id"]
project = Project.objects.create(**validated_data, workspace_id=workspace_id)
ProjectIdentifier.objects.create(
name=project.identifier, project=project, workspace_id=workspace_id
)
_ = ProjectIdentifier.objects.create(
name=project.identifier,
project=project,
workspace_id=self.context["workspace_id"],
)
return project
def update(self, instance, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
# If identifier is not passed update the project and return
if identifier == "":
project = super().update(instance, validated_data)
return project
# If no Project Identifier is found create it
project_identifier = ProjectIdentifier.objects.filter(
name=identifier, workspace_id=instance.workspace_id
).first()
if project_identifier is None:
project = super().update(instance, validated_data)
project_identifier = ProjectIdentifier.objects.filter(
project=project
).first()
if project_identifier is not None:
project_identifier.name = identifier
project_identifier.save()
return project
# If found check if the project_id to be updated and identifier project id is same
if project_identifier.project_id == instance.id:
# If same pass update
project = super().update(instance, validated_data)
return project
# If not same fail update
raise serializers.ValidationError(detail="Project Identifier is already taken")
class ProjectLiteSerializer(BaseSerializer):
class Meta:

View file

@ -239,7 +239,6 @@ class ProjectViewSet(BaseViewSet):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def create(self, request, slug):
try:
workspace = Workspace.objects.get(slug=slug)
serializer = ProjectSerializer(
@ -338,30 +337,9 @@ class ProjectViewSet(BaseViewSet):
serializer = ProjectListSerializer(project)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{
"name": "The project name is already taken",
"code": "PROJECT_NAME_ALREADY_EXIST",
},
status=status.HTTP_409_CONFLICT,
)
except Workspace.DoesNotExist:
return Response(
{"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except serializers.ValidationError:
return Response(
{
"identifier": "The project identifier is already taken",
"code": "PROJECT_IDENTIFIER_ALREADY_EXIST",
},
status=status.HTTP_409_CONFLICT,
)
def partial_update(self, request, slug, pk=None):
try:
# try:
if not ProjectMember.objects.filter(
member=request.user,
workspace__slug=slug,
@ -397,9 +375,7 @@ class ProjectViewSet(BaseViewSet):
if serializer.is_valid():
serializer.save()
if intake_view:
intake = Intake.objects.filter(
project=project, is_default=True
).first()
intake = Intake.objects.filter(project=project, is_default=True).first()
if not intake:
Intake.objects.create(
name=f"{project.name} Intake",
@ -422,22 +398,6 @@ class ProjectViewSet(BaseViewSet):
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"name": "The project name is already taken"},
status=status.HTTP_409_CONFLICT,
)
except (Project.DoesNotExist, Workspace.DoesNotExist):
return Response(
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except serializers.ValidationError:
return Response(
{"identifier": "The project identifier is already taken"},
status=status.HTTP_409_CONFLICT,
)
def destroy(self, request, slug, pk):
if (
WorkspaceMember.objects.filter(

View file

@ -88,32 +88,64 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
handleNextStep(res.id);
})
.catch((err) => {
try {
captureError({
eventName: PROJECT_TRACKER_EVENTS.create,
payload: {
identifier: formData.identifier,
},
});
if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") {
// Handle the new error format where codes are nested in arrays under field names
const errorData = err?.data ?? {};
// Check for specific error codes in the new format
if (errorData.name?.includes("PROJECT_NAME_ALREADY_EXIST")) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("project_name_already_taken"),
});
} else if (err?.data.code === "PROJECT_IDENTIFIER_ALREADY_EXIST") {
}
if (errorData?.identifier?.includes("PROJECT_IDENTIFIER_ALREADY_EXIST")) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("project_identifier_already_taken"),
});
} else {
Object.keys(err?.data ?? {}).map((key) => {
}
// Handle other field-specific errors (excluding name and identifier which are handled above)
Object.keys(errorData).forEach((field) => {
// Skip name and identifier fields as they're handled separately above
if (field === "name" || field === "identifier") return;
const fieldErrors = errorData[field];
if (Array.isArray(fieldErrors)) {
fieldErrors.forEach((errorMessage) => {
setToast({
type: TOAST_TYPE.ERROR,
title: t("error"),
message: err.data[key],
message: errorMessage,
});
});
} else if (typeof fieldErrors === "string") {
setToast({
type: TOAST_TYPE.ERROR,
title: t("error"),
message: fieldErrors,
});
}
});
} catch (error) {
// Fallback error handling if the error processing fails
console.error("Error processing API error:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("something_went_wrong"),
});
}
});
};

View file

@ -104,18 +104,66 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
message: t("project_settings.general.toast.success"),
});
})
.catch((error) => {
.catch((err) => {
try {
captureError({
eventName: PROJECT_TRACKER_EVENTS.update,
payload: {
id: projectId,
},
});
// Handle the new error format where codes are nested in arrays under field names
const errorData = err ?? {};
// Check for specific error codes in the new format
if (errorData.name?.includes("PROJECT_NAME_ALREADY_EXIST")) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: error?.error ?? t("project_settings.general.toast.error"),
message: t("project_name_already_taken"),
});
}
if (errorData?.identifier?.includes("PROJECT_IDENTIFIER_ALREADY_EXIST")) {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("project_identifier_already_taken"),
});
}
// Handle other field-specific errors (excluding name and identifier which are handled above)
Object.keys(errorData).forEach((field) => {
// Skip name and identifier fields as they're handled separately above
if (field === "name" || field === "identifier") return;
const fieldErrors = errorData[field];
if (Array.isArray(fieldErrors)) {
fieldErrors.forEach((errorMessage) => {
setToast({
type: TOAST_TYPE.ERROR,
title: t("error"),
message: errorMessage,
});
});
} else if (typeof fieldErrors === "string") {
setToast({
type: TOAST_TYPE.ERROR,
title: t("error"),
message: fieldErrors,
});
}
});
} catch (error) {
// Fallback error handling if the error processing fails
console.error("Error processing API error:", error);
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("something_went_wrong"),
});
}
});
};