diff --git a/apiserver/plane/ee/views/app/issue_property/base.py b/apiserver/plane/ee/views/app/issue_property/base.py
index 63ca9efdbb..8d45f9719c 100644
--- a/apiserver/plane/ee/views/app/issue_property/base.py
+++ b/apiserver/plane/ee/views/app/issue_property/base.py
@@ -1,5 +1,5 @@
# Django imports
-from django.db import IntegrityError
+from django.db import IntegrityError, models
# Third party imports
from rest_framework import status
@@ -8,10 +8,11 @@ from rest_framework.response import Response
# Module imports
from plane.db.models import Issue
from plane.ee.views.base import BaseAPIView
-from plane.ee.models import IssueProperty
+from plane.ee.models import IssueProperty, IssuePropertyOption
from plane.ee.permissions import ProjectEntityPermission
from plane.ee.serializers import (
IssuePropertySerializer,
+ IssuePropertyOptionSerializer,
)
from plane.payment.flags.flag_decorator import check_feature_flag
from plane.payment.flags.flag import FeatureFlag
@@ -58,8 +59,9 @@ class IssuePropertyEndpoint(BaseAPIView):
issue_type_id,
):
try:
- # Create a new issue properties
- serializer = IssuePropertySerializer(data=request.data)
+
+ # Get the options
+ options = request.data.pop("options", [])
# Check is_active
if not request.data.get("is_active"):
@@ -81,11 +83,94 @@ class IssuePropertyEndpoint(BaseAPIView):
if request.data.get("is_required") is True:
request.data["default_value"] = []
+ # Create a new issue properties
+ serializer = IssuePropertySerializer(data=request.data)
# Validate the data
serializer.is_valid(raise_exception=True)
# Save the data
serializer.save(project_id=project_id, issue_type_id=issue_type_id)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
+
+ issue_property = IssueProperty.objects.get(
+ project_id=project_id,
+ issue_type_id=issue_type_id,
+ pk=serializer.data["id"],
+ )
+
+ # Check if the property type is option and create the options
+ if issue_property.property_type == "OPTION":
+ workspace_id = issue_property.workspace_id
+ issue_property_id = issue_property.id
+
+ # Bulk create the options
+ bulk_create_options = []
+ last_id = IssuePropertyOption.objects.filter(
+ project=project_id, property_id=issue_property_id
+ ).aggregate(largest=models.Max("sort_order"))["largest"]
+
+ if last_id:
+ sort_order = last_id + 10000
+ else:
+ sort_order = 10000
+
+ for option in options:
+ if not option.get("name"):
+ return Response(
+ {"error": "Name of option is required"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
+
+ bulk_create_options.append(
+ IssuePropertyOption(
+ name=option.get("name"),
+ sort_order=sort_order,
+ property_id=issue_property_id,
+ description=option.get("description", ""),
+ logo_props=option.get("logo_props", {}),
+ is_active=option.get("is_active", True),
+ is_default=option.get("is_default", False),
+ parent_id=option.get("parent_id"),
+ workspace_id=issue_property.workspace_id,
+ project_id=project_id,
+ )
+ )
+
+ sort_order += 10000
+
+ # Create the options
+ IssuePropertyOption.objects.bulk_create(
+ bulk_create_options,
+ batch_size=100,
+ )
+
+ # Fetch all the default options
+ issue_property_options = IssuePropertyOption.objects.filter(
+ property_id=issue_property_id,
+ workspace_id=workspace_id,
+ project_id=project_id,
+ is_default=True,
+ ).values_list("id", flat=True)
+
+ # Save the default value
+ issue_property.default_value = [
+ str(option) for option in issue_property_options
+ ]
+ issue_property.save()
+
+ serializer = IssuePropertySerializer(issue_property)
+ options = IssuePropertyOption.objects.filter(
+ property_id=issue_property.id,
+ workspace_id=issue_property.workspace_id,
+ project_id=project_id,
+ )
+ options_serializer = IssuePropertyOptionSerializer(
+ options, many=True
+ )
+ # generate the response with the new data and options
+ response = {
+ "property_detail": serializer.data,
+ "options": options_serializer.data,
+ }
+ return Response(response, status=status.HTTP_201_CREATED)
except IntegrityError:
return Response(
{
@@ -114,6 +199,22 @@ class IssuePropertyEndpoint(BaseAPIView):
},
status=status.HTTP_400_BAD_REQUEST,
)
+ # if property type is being changed, reset the defaults
+ if (
+ request.data.get("property_type")
+ and request.data.get("property_type")
+ != issue_property.property_type
+ ):
+ defaults = {
+ "relation_type": None,
+ "default_value": [],
+ "settings": {},
+ "is_multi": False,
+ "validation_rules": {},
+ }
+ # Update request data with defaults for missing fields
+ for field, default_value in defaults.items():
+ request.data.setdefault(field, default_value)
# Check defaults
if (
diff --git a/apiserver/plane/ee/views/app/issue_property/option.py b/apiserver/plane/ee/views/app/issue_property/option.py
index 885c7c642d..f27cd4b4e6 100644
--- a/apiserver/plane/ee/views/app/issue_property/option.py
+++ b/apiserver/plane/ee/views/app/issue_property/option.py
@@ -78,37 +78,28 @@ class IssuePropertyOptionEndpoint(BaseAPIView):
project=project_id, property_id=issue_property_id
).aggregate(largest=models.Max("sort_order"))["largest"]
+ # Set the sort order for the new option
if last_id:
sort_order = last_id + 10000
else:
sort_order = 10000
- bulk_property_options = []
- for option in request.data.get("options", []):
- bulk_property_options.append(
- IssuePropertyOption(
- name=option.get("name"),
- sort_order=sort_order,
- property_id=issue_property_id,
- description=option.get("description", ""),
- logo_props=option.get("logo_props", {}),
- is_active=option.get("is_active", True),
- is_default=option.get("is_default", False),
- parent_id=option.get("parent_id"),
- workspace_id=issue_property.workspace_id,
- project_id=project_id,
- )
- )
- sort_order += 10000
-
- # Bulk create the options
- issue_property_options = IssuePropertyOption.objects.bulk_create(
- bulk_property_options, batch_size=100
+ # Create the issue property option
+ issue_property_option = IssuePropertyOption.objects.create(
+ name=request.data.get("name"),
+ sort_order=sort_order,
+ property_id=issue_property_id,
+ description=request.data.get("description", ""),
+ logo_props=request.data.get("logo_props", {}),
+ is_active=request.data.get("is_active", True),
+ is_default=request.data.get("is_default", False),
+ parent_id=request.data.get("parent_id"),
+ workspace_id=issue_property.workspace_id,
+ project_id=project_id,
)
- serializer = IssuePropertyOptionSerializer(
- issue_property_options, many=True
- )
+ # Serialize the data
+ serializer = IssuePropertyOptionSerializer(issue_property_option)
# Save the default value
return Response(serializer.data, status=status.HTTP_201_CREATED)
diff --git a/web/ee/components/issue-types/issue-type-list-item.tsx b/web/ee/components/issue-types/issue-type-list-item.tsx
index 4ea85b579b..4aa92efb1d 100644
--- a/web/ee/components/issue-types/issue-type-list-item.tsx
+++ b/web/ee/components/issue-types/issue-type-list-item.tsx
@@ -53,9 +53,9 @@ export const IssueTypeListItem = observer((props: TIssueTypeListItem) => {
isDefault={issueTypeDetail?.is_default}
containerClassName={cn(!issueTypeDetail?.is_active && "opacity-60")}
/>
-
+
-
{issueTypeDetail?.name}
+
{issueTypeDetail?.name}
{issueTypeDetail?.description}
diff --git a/web/ee/components/issue-types/properties/attributes/options/root.tsx b/web/ee/components/issue-types/properties/attributes/options/root.tsx
index 59acbe74cb..fb20570627 100644
--- a/web/ee/components/issue-types/properties/attributes/options/root.tsx
+++ b/web/ee/components/issue-types/properties/attributes/options/root.tsx
@@ -71,7 +71,7 @@ export const IssuePropertyOptionsRoot: FC
= observer(
const { key, ...payload } = value;
await issueProperty
- .createPropertyOptions([payload])
+ .createPropertyOption(payload)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
diff --git a/web/ee/components/issue-types/properties/delete-confirmation-modal.tsx b/web/ee/components/issue-types/properties/delete-confirmation-modal.tsx
new file mode 100644
index 0000000000..d61868d82b
--- /dev/null
+++ b/web/ee/components/issue-types/properties/delete-confirmation-modal.tsx
@@ -0,0 +1,83 @@
+import { observer } from "mobx-react";
+import { AlertTriangle } from "lucide-react";
+import { Button, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
+import { cn } from "@/helpers/common.helper";
+import { useState } from "react";
+
+type TProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ onDisable: () => Promise;
+ onDelete: () => Promise;
+};
+
+export const DeleteConfirmationModal: React.FC = observer((props) => {
+ const { isOpen, onClose, onDisable, onDelete } = props;
+ // states
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleDisable = async () => {
+ setIsSubmitting(true);
+ await onDisable().finally(() => {
+ onClose();
+ setIsSubmitting(false);
+ });
+ };
+
+ const handleDelete = async () => {
+ setIsSubmitting(true);
+ await onDelete().finally(() => {
+ onClose();
+ setIsSubmitting(false);
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
Delete this property
+
+
+
+
Deletion of properties may lead to loss of existing data.
+
Do you want to disable the property instead?
+
+
+
+
+
+
+
+
+
+ );
+});
diff --git a/web/ee/components/issue-types/properties/dropdowns/property-type.tsx b/web/ee/components/issue-types/properties/dropdowns/property-type.tsx
index 2830b1d93d..3f7b416282 100644
--- a/web/ee/components/issue-types/properties/dropdowns/property-type.tsx
+++ b/web/ee/components/issue-types/properties/dropdowns/property-type.tsx
@@ -19,7 +19,7 @@ import {
type TPropertyTypeDropdownProps = {
issueTypeId: string;
propertyType: EIssuePropertyType | undefined;
- propertyRelationType: EIssuePropertyRelationType | undefined;
+ propertyRelationType: EIssuePropertyRelationType | null | undefined;
currentOperationMode: TOperationMode | null;
handlePropertyObjectChange: (value: Partial>) => void;
error?: string;
diff --git a/web/ee/components/issue-types/properties/index.ts b/web/ee/components/issue-types/properties/index.ts
index 199d88e3b5..6e65b05eab 100644
--- a/web/ee/components/issue-types/properties/index.ts
+++ b/web/ee/components/issue-types/properties/index.ts
@@ -7,3 +7,4 @@ export * from "./dropdowns";
export * from "./attributes";
export * from "./mandatory-field-toggle";
export * from "./quick-actions";
+export * from "./delete-confirmation-modal";
diff --git a/web/ee/components/issue-types/properties/property-list-item.tsx b/web/ee/components/issue-types/properties/property-list-item.tsx
index 3e91a1bc3d..6a7ca2ae7b 100644
--- a/web/ee/components/issue-types/properties/property-list-item.tsx
+++ b/web/ee/components/issue-types/properties/property-list-item.tsx
@@ -1,4 +1,6 @@
import { useState } from "react";
+import cloneDeep from "lodash/cloneDeep";
+import isEmpty from "lodash/isEmpty";
import isEqual from "lodash/isEqual";
import omitBy from "lodash/omitBy";
import { observer } from "mobx-react";
@@ -24,6 +26,7 @@ import {
TCreationListModes,
TOperationMode,
TIssuePropertyOptionCreateList,
+ TIssuePropertyOption,
} from "@/plane-web/types";
type TIssuePropertyListItem = {
@@ -136,65 +139,25 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) =>
}
};
- const handleCreatePropertyOptions = async (propertyId: string | undefined) => {
- if (!propertyId || !issuePropertyOptionCreateList || !issuePropertyOptionCreateList.length) return;
-
- // get issue property details from the store
- const issueProperty = issueType?.getPropertyById(propertyId);
- if (!issueProperty) return;
-
- const payload = issuePropertyOptionCreateList
- .map((item) => {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { key, ...rest } = item;
- return rest;
- })
- .filter((item) => !!item.name);
-
- await issueProperty
- .createPropertyOptions(payload)
- .then(async (response) => {
- // get the response and set the default value for the property
- if (response && response.length) {
- const defaultOptionIds = response
- .filter((option) => option.id && option.is_default)
- .map((option) => option.id as string);
- // update the default value for the property
- handlePropertyDataChange("default_value", defaultOptionIds);
- // sync the property options
- await issueProperty.updateProperty(issueTypeId, {
- default_value: defaultOptionIds,
- });
- }
- setToast({
- type: TOAST_TYPE.SUCCESS,
- title: "Success!",
- message: `Property options created successfully.`,
- });
- return response;
- })
- .catch((error) => {
- setToast({
- type: TOAST_TYPE.ERROR,
- title: "Error!",
- message: error?.error ?? `Failed to create issue property options. Please try again!`,
- });
- })
- .finally(() => {
- setIssuePropertyOptionCreateList([]);
- });
- };
-
const handleCreateProperty = async () => {
if (!issuePropertyData) return;
+ // create property options payload (required for option type)
+ let optionsPayload: Partial[] = [];
+ if (issuePropertyData.property_type === EIssuePropertyType.OPTION) {
+ optionsPayload = issuePropertyOptionCreateList
+ .map((item) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { key, ...rest } = item;
+ return rest;
+ })
+ .filter((item) => !!item.name);
+ }
+
setIsSubmitting(true);
await issueType
- ?.createProperty(issuePropertyData)
+ ?.createProperty(issuePropertyData, optionsPayload)
.then(async (response) => {
- if (issuePropertyData.property_type === EIssuePropertyType.OPTION) {
- await handleCreatePropertyOptions(response?.id);
- }
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
@@ -210,6 +173,7 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) =>
});
})
.finally(() => {
+ setIssuePropertyOptionCreateList([]);
key && handleIssuePropertyCreateList("remove", { key, ...issuePropertyData });
setIsSubmitting(false);
});
@@ -218,7 +182,9 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) =>
const handleUpdateProperty = async (data: Partial>) => {
if (!data) return;
// Construct the payload by filtering out unchanged properties
- const payload = omitBy(data, (value, key) => isEqual(value, issuePropertyDetail[key]));
+ const originalData = cloneDeep(issuePropertyDetail);
+ const payload = originalData && omitBy(data, (value, key) => isEqual(value, originalData[key]));
+ if (isEmpty(payload)) return;
setIsSubmitting(true);
await issueProperty
?.updateProperty(issueTypeId, payload)
@@ -395,10 +361,11 @@ export const IssuePropertyListItem = observer((props: TIssuePropertyListItem) =>
setIssuePropertyOperationMode(mode)}
+ onCreateUpdate={handleCreateUpdate}
+ onDiscard={handleDiscard}
+ onDelete={handleDelete}
+ onDisable={async () => handlePropertyDataChange("is_active", false, true)}
+ onIssuePropertyOperationMode={(mode) => setIssuePropertyOperationMode(mode)}
/>
diff --git a/web/ee/components/issue-types/properties/quick-actions.tsx b/web/ee/components/issue-types/properties/quick-actions.tsx
index 804e7abfde..a3a9e27a4a 100644
--- a/web/ee/components/issue-types/properties/quick-actions.tsx
+++ b/web/ee/components/issue-types/properties/quick-actions.tsx
@@ -2,55 +2,44 @@ import { useState } from "react";
import { observer } from "mobx-react";
import { Check, Pencil, Trash2, X } from "lucide-react";
// ui
-import { AlertModalCore, Spinner, Tooltip } from "@plane/ui";
+import { Spinner, Tooltip } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
+// plane web components
+import { DeleteConfirmationModal } from "@/plane-web/components/issue-types";
// plane web types
import { TOperationMode } from "@/plane-web/types";
type TIssuePropertyQuickActions = {
currentOperationMode: TOperationMode | null;
isSubmitting: boolean;
- handleCreateUpdate: () => Promise
;
- handleDiscard: () => void;
- handleDelete: () => Promise;
- handleIssuePropertyOperationMode: (mode: TOperationMode) => void;
+ onCreateUpdate: () => Promise;
+ onDiscard: () => void;
+ onDisable: () => Promise;
+ onDelete: () => Promise;
+ onIssuePropertyOperationMode: (mode: TOperationMode) => void;
};
export const IssuePropertyQuickActions = observer((props: TIssuePropertyQuickActions) => {
const {
currentOperationMode,
isSubmitting,
- handleCreateUpdate,
- handleDiscard,
- handleDelete,
- handleIssuePropertyOperationMode,
+ onCreateUpdate,
+ onDiscard,
+ onDisable,
+ onDelete,
+ onIssuePropertyOperationMode,
} = props;
// states
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
- const [isDeleteLoading, setIsDeleteLoading] = useState(false);
-
- const handleDeleteIssueProperty = async () => {
- setIsDeleteLoading(true);
- await handleDelete().finally(() => {
- setIsDeleteLoading(false);
- });
- };
return (
<>
- setIsDeleteModalOpen(false)}
- handleSubmit={handleDeleteIssueProperty}
- isSubmitting={isDeleteLoading}
- title="Delete this property"
- content={
- <>
- Deletion of properties may lead to loss of existing data or weird behavior.
- Do you want to disable the property instead?
- >
- }
+ onClose={() => setIsDeleteModalOpen(false)}
+ onDisable={onDisable}
+ onDelete={onDelete}
/>
{isSubmitting ?
:
}
@@ -79,7 +68,7 @@ export const IssuePropertyQuickActions = observer((props: TIssuePropertyQuickAct
"bg-custom-background-80": isSubmitting,
}
)}
- onClick={handleDiscard}
+ onClick={onDiscard}
disabled={isSubmitting}
>
@@ -91,7 +80,7 @@ export const IssuePropertyQuickActions = observer((props: TIssuePropertyQuickAct
diff --git a/web/ee/components/issue-types/properties/root.tsx b/web/ee/components/issue-types/properties/root.tsx
index 6590029e59..e7449089c5 100644
--- a/web/ee/components/issue-types/properties/root.tsx
+++ b/web/ee/components/issue-types/properties/root.tsx
@@ -1,13 +1,14 @@
import { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
+import { useParams } from "next/navigation";
import { v4 } from "uuid";
import { Plus } from "lucide-react";
// ui
-import { Button } from "@plane/ui";
+import { Button, Loader } from "@plane/ui";
// plane web components
import { IssuePropertyList, IssueTypePropertiesEmptyState } from "@/plane-web/components/issue-types";
// plane web hooks
-import { useIssueType } from "@/plane-web/hooks/store";
+import { useIssueType, useIssueTypes } from "@/plane-web/hooks/store";
// plane web types
import { EIssuePropertyType, TIssueProperty, TCreationListModes } from "@/plane-web/types";
@@ -31,11 +32,15 @@ const defaultIssueProperty: Partial> = {
export const IssuePropertiesRoot = observer((props: TIssuePropertiesRoot) => {
const { issueTypeId } = props;
+ // router
+ const { projectId } = useParams();
// states
const [issuePropertyCreateList, setIssuePropertyCreateList] = useState([]);
// store hooks
+ const { getProjectIssuePropertiesLoader } = useIssueTypes();
const issueType = useIssueType(issueTypeId);
// derived values
+ const issuePropertiesLoader = getProjectIssuePropertiesLoader(projectId?.toString());
const properties = issueType?.properties;
// refs
const containerRef = useRef(null);
@@ -73,7 +78,14 @@ export const IssuePropertiesRoot = observer((props: TIssuePropertiesRoot) => {
- {(properties && properties?.length > 0) || issuePropertyCreateList.length > 0 ? (
+ {issuePropertiesLoader === "init-loader" ? (
+
+
+
+
+
+
+ ) : (properties && properties?.length > 0) || issuePropertyCreateList.length > 0 ? (
diff --git a/web/ee/components/issues/issue-modal/form.tsx b/web/ee/components/issues/issue-modal/form.tsx
index aafef9ac74..fa0b8cfb77 100644
--- a/web/ee/components/issues/issue-modal/form.tsx
+++ b/web/ee/components/issues/issue-modal/form.tsx
@@ -301,7 +301,12 @@ export const IssueFormRoot: FC = observer((props) => {
handleFormChange={handleFormChange}
/>
{projectId && (
-
+
)}
)}
diff --git a/web/ee/components/issues/issue-modal/issue-type-select.tsx b/web/ee/components/issues/issue-modal/issue-type-select.tsx
index be3a650d68..baeff3070b 100644
--- a/web/ee/components/issues/issue-modal/issue-type-select.tsx
+++ b/web/ee/components/issues/issue-modal/issue-type-select.tsx
@@ -16,12 +16,13 @@ import { useFlag } from "@/plane-web/hooks/store";
type TIssueTypeSelectProps = {
control: Control;
- projectId: string;
+ projectId: string | null;
disabled?: boolean;
+ handleFormChange: () => void;
};
export const IssueTypeSelect: React.FC = observer((props) => {
- const { control, projectId, disabled = false } = props;
+ const { control, projectId, disabled = false, handleFormChange } = props;
// router
const { workspaceSlug } = useParams();
// store hooks
@@ -52,6 +53,7 @@ export const IssueTypeSelect: React.FC = observer((props)
disabled={disabled}
handleIssueTypeChange={(issueTypeId) => {
onChange(issueTypeId);
+ handleFormChange();
}}
/>
)}
diff --git a/web/ee/constants/issue-properties.ts b/web/ee/constants/issue-properties.ts
index 7ce67aa0bf..21f3d93809 100644
--- a/web/ee/constants/issue-properties.ts
+++ b/web/ee/constants/issue-properties.ts
@@ -61,10 +61,10 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
},
},
property_type: EIssuePropertyType.TEXT,
- relation_type: undefined,
- is_multi: undefined,
+ relation_type: null,
+ is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: {
display_format: "single-line",
} as TIssuePropertySettingsMap[EIssuePropertyType.TEXT],
@@ -82,10 +82,10 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
},
},
property_type: EIssuePropertyType.DECIMAL,
- relation_type: undefined,
- is_multi: undefined,
+ relation_type: null,
+ is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: undefined,
},
},
@@ -101,10 +101,10 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
},
},
property_type: EIssuePropertyType.OPTION,
- relation_type: undefined,
+ relation_type: null,
is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: undefined,
},
},
@@ -120,10 +120,10 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
},
},
property_type: EIssuePropertyType.BOOLEAN,
- relation_type: undefined,
- is_multi: undefined,
+ relation_type: null,
+ is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: undefined,
},
},
@@ -139,10 +139,10 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
},
},
property_type: EIssuePropertyType.DATETIME,
- relation_type: undefined,
- is_multi: undefined,
+ relation_type: null,
+ is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: undefined,
},
},
@@ -161,7 +161,7 @@ export const ISSUE_PROPERTY_TYPE_DETAILS: Partial<
relation_type: EIssuePropertyRelationType.USER,
is_multi: false,
is_required: false,
- default_value: undefined,
+ default_value: [],
settings: undefined,
},
},
diff --git a/web/ee/helpers/issue-properties.helper.ts b/web/ee/helpers/issue-properties.helper.ts
index 344b96e04e..3ed5ba0571 100644
--- a/web/ee/helpers/issue-properties.helper.ts
+++ b/web/ee/helpers/issue-properties.helper.ts
@@ -18,14 +18,14 @@ import {
// Get the key for the issue property type based on the property type and relation type
export const getIssuePropertyTypeKey = (
issuePropertyType: EIssuePropertyType | undefined,
- issuePropertyRelationType: EIssuePropertyRelationType | undefined
+ issuePropertyRelationType: EIssuePropertyRelationType | null | undefined
) =>
`${issuePropertyType}${issuePropertyRelationType ? `_${issuePropertyRelationType}` : ""}` as TIssuePropertyTypeKeys;
// Get the display name for the issue property type based on the property type and relation type
export const getIssuePropertyTypeDisplayName = (
issuePropertyType: EIssuePropertyType | undefined,
- issuePropertyRelationType: EIssuePropertyRelationType | undefined
+ issuePropertyRelationType: EIssuePropertyRelationType | null | undefined
) => {
const propertyTypeKey = getIssuePropertyTypeKey(issuePropertyType, issuePropertyRelationType);
return ISSUE_PROPERTY_TYPE_DETAILS[propertyTypeKey]?.displayName || "--";
diff --git a/web/ee/services/issue-types/issue-properties.service.ts b/web/ee/services/issue-types/issue-properties.service.ts
index 48f2cb07b1..1651556123 100644
--- a/web/ee/services/issue-types/issue-properties.service.ts
+++ b/web/ee/services/issue-types/issue-properties.service.ts
@@ -1,7 +1,7 @@
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
// plane web types
-import { EIssuePropertyType, TIssueProperty } from "@/plane-web/types";
+import { EIssuePropertyType, TIssueProperty, TIssuePropertyOption, TIssuePropertyResponse } from "@/plane-web/types";
// services
import { APIService } from "@/services/api.service";
@@ -22,11 +22,15 @@ export class IssuePropertiesService extends APIService {
workspaceSlug: string,
projectId: string,
issueTypeId: string,
- data: Partial>
- ): Promise> {
+ data: Partial>,
+ options?: Partial[] | undefined
+ ): Promise {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-types/${issueTypeId}/issue-properties/`,
- data
+ {
+ ...data,
+ options: options ?? [],
+ }
)
.then((response) => response?.data)
.catch((error) => {
diff --git a/web/ee/services/issue-types/issue-property-options.service.ts b/web/ee/services/issue-types/issue-property-options.service.ts
index 830cff0df2..8a2b08b032 100644
--- a/web/ee/services/issue-types/issue-property-options.service.ts
+++ b/web/ee/services/issue-types/issue-property-options.service.ts
@@ -22,13 +22,11 @@ export class IssuePropertyOptionsService extends APIService {
workspaceSlug: string,
projectId: string,
issuePropertyId: string,
- data: Partial[]
- ): Promise {
+ data: Partial
+ ): Promise {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/${issuePropertyId}/options/`,
- {
- options: data,
- }
+ data
)
.then((response) => response?.data)
.catch((error) => {
diff --git a/web/ee/store/issue-types/issue-property.ts b/web/ee/store/issue-types/issue-property.ts
index 867ca4af9c..7b91882467 100644
--- a/web/ee/store/issue-types/issue-property.ts
+++ b/web/ee/store/issue-types/issue-property.ts
@@ -30,9 +30,7 @@ export interface IIssueProperty extends TIssueProp
// actions
updateProperty: (issueTypeId: string, propertyData: Partial>) => Promise;
addPropertyOption: (propertyOptionData: TIssuePropertyOption) => void;
- createPropertyOptions: (
- propertyOptions: Partial[]
- ) => Promise;
+ createPropertyOption: (propertyOption: Partial) => Promise;
deletePropertyOption: (propertyOptionId: string) => Promise;
}
@@ -45,7 +43,7 @@ export class IssueProperty implements IIssueProper
logo_props: TLogoProps | undefined = undefined;
sort_order: number | undefined = undefined;
property_type: T | undefined = undefined;
- relation_type: EIssuePropertyRelationType | undefined = undefined;
+ relation_type: EIssuePropertyRelationType | null | undefined = undefined;
is_required: boolean | undefined = undefined;
default_value: string[] | undefined = undefined;
settings: TIssuePropertySettingsMap[T] | undefined = undefined;
@@ -92,7 +90,7 @@ export class IssueProperty implements IIssueProper
// actions
updateProperty: action,
addPropertyOption: action,
- createPropertyOptions: action,
+ createPropertyOption: action,
deletePropertyOption: action,
});
@@ -211,26 +209,24 @@ export class IssueProperty implements IIssueProper
/**
* @description Create a new property option
- * @param {Partial[]} propertyOptions
+ * @param {Partial} propertyOption
*/
- createPropertyOptions = async (propertyOptions: Partial[]) => {
+ createPropertyOption = async (propertyOption: Partial) => {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return undefined;
try {
- const issuePropertyOptions = await this.propertyOptionService.create(
+ const issuePropertyOption = await this.propertyOptionService.create(
workspaceSlug,
projectId,
this.id,
- propertyOptions
+ propertyOption
);
runInAction(() => {
- issuePropertyOptions.forEach((option) => {
- this.addPropertyOption(option);
- });
+ this.addPropertyOption(issuePropertyOption);
});
- return issuePropertyOptions;
+ return issuePropertyOption;
} catch (error) {
console.error("IssueProperty -> createPropertyOption -> error", error);
throw error;
diff --git a/web/ee/store/issue-types/issue-type.ts b/web/ee/store/issue-types/issue-type.ts
index 01815fceca..29e449b962 100644
--- a/web/ee/store/issue-types/issue-type.ts
+++ b/web/ee/store/issue-types/issue-type.ts
@@ -25,7 +25,8 @@ export interface IIssueType extends TIssueType {
updateType: (issueTypeData: Partial) => Promise;
addProperty: (propertyData: TIssueProperty, propertyOptions?: TIssuePropertyOption[]) => void;
createProperty: (
- propertyData: Partial>
+ propertyData: Partial>,
+ propertyOptions?: Partial[]
) => Promise | undefined>;
deleteProperty: (propertyId: string) => Promise;
}
@@ -196,16 +197,25 @@ export class IssueType implements IIssueType {
* @description Create an issue property
* @param propertyData Issue property data
*/
- createProperty = async (propertyData: Partial>) => {
+ createProperty = async (
+ propertyData: Partial>,
+ propertyOptions?: Partial[]
+ ) => {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return;
try {
- const issueProperty = await this.issuePropertyService.create(workspaceSlug, projectId, this.id, propertyData);
+ const issueProperty = await this.issuePropertyService.create(
+ workspaceSlug,
+ projectId,
+ this.id,
+ propertyData,
+ propertyOptions
+ );
runInAction(() => {
- this.addProperty(issueProperty);
+ this.addProperty(issueProperty.property_detail, issueProperty.options);
});
- return issueProperty;
+ return issueProperty.property_detail;
} catch (error) {
console.error("IssueType.createProperty -> error", error);
throw error;
diff --git a/web/ee/types/issue-types/issue-properties.d.ts b/web/ee/types/issue-types/issue-properties.d.ts
index 2dad0e4040..2b002389cb 100644
--- a/web/ee/types/issue-types/issue-properties.d.ts
+++ b/web/ee/types/issue-types/issue-properties.d.ts
@@ -1,7 +1,7 @@
// types
import { TLogoProps } from "@plane/types";
// plane web types
-import { TIssuePropertySettingsMap } from "@/plane-web/types/issue-types";
+import { TIssuePropertyOption, TIssuePropertySettingsMap } from "@/plane-web/types/issue-types";
// Issue property types
export enum EIssuePropertyType {
@@ -27,7 +27,7 @@ type TBaseIssueProperty = {
description: string | undefined;
logo_props: TLogoProps | undefined;
sort_order: number | undefined;
- relation_type: EIssuePropertyRelationType | undefined;
+ relation_type: EIssuePropertyRelationType | null | undefined;
is_required: boolean | undefined;
default_value: string[] | undefined;
is_active: boolean | undefined;
@@ -44,3 +44,9 @@ export interface TIssueProperty extends TBaseIssue
property_type: T | undefined;
settings: TIssuePropertySettingsMap[T] | undefined;
}
+
+// Issue property response
+export interface TIssuePropertyResponse {
+ property_detail: TIssueProperty;
+ options: TIssuePropertyOption[];
+}
diff --git a/web/ee/types/issue-types/issue-property-configurations.d.ts b/web/ee/types/issue-types/issue-property-configurations.d.ts
index 18e6a1aa33..fc06b910ca 100644
--- a/web/ee/types/issue-types/issue-property-configurations.d.ts
+++ b/web/ee/types/issue-types/issue-property-configurations.d.ts
@@ -20,11 +20,11 @@ export type TissuePropertyTypeDetails = {
dataToUpdate: {
logo_props: TLogoProps;
property_type: EIssuePropertyType;
- relation_type: EIssuePropertyRelationType | undefined;
- is_multi: boolean | undefined;
- is_required: boolean | undefined;
- default_value: string[] | undefined;
- settings: TIssuePropertySettingsMap[T] | undefined;
+ relation_type: EIssuePropertyRelationType | null;
+ is_multi: boolean;
+ is_required: boolean;
+ default_value: string[];
+ settings: TIssuePropertySettingsMap[T];
};
};