mirror of
https://github.com/makeplane/plane.git
synced 2026-07-13 22:09:12 +02:00
[WEB-4525] fix: project create/update error handling (#3695)
* fix: internal server error * refactor: project creation endpoint * chore: project create/update error handling --------- Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
This commit is contained in:
committed by
GitHub
parent
86991a1101
commit
bfe77c3f16
@@ -434,145 +434,128 @@ class ProjectViewSet(BaseViewSet):
|
||||
user_id=serializer.data["project_lead"],
|
||||
)
|
||||
|
||||
if serializer.data["project_lead"] is not None and str(
|
||||
serializer.data["project_lead"]
|
||||
) != str(request.user.id):
|
||||
ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member_id=serializer.data["project_lead"],
|
||||
role=20,
|
||||
)
|
||||
# Also create the issue property for the user
|
||||
IssueUserProperty.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
user_id=serializer.data["project_lead"],
|
||||
)
|
||||
# Default states
|
||||
states = [
|
||||
{
|
||||
"name": "Backlog",
|
||||
"color": "#60646C",
|
||||
"sequence": 15000,
|
||||
"group": "backlog",
|
||||
"default": True,
|
||||
},
|
||||
{
|
||||
"name": "Todo",
|
||||
"color": "#60646C",
|
||||
"sequence": 25000,
|
||||
"group": "unstarted",
|
||||
},
|
||||
{
|
||||
"name": "In Progress",
|
||||
"color": "#F59E0B",
|
||||
"sequence": 35000,
|
||||
"group": "started",
|
||||
},
|
||||
{
|
||||
"name": "Done",
|
||||
"color": "#46A758",
|
||||
"sequence": 45000,
|
||||
"group": "completed",
|
||||
},
|
||||
{
|
||||
"name": "Cancelled",
|
||||
"color": "#9AA4BC",
|
||||
"sequence": 55000,
|
||||
"group": "cancelled",
|
||||
},
|
||||
]
|
||||
|
||||
# Default states
|
||||
states = [
|
||||
{
|
||||
"name": "Backlog",
|
||||
"color": "#60646C",
|
||||
"sequence": 15000,
|
||||
"group": "backlog",
|
||||
"default": True,
|
||||
},
|
||||
{
|
||||
"name": "Todo",
|
||||
"color": "#60646C",
|
||||
"sequence": 25000,
|
||||
"group": "unstarted",
|
||||
},
|
||||
{
|
||||
"name": "In Progress",
|
||||
"color": "#F59E0B",
|
||||
"sequence": 35000,
|
||||
"group": "started",
|
||||
},
|
||||
{
|
||||
"name": "Done",
|
||||
"color": "#46A758",
|
||||
"sequence": 45000,
|
||||
"group": "completed",
|
||||
},
|
||||
{
|
||||
"name": "Cancelled",
|
||||
"color": "#9AA4BC",
|
||||
"sequence": 55000,
|
||||
"group": "cancelled",
|
||||
},
|
||||
State.objects.bulk_create(
|
||||
[
|
||||
State(
|
||||
name=state["name"],
|
||||
color=state["color"],
|
||||
project=serializer.instance,
|
||||
sequence=state["sequence"],
|
||||
workspace=serializer.instance.workspace,
|
||||
group=state["group"],
|
||||
default=state.get("default", False),
|
||||
created_by=request.user,
|
||||
)
|
||||
for state in states
|
||||
]
|
||||
)
|
||||
|
||||
State.objects.bulk_create(
|
||||
[
|
||||
State(
|
||||
name=state["name"],
|
||||
color=state["color"],
|
||||
project=serializer.instance,
|
||||
sequence=state["sequence"],
|
||||
workspace=serializer.instance.workspace,
|
||||
group=state["group"],
|
||||
default=state.get("default", False),
|
||||
created_by=request.user,
|
||||
)
|
||||
for state in states
|
||||
]
|
||||
)
|
||||
|
||||
# validating the PROJECT_GROUPING feature flag is enabled
|
||||
if check_workspace_feature_flag(
|
||||
feature_key=FeatureFlag.PROJECT_GROUPING,
|
||||
slug=slug,
|
||||
user_id=str(request.user.id),
|
||||
default_value=False,
|
||||
# validating the PROJECT_GROUPING feature flag is enabled
|
||||
if check_workspace_feature_flag(
|
||||
feature_key=FeatureFlag.PROJECT_GROUPING,
|
||||
slug=slug,
|
||||
user_id=str(request.user.id),
|
||||
default_value=False,
|
||||
):
|
||||
# validating the is_project_grouping_enabled workspace feature is enabled
|
||||
if check_workspace_feature(
|
||||
slug, WorkspaceFeatureContext.IS_PROJECT_GROUPING_ENABLED
|
||||
):
|
||||
# validating the is_project_grouping_enabled workspace feature is enabled
|
||||
if check_workspace_feature(
|
||||
slug, WorkspaceFeatureContext.IS_PROJECT_GROUPING_ENABLED
|
||||
):
|
||||
state_id = request.data.get("state_id", None)
|
||||
priority = request.data.get("priority", "none")
|
||||
start_date = request.data.get("start_date", None)
|
||||
target_date = request.data.get("target_date", None)
|
||||
state_id = request.data.get("state_id", None)
|
||||
priority = request.data.get("priority", "none")
|
||||
start_date = request.data.get("start_date", None)
|
||||
target_date = request.data.get("target_date", None)
|
||||
|
||||
if state_id is None:
|
||||
state_id = (
|
||||
ProjectState.objects.filter(
|
||||
workspace=workspace, default=True
|
||||
)
|
||||
.values_list("id", flat=True)
|
||||
.first()
|
||||
if state_id is None:
|
||||
state_id = (
|
||||
ProjectState.objects.filter(
|
||||
workspace=workspace, default=True
|
||||
)
|
||||
|
||||
# also create project attributes
|
||||
_ = ProjectAttribute.objects.create(
|
||||
project_id=serializer.data.get("id"),
|
||||
state_id=state_id,
|
||||
priority=priority,
|
||||
start_date=start_date,
|
||||
target_date=target_date,
|
||||
workspace_id=workspace.id,
|
||||
.values_list("id", flat=True)
|
||||
.first()
|
||||
)
|
||||
|
||||
project = (
|
||||
self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
)
|
||||
# also create project attributes
|
||||
_ = ProjectAttribute.objects.create(
|
||||
project_id=serializer.data.get("id"),
|
||||
state_id=state_id,
|
||||
priority=priority,
|
||||
start_date=start_date,
|
||||
target_date=target_date,
|
||||
workspace_id=workspace.id,
|
||||
)
|
||||
|
||||
# Create the project feature
|
||||
_ = ProjectFeature.objects.create(
|
||||
workspace_id=workspace.id, project_id=project.id
|
||||
)
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
|
||||
# Create the model activity
|
||||
model_activity.delay(
|
||||
model_name="project",
|
||||
model_id=str(project.id),
|
||||
requested_data=request.data,
|
||||
current_instance=None,
|
||||
actor_id=request.user.id,
|
||||
slug=slug,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
# Create the project feature
|
||||
_ = ProjectFeature.objects.create(
|
||||
workspace_id=workspace.id, project_id=project.id
|
||||
)
|
||||
|
||||
project_activity.delay(
|
||||
type="project.activity.created",
|
||||
requested_data=json.dumps(
|
||||
self.request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
project_id=str(project.id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
# Create the model activity
|
||||
model_activity.delay(
|
||||
model_name="project",
|
||||
model_id=str(project.id),
|
||||
requested_data=request.data,
|
||||
current_instance=None,
|
||||
actor_id=request.user.id,
|
||||
slug=slug,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
|
||||
project_activity.delay(
|
||||
type="project.activity.created",
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
project_id=str(project.id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
|
||||
serializer = ProjectListSerializer(
|
||||
project, context={"request": request, "slug": slug}
|
||||
)
|
||||
payload = self.update_project_member_role(serializer.data)
|
||||
return Response(payload, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
serializer = ProjectListSerializer(
|
||||
project, context={"request": request, "slug": slug}
|
||||
)
|
||||
payload = self.update_project_member_role(serializer.data)
|
||||
return Response(payload, 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(
|
||||
|
||||
@@ -99,45 +99,32 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||
// 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"),
|
||||
});
|
||||
}
|
||||
const nameError = errorData.name?.includes("PROJECT_NAME_ALREADY_EXIST");
|
||||
const identifierError = errorData?.identifier?.includes("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"),
|
||||
});
|
||||
}
|
||||
|
||||
// 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") {
|
||||
if (nameError || identifierError) {
|
||||
if (nameError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("error"),
|
||||
message: fieldErrors,
|
||||
title: t("toast.error"),
|
||||
message: t("project_name_already_taken"),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (identifierError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("project_identifier_already_taken"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("something_went_wrong"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback error handling if the error processing fails
|
||||
console.error("Error processing API error:", error);
|
||||
|
||||
@@ -116,45 +116,34 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
// 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: t("project_name_already_taken"),
|
||||
});
|
||||
}
|
||||
const nameError = errorData.name?.includes("PROJECT_NAME_ALREADY_EXIST");
|
||||
const identifierError = errorData?.identifier?.includes("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"),
|
||||
});
|
||||
}
|
||||
if (nameError || identifierError) {
|
||||
console.log("coming here 1");
|
||||
|
||||
// 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") {
|
||||
if (nameError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("error"),
|
||||
message: fieldErrors,
|
||||
title: t("toast.error"),
|
||||
message: t("project_name_already_taken"),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (identifierError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("project_identifier_already_taken"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("something_went_wrong"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback error handling if the error processing fails
|
||||
console.error("Error processing API error:", error);
|
||||
|
||||
@@ -156,19 +156,52 @@ export const CreateProjectFormBase: FC<TCreateProjectFormProps> = observer((prop
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
Object.keys(err?.data ?? {}).map((key) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err.data[key],
|
||||
});
|
||||
try {
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
identifier: formData.identifier,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Handle the new error format where codes are nested in arrays under field names
|
||||
const errorData = err?.data ?? {};
|
||||
|
||||
const nameError = errorData.name?.includes("PROJECT_NAME_ALREADY_EXIST");
|
||||
const identifierError = errorData?.identifier?.includes("PROJECT_IDENTIFIER_ALREADY_EXIST");
|
||||
|
||||
if (nameError || identifierError) {
|
||||
if (nameError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("project_name_already_taken"),
|
||||
});
|
||||
}
|
||||
|
||||
if (identifierError) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("project_identifier_already_taken"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("something_went_wrong"),
|
||||
});
|
||||
}
|
||||
} 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"),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user