diff --git a/apiserver/plane/app/views/issue/activity.py b/apiserver/plane/app/views/issue/activity.py index f46cb87f39..685486cc0a 100644 --- a/apiserver/plane/app/views/issue/activity.py +++ b/apiserver/plane/app/views/issue/activity.py @@ -29,6 +29,8 @@ from plane.db.models import ( IssueComment, CommentReaction, ) +from plane.payment.flags.flag_decorator import check_workspace_feature_flag +from plane.payment.flags.flag import FeatureFlag class IssueActivityEndpoint(BaseAPIView): @@ -61,6 +63,13 @@ class IssueActivityEndpoint(BaseAPIView): .filter(**filters) .select_related("actor", "workspace", "issue", "project") ).order_by("created_at") + + if not check_workspace_feature_flag( + feature_key=FeatureFlag.ISSUE_TYPE_SETTINGS, + slug=slug, + ): + issue_activities = issue_activities.filter(field="type") + issue_comments = ( IssueComment.objects.filter(issue_id=issue_id) .filter( diff --git a/apiserver/plane/bgtasks/issue_activities_task.py b/apiserver/plane/bgtasks/issue_activities_task.py index ae112fa63e..43a790f58a 100644 --- a/apiserver/plane/bgtasks/issue_activities_task.py +++ b/apiserver/plane/bgtasks/issue_activities_task.py @@ -27,6 +27,7 @@ from plane.db.models import ( State, User, EstimatePoint, + IssueType, ) from plane.settings.redis import redis_instance from plane.utils.exception_logger import log_exception @@ -570,6 +571,51 @@ def track_closed_to( ) +def track_type( + requested_data, + current_instance, + issue_id, + project_id, + workspace_id, + actor_id, + issue_activities, + epoch, +): + + new_type_id = requested_data.get("type_id") + old_type_id = current_instance.get("type_id") + + if new_type_id != old_type_id: + verb = "updated" if new_type_id else "deleted" + old_type_id = ( + IssueType.objects.filter(pk=old_type_id).first() + if old_type_id + else None + ) + new_type_id = ( + IssueType.objects.filter(pk=new_type_id).first() + if new_type_id + else None + ) + + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor_id=actor_id, + verb=verb, + old_value=old_type_id.name if old_type_id else None, + new_value=new_type_id.name if new_type_id else None, + field="type", + project_id=project_id, + workspace_id=workspace_id, + comment="", + old_identifier=old_type_id.id if old_type_id else None, + new_identifier=new_type_id.id if new_type_id else None, + epoch=epoch, + ) + ) + + def create_issue_activity( requested_data, current_instance, @@ -632,6 +678,7 @@ def update_issue_activity( "estimate_point": track_estimate_points, "archived_at": track_archive_at, "closed_to": track_closed_to, + "type_id": track_type, } requested_data = ( @@ -1700,16 +1747,12 @@ def issue_activity( event=( "issue_comment" if activity.field == "comment" - else "inbox_issue" - if inbox - else "issue" + else "inbox_issue" if inbox else "issue" ), event_id=( activity.issue_comment_id if activity.field == "comment" - else inbox - if inbox - else activity.issue_id + else inbox if inbox else activity.issue_id ), verb=activity.verb, field=( @@ -1755,4 +1798,3 @@ def issue_activity( except Exception as e: log_exception(e) return - diff --git a/web/ee/components/issue-types/common/issue-type-logo.tsx b/web/ee/components/issue-types/common/issue-type-logo.tsx index 52bd11791a..616c01eca8 100644 --- a/web/ee/components/issue-types/common/issue-type-logo.tsx +++ b/web/ee/components/issue-types/common/issue-type-logo.tsx @@ -6,7 +6,7 @@ import { LayersIcon, LUCIDE_ICONS_LIST } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; -export type TIssueTypeLogoSize = "sm" | "md" | "lg" | "xl"; +export type TIssueTypeLogoSize = "xs" | "sm" | "md" | "lg" | "xl"; type Props = { icon_props: TLogoProps["icon"]; @@ -16,6 +16,7 @@ type Props = { }; const iconSizeMap = { + xs: 11, sm: 12.5, md: 14.5, lg: 18, @@ -23,6 +24,7 @@ const iconSizeMap = { }; const containerSizeMap = { + xs: 15.5, sm: 18, md: 21, lg: 25.5, diff --git a/web/ee/components/issue-types/dropdowns/issue-type.tsx b/web/ee/components/issue-types/dropdowns/issue-type.tsx index d0e1df4128..6eb96a28b0 100644 --- a/web/ee/components/issue-types/dropdowns/issue-type.tsx +++ b/web/ee/components/issue-types/dropdowns/issue-type.tsx @@ -18,18 +18,16 @@ export const IssueTypeDropdown = observer((props: TIssueTypeDropdownProps) => { // store hooks const { loader: issueTypesLoader, getProjectIssueTypes } = useIssueTypes(); // derived values - const issueTypes = getProjectIssueTypes(projectId, true); + const allIssueTypes = getProjectIssueTypes(projectId, false); + const activeIssueTypes = getProjectIssueTypes(projectId, true); // Can be used with CustomSearchSelect as well - const issueTypeOptions = Object.entries(issueTypes).map(([issueTypeId, issueTypeDetail]) => ({ + const issueTypeOptions = Object.entries(activeIssueTypes).map(([issueTypeId, issueTypeDetail]) => ({ value: issueTypeId, query: issueTypeDetail.name ?? "", content: (
- +
{issueTypeDetail.name}
), @@ -49,10 +47,10 @@ export const IssueTypeDropdown = observer((props: TIssueTypeDropdownProps) => { label={
-
{issueTypes[issueTypeId]?.name}
+
{allIssueTypes[issueTypeId]?.name}
} options={issueTypeOptions} diff --git a/web/ee/components/issues/issue-details/issue-type-activity.tsx b/web/ee/components/issues/issue-details/issue-type-activity.tsx index 1e2cb90c25..8d06d30c65 100644 --- a/web/ee/components/issues/issue-details/issue-type-activity.tsx +++ b/web/ee/components/issues/issue-details/issue-type-activity.tsx @@ -1 +1,79 @@ -export * from "ce/components/issues/issue-details"; +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +// icons +import { ArrowRightLeft } from "lucide-react"; +// ce imports +import { IssueTypeActivity as BaseIssueTypeActivity, TIssueTypeActivity } from "@/ce/components/issues"; +// components +import { + IssueActivityBlockComponent, + IssueLink, +} from "@/components/issues/issue-detail/issue-activity/activity/actions"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useIssueDetail } from "@/hooks/store"; +// plane web components +import { IssueTypeLogo } from "@/plane-web/components/issue-types"; +// plane web hooks +import { useIssueTypes } from "@/plane-web/hooks/store"; + +type TIssueTypeDetail = { + issueTypeId: string; + className?: string; +}; + +const IssueTypeDetail: FC = observer((props) => { + const { issueTypeId, className = "" } = props; + // hooks + const { getIssueTypeById } = useIssueTypes(); + // derived values + const issueTypeDetail = getIssueTypeById(issueTypeId); + + return ( + + + {issueTypeDetail?.name} + + ); +}); + +export const IssueTypeActivity: FC = observer((props) => { + const { activityId, showIssue = false, ends } = props; + // router + const { workspaceSlug } = useParams(); + // hooks + const { + activity: { getActivityById }, + } = useIssueDetail(); + const { isIssueTypeEnabledForProject } = useIssueTypes(); + // derived values + const activity = getActivityById(activityId); + if (!activity) return <>; + + const isIssueTypeDisplayEnabled = + workspaceSlug && activity?.project + ? isIssueTypeEnabledForProject(workspaceSlug?.toString(), activity?.project, "ISSUE_TYPE_DISPLAY") + : false; + + if (!isIssueTypeDisplayEnabled) return ; + + return ( + } + activityId={activityId} + ends={ends} + > + + changed issue type to{" "} + {activity.new_identifier && } + from {activity.old_identifier && } + {showIssue ? ` for ` : ``} + {showIssue && }. + + + ); +}); diff --git a/web/ee/components/issues/issue-details/issue-type-switcher.tsx b/web/ee/components/issues/issue-details/issue-type-switcher.tsx index 3b9c704a8a..b54d973e0a 100644 --- a/web/ee/components/issues/issue-details/issue-type-switcher.tsx +++ b/web/ee/components/issues/issue-details/issue-type-switcher.tsx @@ -28,6 +28,7 @@ export const IssueTypeSwitcher: React.FC = observer((pr const { issue: { getIssueById }, toggleCreateIssueModal, + fetchActivities, } = useIssueDetail(); const { isIssueTypeEnabledForProject } = useIssueTypes(); // derived values @@ -54,6 +55,11 @@ export const IssueTypeSwitcher: React.FC = observer((pr <> { + if (workspaceSlug && issue.project_id) { + await fetchActivities(workspaceSlug.toString(), issue.project_id, issueId); + } + }} onClose={() => { setIsCreateUpdateIssueModalOpen(false); toggleCreateIssueModal(false); @@ -72,7 +78,7 @@ export const IssueTypeSwitcher: React.FC = observer((pr {!disabled && ( - Switch Issue Type + Switch issue type )} diff --git a/web/ee/components/issues/issue-modal/provider.tsx b/web/ee/components/issues/issue-modal/provider.tsx index 125c89ee48..406e8c191f 100644 --- a/web/ee/components/issues/issue-modal/provider.tsx +++ b/web/ee/components/issues/issue-modal/provider.tsx @@ -15,7 +15,7 @@ import { useIssueDetail } from "@/hooks/store"; // plane web helpers import { getPropertiesDefaultValues } from "@/plane-web/helpers/issue-properties.helper"; // plane web hooks -import { useIssueTypes } from "@/plane-web/hooks/store"; +import { useIssuePropertiesActivity, useIssueTypes } from "@/plane-web/hooks/store"; // plane web services import { IssuePropertyValuesService } from "@/plane-web/services/issue-types"; // plane web types @@ -44,7 +44,7 @@ export const IssueModalProvider = observer((props: TIssueModalProviderProps) => getProjectIssueTypes, getProjectDefaultIssueType, } = useIssueTypes(); - + const { fetchPropertyActivities } = useIssuePropertiesActivity(); // helpers const getIssueTypeIdOnProjectChange = (projectId: string) => { // get active issue types for the project @@ -135,7 +135,10 @@ export const IssueModalProvider = observer((props: TIssueModalProviderProps) => await issuePropertyValuesService .create(workspaceSlug, projectId, issueId, filteredIssuePropertyValues) .then(() => { + // mutate issue property values mutate(`ISSUE_PROPERTY_VALUES_${workspaceSlug}_${projectId}_${issueId}_${isIssueTypeDisplayEnabled}`); + // fetch property activities + fetchPropertyActivities(workspaceSlug, projectId, issueId); // reset issue property values setIssuePropertyValues({ ...getPropertiesDefaultValues(issueType?.activeProperties ?? []),