From 5ac4cad23b654c68dbbf22179f47bbd3bc517692 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 4 Nov 2024 15:36:15 +0530 Subject: [PATCH 1/5] fix relation creation and removal for Issue relations --- apiserver/plane/app/views/issue/relation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/app/views/issue/relation.py b/apiserver/plane/app/views/issue/relation.py index f169a65bd4a..67bfeff1f34 100644 --- a/apiserver/plane/app/views/issue/relation.py +++ b/apiserver/plane/app/views/issue/relation.py @@ -318,7 +318,7 @@ def create(self, request, slug, project_id, issue_id): origin=request.META.get("HTTP_ORIGIN"), ) - if relation_type == "blocking": + if relation_type in ["blocking", "start_after", "finish_after"]: return Response( RelatedIssueSerializer(issue_relation, many=True).data, status=status.HTTP_201_CREATED, @@ -333,7 +333,7 @@ def remove_relation(self, request, slug, project_id, issue_id): relation_type = request.data.get("relation_type", None) related_issue = request.data.get("related_issue", None) - if relation_type == "blocking": + if relation_type in ["blocking", "start_after", "finish_after"]: issue_relation = IssueRelation.objects.get( workspace__slug=slug, project_id=project_id, From d48128b14b093503bcd3a50f193bcecc9129929b Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 4 Nov 2024 15:39:04 +0530 Subject: [PATCH 2/5] fix Scrolling to block when the block is beyond current chart's limits --- .../gantt-chart/blocks/block-row-list.tsx | 14 +++++++-- .../gantt-chart/blocks/block-row.tsx | 15 +++------ .../gantt-chart/chart/main-content.tsx | 31 ++++++++++++++++++- .../components/gantt-chart/chart/root.tsx | 18 ++++++++--- .../gantt-chart/views/month-view.ts | 20 ++++++------ .../gantt-chart/views/quarter-view.ts | 16 +++++----- .../components/gantt-chart/views/week-view.ts | 20 ++++++------ 7 files changed, 90 insertions(+), 44 deletions(-) diff --git a/web/core/components/gantt-chart/blocks/block-row-list.tsx b/web/core/components/gantt-chart/blocks/block-row-list.tsx index c64dc81dfa0..183f626570d 100644 --- a/web/core/components/gantt-chart/blocks/block-row-list.tsx +++ b/web/core/components/gantt-chart/blocks/block-row-list.tsx @@ -5,12 +5,13 @@ import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; // types import { BLOCK_HEIGHT } from "../constants"; -import { IBlockUpdateData } from "../types"; +import { IBlockUpdateData, IGanttBlock } from "../types"; import { BlockRow } from "./block-row"; export type GanttChartBlocksProps = { blockIds: string[]; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; + handleScrollToBlock: (block: IGanttBlock) => void; enableAddBlock: boolean | ((blockId: string) => boolean); showAllBlocks: boolean; selectionHelpers: TSelectionHelper; @@ -18,7 +19,15 @@ export type GanttChartBlocksProps = { }; export const GanttChartRowList: FC = (props) => { - const { blockIds, blockUpdateHandler, enableAddBlock, showAllBlocks, selectionHelpers, ganttContainerRef } = props; + const { + blockIds, + blockUpdateHandler, + handleScrollToBlock, + enableAddBlock, + showAllBlocks, + selectionHelpers, + ganttContainerRef, + } = props; return (
@@ -37,6 +46,7 @@ export const GanttChartRowList: FC = (props) => { blockId={blockId} showAllBlocks={showAllBlocks} blockUpdateHandler={blockUpdateHandler} + handleScrollToBlock={handleScrollToBlock} enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock} selectionHelpers={selectionHelpers} ganttContainerRef={ganttContainerRef} diff --git a/web/core/components/gantt-chart/blocks/block-row.tsx b/web/core/components/gantt-chart/blocks/block-row.tsx index 004ca3012d4..1bcec7f389a 100644 --- a/web/core/components/gantt-chart/blocks/block-row.tsx +++ b/web/core/components/gantt-chart/blocks/block-row.tsx @@ -10,19 +10,20 @@ import { useTimeLineChartStore } from "@/hooks/use-timeline-chart"; // import { BLOCK_HEIGHT, SIDEBAR_WIDTH } from "../constants"; import { ChartAddBlock } from "../helpers"; -import { IBlockUpdateData } from "../types"; +import { IBlockUpdateData, IGanttBlock } from "../types"; type Props = { blockId: string; showAllBlocks: boolean; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; + handleScrollToBlock: (block: IGanttBlock) => void; enableAddBlock: boolean; selectionHelpers: TSelectionHelper; ganttContainerRef: React.RefObject; }; export const BlockRow: React.FC = observer((props) => { - const { blockId, showAllBlocks, blockUpdateHandler, enableAddBlock, selectionHelpers } = props; + const { blockId, showAllBlocks, blockUpdateHandler, handleScrollToBlock, enableAddBlock, selectionHelpers } = props; // states const [isHidden, setIsHidden] = useState(false); const [isBlockHiddenOnLeft, setIsBlockHiddenOnLeft] = useState(false); @@ -67,14 +68,6 @@ export const BlockRow: React.FC = observer((props) => { // hide the block if it doesn't have start and target dates and showAllBlocks is false if (!block || !block.data || (!showAllBlocks && !(block.start_date && block.target_date))) return null; - // scroll to a hidden block - const handleScrollToBlock = () => { - const scrollContainer = document.querySelector("#gantt-container") as HTMLDivElement; - if (!scrollContainer || !block.position) return; - // update container's scroll position to the block's position - scrollContainer.scrollLeft = block.position.marginLeft - 4; - }; - const isBlockVisibleOnChart = block.start_date && block.target_date; const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id); const isBlockFocused = selectionHelpers.getIsEntityActive(block.id); @@ -106,7 +99,7 @@ export const BlockRow: React.FC = observer((props) => { style={{ left: `${SIDEBAR_WIDTH + 4}px`, }} - onClick={handleScrollToBlock} + onClick={() => handleScrollToBlock(block)} > React.ReactNode; title: string; - updateCurrentViewRenderPayload: (direction: "left" | "right", currentView: TGanttViews) => void; + updateCurrentViewRenderPayload: ( + direction: "left" | "right", + currentView: TGanttViews, + targetDate?: Date + ) => ChartDataType | undefined; quickAdd?: React.JSX.Element | undefined; }; @@ -105,6 +113,26 @@ export const GanttChartMainContent: React.FC = observer((props) => { if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView); }; + const handleScrollToBlock = (block: IGanttBlock) => { + const scrollContainer = ganttContainerRef.current as HTMLDivElement; + const scrollToDate = getDate(block.start_date); + let chartData; + + if (!scrollContainer || !currentViewData || !scrollToDate) return; + + if (scrollToDate.getTime() < currentViewData.data.startDate.getTime()) { + chartData = updateCurrentViewRenderPayload("left", currentView, scrollToDate); + } else if (scrollToDate.getTime() > currentViewData.data.endDate.getTime()) { + chartData = updateCurrentViewRenderPayload("right", currentView, scrollToDate); + } + // update container's scroll position to the block's position + const updatedPosition = getItemPositionWidth(chartData ?? currentViewData, block); + + setTimeout(() => { + if (updatedPosition) scrollContainer.scrollLeft = updatedPosition.marginLeft - 4; + }); + }; + const CHART_VIEW_COMPONENTS: { [key in TGanttViews]: React.FC; } = { @@ -166,6 +194,7 @@ export const GanttChartMainContent: React.FC = observer((props) => { = observer((props) => { updateAllBlocksOnChartChangeWhileDragging, } = useTimeLineChartStore(); - const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => { + const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews, targetDate?: Date) => { const selectedCurrentView: TGanttViews = view; const selectedCurrentViewData: ChartDataType | undefined = selectedCurrentView && selectedCurrentView === currentViewData?.key @@ -96,7 +96,7 @@ export const ChartViewRoot: FC = observer((props) => { if (selectedCurrentViewData === undefined) return; const currentViewHelpers = timelineViewHelpers[selectedCurrentView]; - const currentRender = currentViewHelpers.generateChart(selectedCurrentViewData, side); + const currentRender = currentViewHelpers.generateChart(selectedCurrentViewData, side, targetDate); const mergeRenderPayloads = currentViewHelpers.mergeRenderPayloads as ( a: IWeekBlock[] | IMonthView | IMonthBlock[], b: IWeekBlock[] | IMonthView | IMonthBlock[] @@ -109,7 +109,8 @@ export const ChartViewRoot: FC = observer((props) => { if (side === "left") { updateCurrentView(selectedCurrentView); updateRenderView(mergeRenderPayloads(currentRender.payload, renderView)); - updatingCurrentLeftScrollPosition(currentRender.scrollWidth); + updateItemsContainerWidth(currentRender.scrollWidth); + if (!targetDate) updateCurrentLeftScrollPosition(currentRender.scrollWidth); updateAllBlocksOnChartChangeWhileDragging(currentRender.scrollWidth); setItemsContainerWidth(itemsContainerWidth + currentRender.scrollWidth); } else if (side === "right") { @@ -125,6 +126,8 @@ export const ChartViewRoot: FC = observer((props) => { }, 50); } } + + return currentRender.state; }; const handleToday = () => updateCurrentViewRenderPayload(null, currentView); @@ -135,12 +138,17 @@ export const ChartViewRoot: FC = observer((props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const updatingCurrentLeftScrollPosition = (width: number) => { + const updateItemsContainerWidth = (width: number) => { + const scrollContainer = document.querySelector("#gantt-container") as HTMLDivElement; + if (!scrollContainer) return; + setItemsContainerWidth(width + scrollContainer?.scrollLeft); + }; + + const updateCurrentLeftScrollPosition = (width: number) => { const scrollContainer = document.querySelector("#gantt-container") as HTMLDivElement; if (!scrollContainer) return; scrollContainer.scrollLeft = width + scrollContainer?.scrollLeft; - setItemsContainerWidth(width + scrollContainer?.scrollLeft); }; const handleScrollToCurrentSelectedDate = (currentState: ChartDataType, date: Date) => { diff --git a/web/core/components/gantt-chart/views/month-view.ts b/web/core/components/gantt-chart/views/month-view.ts index 2e66729778f..41acf14d5d7 100644 --- a/web/core/components/gantt-chart/views/month-view.ts +++ b/web/core/components/gantt-chart/views/month-view.ts @@ -30,7 +30,7 @@ export interface IMonthView { * @param side * @returns */ -const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "right") => { +const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => { let renderState = cloneDeep(monthPayload); const range: number = renderState.data.approxFilterRange || 6; @@ -63,15 +63,16 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | " } // When side is left, generate more months on the left side of the start date else if (side === "left") { - const currentDate = renderState.data.startDate; + const chartStartDate = renderState.data.startDate; + const currentDate = targetDate ? targetDate : chartStartDate; - minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate()); - plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1); + minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1); + plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate); startDate = filteredDates.weeks[0]?.startDate; - endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1); + endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); renderState = { ...renderState, data: { ...renderState.data, startDate }, @@ -79,14 +80,15 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | " } // When side is right, generate more months on the right side of the end date else if (side === "right") { - const currentDate = renderState.data.endDate; + const chartEndDate = renderState.data.endDate; + const currentDate = targetDate ? targetDate : chartEndDate; - minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1); - plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate()); + minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); + plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1); if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate); - startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1); + startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate; renderState = { ...renderState, diff --git a/web/core/components/gantt-chart/views/quarter-view.ts b/web/core/components/gantt-chart/views/quarter-view.ts index 4f7ac3446b9..a584a26e35f 100644 --- a/web/core/components/gantt-chart/views/quarter-view.ts +++ b/web/core/components/gantt-chart/views/quarter-view.ts @@ -19,7 +19,7 @@ export interface IQuarterMonthBlock { * @param side * @returns */ -const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" | "right") => { +const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => { let renderState = quarterPayload; const range: number = renderState.data.approxFilterRange || 12; @@ -55,16 +55,17 @@ const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" } // When side is left, generate more months on the left side of the start date else if (side === "left") { - const currentDate = renderState.data.startDate; + const chartStartDate = renderState.data.startDate; + const currentDate = targetDate ? targetDate : chartStartDate; minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range / 2, 1); - plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1); + plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth() - 1, 1); if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate); const startMonthBlock = filteredDates[0]; startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1); - endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1); + endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); renderState = { ...renderState, data: { ...renderState.data, startDate }, @@ -72,15 +73,16 @@ const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" } // When side is right, generate more months on the right side of the end date else if (side === "right") { - const currentDate = renderState.data.endDate; + const chartEndDate = renderState.data.endDate; + const currentDate = targetDate ? targetDate : chartEndDate; - minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); + minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth() + 1, 1); plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range / 2, 1); if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate); const endMonthBlock = filteredDates[filteredDates.length - 1]; - startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1); + startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0); renderState = { ...renderState, diff --git a/web/core/components/gantt-chart/views/week-view.ts b/web/core/components/gantt-chart/views/week-view.ts index 03a0da1b3c7..65915274c53 100644 --- a/web/core/components/gantt-chart/views/week-view.ts +++ b/web/core/components/gantt-chart/views/week-view.ts @@ -38,7 +38,7 @@ export interface IWeekBlock { * @param side * @returns */ -const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "right") => { +const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => { let renderState = weekPayload; const range: number = renderState.data.approxFilterRange || 6; @@ -71,15 +71,16 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri } // When side is left, generate more weeks on the left side of the start date else if (side === "left") { - const currentDate = renderState.data.startDate; + const chartStartDate = renderState.data.startDate; + const currentDate = targetDate ? targetDate : chartStartDate; - minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate()); - plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1); + minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1); + plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate); startDate = filteredDates[0].startDate; - endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1); + endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1); renderState = { ...renderState, data: { ...renderState.data, startDate }, @@ -87,14 +88,15 @@ const generateWeekChart = (weekPayload: ChartDataType, side: null | "left" | "ri } // When side is right, generate more weeks on the right side of the end date else if (side === "right") { - const currentDate = renderState.data.endDate; + const chartEndDate = renderState.data.endDate; + const currentDate = targetDate ? targetDate : chartEndDate; - minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1); - plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate()); + minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); + plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1); if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate); - startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1); + startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1); endDate = filteredDates[filteredDates.length - 1].endDate; renderState = { ...renderState, From e99bf4fd5ff2eadec6d5ef4a0ef553a036ed0495 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 4 Nov 2024 15:39:34 +0530 Subject: [PATCH 3/5] fix dark mode for timeline layout --- web/core/components/gantt-chart/chart/views/month.tsx | 4 ++-- web/core/components/gantt-chart/chart/views/quarter.tsx | 4 ++-- web/core/components/gantt-chart/chart/views/week.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/core/components/gantt-chart/chart/views/month.tsx b/web/core/components/gantt-chart/chart/views/month.tsx index 583d271de6c..143d2690c57 100644 --- a/web/core/components/gantt-chart/chart/views/month.tsx +++ b/web/core/components/gantt-chart/chart/views/month.tsx @@ -67,7 +67,7 @@ export const MonthChartView: FC = observer(() => { className={cn( "flex flex-shrink-0 py-1 px-2 text-center capitalize justify-between outline-[0.25px] outline outline-custom-border-200", { - "bg-custom-primary-10": weekBlock.today, + "bg-custom-primary-100/20": weekBlock.today, } )} style={{ width: `${currentViewData?.data.dayWidth * 7}px` }} @@ -92,7 +92,7 @@ export const MonthChartView: FC = observer(() => {
diff --git a/web/core/components/gantt-chart/chart/views/quarter.tsx b/web/core/components/gantt-chart/chart/views/quarter.tsx index 1bd3e180709..6252081e332 100644 --- a/web/core/components/gantt-chart/chart/views/quarter.tsx +++ b/web/core/components/gantt-chart/chart/views/quarter.tsx @@ -56,7 +56,7 @@ export const QuarterChartView: FC = observer(() => { className={cn( "flex flex-shrink-0 text-center capitalize justify-center outline-[0.25px] outline outline-custom-border-200", { - "bg-custom-primary-10": monthBlock.today, + "bg-custom-primary-100/20": monthBlock.today, } )} style={{ width: `${currentViewData?.data.dayWidth * monthBlock.days}px` }} @@ -80,7 +80,7 @@ export const QuarterChartView: FC = observer(() => {
diff --git a/web/core/components/gantt-chart/chart/views/week.tsx b/web/core/components/gantt-chart/chart/views/week.tsx index 88cb6b04309..3532a25c21d 100644 --- a/web/core/components/gantt-chart/chart/views/week.tsx +++ b/web/core/components/gantt-chart/chart/views/week.tsx @@ -49,7 +49,7 @@ export const WeekChartView: FC = observer(() => { className={cn( "flex flex-shrink-0 p-1 text-center capitalize justify-between outline-[0.25px] outline outline-custom-border-200", { - "bg-custom-primary-10": weekDay.today, + "bg-custom-primary-100/20": weekDay.today, } )} style={{ width: `${currentViewData?.data.dayWidth}px` }} @@ -71,12 +71,12 @@ export const WeekChartView: FC = observer(() => {
{/** Day Columns */} -
+
{block?.children?.map((weekDay, index) => (
From ac3dd68a0d965cafd4af98eff2bc15615f4164c8 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 4 Nov 2024 15:42:51 +0530 Subject: [PATCH 4/5] use a hook to get the current relations available in the environment, instead of directly importing it --- .../issue-detail-widget-collapsibles.tsx | 5 ++- .../relations/content.tsx | 3 +- .../relations/quick-action-button.tsx | 38 +++++++++++-------- .../issue-detail-widgets/relations/title.tsx | 5 ++- .../issues/issue-detail/relation-select.tsx | 11 ++++-- .../issue/issue-details/relation.store.ts | 24 +++++++----- web/helpers/array.helper.ts | 12 ++++++ 7 files changed, 67 insertions(+), 31 deletions(-) diff --git a/web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx b/web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx index 124c1738090..8537020b786 100644 --- a/web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx +++ b/web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx @@ -10,6 +10,8 @@ import { } from "@/components/issues/issue-detail-widgets"; // hooks import { useIssueDetail } from "@/hooks/store"; +// Plane-web +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; type Props = { workspaceSlug: string; @@ -31,7 +33,8 @@ export const IssueDetailWidgetCollapsibles: FC = observer((props) => { // derived values const issue = getIssueById(issueId); const subIssues = subIssuesByIssueId(issueId); - const issueRelationsCount = getRelationCountByIssueId(issueId); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); + const issueRelationsCount = getRelationCountByIssueId(issueId, ISSUE_RELATION_OPTIONS); // render conditions const shouldRenderSubIssues = !!subIssues && subIssues.length > 0; diff --git a/web/core/components/issues/issue-detail-widgets/relations/content.tsx b/web/core/components/issues/issue-detail-widgets/relations/content.tsx index 672faabb9d1..79be48e4f00 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/content.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/content.tsx @@ -10,7 +10,7 @@ import { CreateUpdateIssueModal } from "@/components/issues/issue-modal"; // hooks import { useIssueDetail } from "@/hooks/store"; // Plane-web -import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations"; +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; import { TIssueRelationTypes } from "@/plane-web/types"; // helper import { useRelationOperations } from "./helper"; @@ -63,6 +63,7 @@ export const RelationsCollapsibleContent: FC = observer((props) => { // derived values const relations = getRelationsByIssueId(issueId); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); const handleIssueCrudState = (key: "update" | "delete", _issueId: string | null, issue: TIssue | null = null) => { setIssueCrudState({ diff --git a/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx b/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx index 570ec010352..dff072e7dca 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx @@ -6,7 +6,7 @@ import { CustomMenu } from "@plane/ui"; // hooks import { useIssueDetail } from "@/hooks/store"; // Plane-web -import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations"; +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; import { TIssueRelationTypes } from "@/plane-web/types"; type Props = { @@ -20,6 +20,8 @@ export const RelationActionButton: FC = observer((props) => { // store hooks const { toggleRelationModal, setRelationKey } = useIssueDetail(); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); + // handlers const handleOnClick = (relationKey: TIssueRelationTypes) => { setRelationKey(relationKey); @@ -37,21 +39,25 @@ export const RelationActionButton: FC = observer((props) => { maxHeight="lg" closeOnSelect > - {Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => ( - { - e.preventDefault(); - e.stopPropagation(); - handleOnClick(item.key as TIssueRelationTypes); - }} - > -
- {item.icon(12)} - {item.label} -
-
- ))} + {Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => { + if (!item) return <>; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + handleOnClick(item.key as TIssueRelationTypes); + }} + > +
+ {item.icon(12)} + {item.label} +
+
+ ); + })} ); }); diff --git a/web/core/components/issues/issue-detail-widgets/relations/title.tsx b/web/core/components/issues/issue-detail-widgets/relations/title.tsx index a288146d16d..2c3854beddd 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/title.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/title.tsx @@ -6,6 +6,8 @@ import { CollapsibleButton } from "@plane/ui"; import { RelationActionButton } from "@/components/issues/issue-detail-widgets"; // hooks import { useIssueDetail } from "@/hooks/store"; +// Plane-web +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; type Props = { isOpen: boolean; @@ -20,8 +22,9 @@ export const RelationsCollapsibleTitle: FC = observer((props) => { relation: { getRelationCountByIssueId }, } = useIssueDetail(); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); // derived values - const relationsCount = getRelationCountByIssueId(issueId); + const relationsCount = getRelationCountByIssueId(issueId, ISSUE_RELATION_OPTIONS); // indicator element const indicatorElement = useMemo( diff --git a/web/core/components/issues/issue-detail/relation-select.tsx b/web/core/components/issues/issue-detail/relation-select.tsx index a1a1f09d211..1796f5454da 100644 --- a/web/core/components/issues/issue-detail/relation-select.tsx +++ b/web/core/components/issues/issue-detail/relation-select.tsx @@ -15,8 +15,10 @@ import { cn } from "@/helpers/common.helper"; import { useIssueDetail, useIssues, useProject } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // Plane-web -import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations"; +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; import { TIssueRelationTypes } from "@/plane-web/types"; +// +import { TRelationObject } from "../issue-detail-widgets"; type TIssueRelationSelect = { className?: string; @@ -41,6 +43,7 @@ export const IssueRelationSelect: React.FC = observer((pro const { issueMap } = useIssues(); const { isMobile } = usePlatformOS(); const relationIssueIds = getRelationByIssueIdRelationType(issueId, relationKey); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); const onSubmit = async (data: ISearchIssueResponse[]) => { if (data.length === 0) { @@ -68,6 +71,8 @@ export const IssueRelationSelect: React.FC = observer((pro const isRelationKeyModalActive = isRelationModalOpen?.relationType === relationKey && isRelationModalOpen?.issueId === issueId; + const currRelationOption: TRelationObject | undefined = ISSUE_RELATION_OPTIONS[relationKey]; + return ( <> = observer((pro return (
= observer((pro })}
) : ( - {ISSUE_RELATION_OPTIONS[relationKey].placeholder} + {currRelationOption?.placeholder} )} {!disabled && ( TIssueRelationIdMap | undefined; - getRelationCountByIssueId: (issueId: string) => number; + getRelationCountByIssueId: ( + issueId: string, + ISSUE_RELATION_OPTIONS: { [key in TIssueRelationTypes]?: TRelationObject } + ) => number; getRelationByIssueIdRelationType: (issueId: string, relationType: TIssueRelationTypes) => string[] | undefined; extractRelationsFromIssues: (issues: TIssue[]) => void; createCurrentRelation: (issueId: string, relationType: TIssueRelationTypes, relatedIssueId: string) => Promise; @@ -85,15 +89,17 @@ export class IssueRelationStore implements IIssueRelationStore { return this.relationMap?.[issueId] ?? undefined; }; - getRelationCountByIssueId = computedFn((issueId: string) => { - const issueRelations = this.getRelationsByIssueId(issueId); + getRelationCountByIssueId = computedFn( + (issueId: string, ISSUE_RELATION_OPTIONS: { [key in TIssueRelationTypes]?: TRelationObject }) => { + const issueRelations = this.getRelationsByIssueId(issueId); - const issueRelationKeys = (Object.keys(issueRelations ?? {}) as TIssueRelationTypes[]).filter( - (relationKey) => !!ISSUE_RELATION_OPTIONS[relationKey] - ); + const issueRelationKeys = (Object.keys(issueRelations ?? {}) as TIssueRelationTypes[]).filter( + (relationKey) => !!ISSUE_RELATION_OPTIONS[relationKey] + ); - return issueRelationKeys.reduce((acc, curr) => acc + (issueRelations?.[curr]?.length ?? 0), 0); - }); + return issueRelationKeys.reduce((acc, curr) => acc + (issueRelations?.[curr]?.length ?? 0), 0); + } + ); getRelationByIssueIdRelationType = (issueId: string, relationType: TIssueRelationTypes) => { if (!issueId || !relationType) return undefined; diff --git a/web/helpers/array.helper.ts b/web/helpers/array.helper.ts index c2799bfa202..4efeb352371 100644 --- a/web/helpers/array.helper.ts +++ b/web/helpers/array.helper.ts @@ -1,3 +1,4 @@ +import isEmpty from "lodash/isEmpty"; import { IIssueLabel, IIssueLabelTree } from "@plane/types"; export const groupBy = (array: any[], key: string) => { @@ -90,3 +91,14 @@ export const buildTree = (array: IIssueLabel[], parent = null) => { return tree; }; + +/** + * Returns Valid keys from object whose value is not falsy + * @param obj + * @returns + */ +export const getValidKeysFromObject = (obj: any) => { + if (!obj || isEmpty(obj) || typeof obj !== "object" || Array.isArray(obj)) return []; + + return Object.keys(obj).filter((key) => !!obj[key]); +}; From 623c2b061fa87a42e26415115e513cb2424858cf Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 4 Nov 2024 15:44:04 +0530 Subject: [PATCH 5/5] Update relation activity for all the relations --- web/ce/components/relations/activity.ts | 20 +++++++++++++++ web/ce/components/relations/index.tsx | 4 +++ .../activity/actions/relation.tsx | 25 ++++--------------- .../issue-activity/activity/activity-list.tsx | 7 +++++- 4 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 web/ce/components/relations/activity.ts diff --git a/web/ce/components/relations/activity.ts b/web/ce/components/relations/activity.ts new file mode 100644 index 00000000000..6eeccfbca49 --- /dev/null +++ b/web/ce/components/relations/activity.ts @@ -0,0 +1,20 @@ +import { TIssueActivity } from "@plane/types"; + +export const getRelationActivityContent = (activity: TIssueActivity | undefined): string | undefined => { + if (!activity) return; + + switch (activity.field) { + case "blocking": + return activity.old_value === "" ? `marked this issue is blocking issue ` : `removed the blocking issue `; + case "blocked_by": + return activity.old_value === "" + ? `marked this issue is being blocked by ` + : `removed this issue being blocked by issue `; + case "duplicate": + return activity.old_value === "" ? `marked this issue as duplicate of ` : `removed this issue as a duplicate of `; + case "relates_to": + activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `; + } + + return; +}; diff --git a/web/ce/components/relations/index.tsx b/web/ce/components/relations/index.tsx index 5c110b8e8f9..5517ac06359 100644 --- a/web/ce/components/relations/index.tsx +++ b/web/ce/components/relations/index.tsx @@ -3,6 +3,8 @@ import { RelatedIcon } from "@plane/ui"; import { TRelationObject } from "@/components/issues"; import { TIssueRelationTypes } from "../../types"; +export * from "./activity"; + export const ISSUE_RELATION_OPTIONS: Record = { relates_to: { key: "relates_to", @@ -33,3 +35,5 @@ export const ISSUE_RELATION_OPTIONS: Record ISSUE_RELATION_OPTIONS; diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx b/web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx index cb6c07c9915..adcebb5146f 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; // hooks import { useIssueDetail } from "@/hooks/store"; // Plane-web -import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations"; +import { getRelationActivityContent, useTimeLineRelationOptions } from "@/plane-web/components/relations"; import { TIssueRelationTypes } from "@/plane-web/types"; // import { IssueActivityBlockComponent } from "./"; @@ -18,32 +18,17 @@ export const IssueRelationActivity: FC = observer((props } = useIssueDetail(); const activity = getActivityById(activityId); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); + const activityContent = getRelationActivityContent(activity); if (!activity) return <>; return ( } + icon={activity.field ? ISSUE_RELATION_OPTIONS[activity.field as TIssueRelationTypes]?.icon(14) : <>} activityId={activityId} ends={ends} > - <> - {activity.field === "blocking" && - (activity.old_value === "" ? `marked this issue is blocking issue ` : `removed the blocking issue `)} - {activity.field === "blocked_by" && - (activity.old_value === "" - ? `marked this issue is being blocked by ` - : `removed this issue being blocked by issue `)} - {activity.field === "duplicate" && - (activity.old_value === "" ? `marked this issue as duplicate of ` : `removed this issue as a duplicate of `)} - {activity.field === "relates_to" && - (activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `)} - - {activity.old_value === "" ? ( - {activity.new_value}. - ) : ( - {activity.old_value}. - )} - + {activityContent} ); }); diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx b/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx index 18cd3481fe9..148cf1f26cc 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx @@ -1,9 +1,12 @@ import { FC } from "react"; import { observer } from "mobx-react"; +// helpers +import { getValidKeysFromObject } from "@/helpers/array.helper"; // hooks import { useIssueDetail } from "@/hooks/store"; // plane web components import { IssueTypeActivity } from "@/plane-web/components/issues/issue-details"; +import { useTimeLineRelationOptions } from "@/plane-web/components/relations"; // local components import { IssueDefaultActivity, @@ -38,6 +41,8 @@ export const IssueActivityItem: FC = observer((props) => { activity: { getActivityById }, comment: {}, } = useIssueDetail(); + const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions(); + const activityRelations = getValidKeysFromObject(ISSUE_RELATION_OPTIONS); const componentDefaultProps = { activityId, ends }; @@ -59,7 +64,7 @@ export const IssueActivityItem: FC = observer((props) => { return ; case "parent": return ; - case ["blocking", "blocked_by", "duplicate", "relates_to"].find((field) => field === activityField): + case activityRelations.find((field) => field === activityField): return ; case "start_date": return ;