Skip to content

Comments

[WEB-2442] fix : Timeline layout bugs#5946

Merged
pushya22 merged 6 commits intopreviewfrom
fix-timeline-layout-bugs
Nov 4, 2024
Merged

[WEB-2442] fix : Timeline layout bugs#5946
pushya22 merged 6 commits intopreviewfrom
fix-timeline-layout-bugs

Conversation

@rahulramesha
Copy link
Contributor

@rahulramesha rahulramesha commented Nov 4, 2024

This PR fixes few bugs in Timeline layout. The changes include,

  1. Relation creation and removal for issues
  2. Scrolling to the block when the block is beyond current chart's limits
  3. Fix Drak mode for Timeline layout
  4. update relation activity for all relations

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced handling of issue relations to support multiple relation types (blocking, start_after, finish_after).
    • Introduced a new function for generating activity content based on relation types.
    • Added scrolling functionality to Gantt chart components for improved navigation.
  • Improvements

    • Updated styling for current week, month, and day indicators in Gantt chart views for better visual clarity.
    • Implemented dynamic retrieval of issue relation options to improve flexibility in relation management.
  • Bug Fixes

    • Streamlined logic for counting issue relations, ensuring accurate counts based on updated options.
  • Chores

    • Added a utility function to filter valid keys from objects, enhancing code reusability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 4, 2024

Walkthrough

The changes in this pull request primarily focus on enhancing the handling of issue relations across various components and methods. Modifications include the introduction of new functions, updates to existing method signatures, and the integration of a dynamic approach for accessing relation options through the useTimeLineRelationOptions hook. These changes aim to improve the flexibility and robustness of the components involved in managing issue relations, activities, and rendering within the Gantt chart.

Changes

File Path Change Summary
apiserver/plane/app/views/issue/relation.py Updated create and remove_relation methods in IssueRelationViewSet to handle multiple relation types: "blocking", "start_after", and "finish_after".
web/ce/components/relations/activity.ts Added getRelationActivityContent function to return activity content based on TIssueActivity.
web/ce/components/relations/index.tsx Added export for all entities from activity module and introduced useTimeLineRelationOptions function.
web/core/components/gantt-chart/blocks/block-row-list.tsx Updated GanttChartBlocksProps to include handleScrollToBlock prop and passed it to BlockRow.
web/core/components/gantt-chart/blocks/block-row.tsx Added handleScrollToBlock prop to BlockRow and modified button click handling.
web/core/components/gantt-chart/chart/main-content.tsx Added handleScrollToBlock function and modified updateCurrentViewRenderPayload method to include targetDate.
web/core/components/gantt-chart/chart/root.tsx Updated updateCurrentViewRenderPayload to include targetDate and renamed updatingCurrentLeftScrollPosition to updateItemsContainerWidth.
web/core/components/gantt-chart/chart/views/month.tsx Updated CSS class for current week representation.
web/core/components/gantt-chart/chart/views/quarter.tsx Updated CSS class for current month representation.
web/core/components/gantt-chart/chart/views/week.tsx Updated CSS classes for current day and background styling.
web/core/components/gantt-chart/views/month-view.ts Updated generateMonthChart function to include targetDate parameter.
web/core/components/gantt-chart/views/quarter-view.ts Updated generateQuarterChart function to include targetDate parameter.
web/core/components/gantt-chart/views/week-view.ts Updated generateWeekChart function to include targetDate parameter.
web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx Updated relation count logic to use useTimeLineRelationOptions.
web/core/components/issues/issue-detail-widgets/relations/content.tsx Replaced direct import of ISSUE_RELATION_OPTIONS with useTimeLineRelationOptions.
web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx Updated to use useTimeLineRelationOptions for relation options.
web/core/components/issues/issue-detail-widgets/relations/title.tsx Updated getRelationCountByIssueId to accept ISSUE_RELATION_OPTIONS as an argument.
web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx Simplified rendering logic using getRelationActivityContent.
web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx Updated activity relations handling to use dynamic keys from ISSUE_RELATION_OPTIONS.
web/core/components/issues/issue-detail/relation-select.tsx Replaced ISSUE_RELATION_OPTIONS with useTimeLineRelationOptions for dynamic relation options.
web/core/store/issue/issue-details/relation.store.ts Updated getRelationCountByIssueId method to accept ISSUE_RELATION_OPTIONS parameter.
web/helpers/array.helper.ts Added getValidKeysFromObject utility function to filter truthy keys from an object.

Possibly related PRs

Suggested labels

🐛bug, 🌐frontend, 🌟improvement

Suggested reviewers

  • SatishGandham

🐇 In the fields where issues bloom,
New relations find their room.
With options fresh and logic clear,
We hop along without a fear.
Dynamic paths now brightly shine,
In our code, all will align! 🌼✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@rahulramesha rahulramesha changed the title fix : Timeline layout bugs [WEB-2442] fix : Timeline layout bugs Nov 4, 2024
@rahulramesha rahulramesha marked this pull request as ready for review November 4, 2024 11:18
@pushya22 pushya22 merged commit 71589f9 into preview Nov 4, 2024
@pushya22 pushya22 deleted the fix-timeline-layout-bugs branch November 4, 2024 11:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (25)
web/ce/components/relations/activity.ts (2)

3-4: Consider making the null check more explicit.

While the current check works, being more explicit about the check would improve code readability and maintainability.

-  if (!activity) return;
+  if (activity === undefined) return undefined;

3-20: Add unit tests for relation activity content generation.

This utility function is used by multiple components and handles critical relation state messages. Consider adding unit tests to verify all cases and edge conditions.

Would you like me to help create comprehensive unit tests for this function? I can generate test cases covering:

  • All relation types
  • Empty and non-empty old_value scenarios
  • Undefined activity handling
web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx (1)

21-22: Add error handling for activity content generation.

While the code is more maintainable now, consider adding error handling for the getRelationActivityContent call to gracefully handle edge cases.

- const activityContent = getRelationActivityContent(activity);
+ const activityContent = activity ? getRelationActivityContent(activity) : null;
web/ce/components/relations/index.tsx (1)

38-39: Consider memoizing the relation options.

While the implementation is correct, consider memoizing the return value to prevent unnecessary re-renders in components that depend on these options.

-export const useTimeLineRelationOptions = () => ISSUE_RELATION_OPTIONS;
+import { useMemo } from 'react';
+
+export const useTimeLineRelationOptions = () => {
+  return useMemo(() => ISSUE_RELATION_OPTIONS, []);
+};
web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx (1)

23-23: Consider adding error handling for the hook.

While the hook usage is correct, consider handling potential initialization errors or loading states to improve robustness.

-  const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
+  const { relationOptions: ISSUE_RELATION_OPTIONS, error, isLoading } = useTimeLineRelationOptions();
+  
+  if (error) {
+    return <div>Failed to load relation options</div>;
+  }
+  
+  if (isLoading) {
+    return <div>Loading...</div>;
+  }
web/core/components/gantt-chart/blocks/block-row-list.tsx (2)

13-14: Consider adding JSDoc comments for the new prop.

The type definition is correct, but adding documentation would improve maintainability.

 export type GanttChartBlocksProps = {
   blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
+  /** Callback to handle scrolling to a specific block in the Gantt chart */
   handleScrollToBlock: (block: IGanttBlock) => void;

Line range hint 35-54: Fix the key prop placement for proper list rendering.

The key prop is currently placed on the BlockRow component, but it's wrapped in a fragment and RenderIfVisible. This could lead to React reconciliation issues.

Move the key prop to the outermost element:

 {blockIds?.map((blockId) => (
-        <>
+        <React.Fragment key={blockId}>
           <RenderIfVisible
             root={ganttContainerRef}
             horizontalOffset={100}
             verticalOffset={200}
             classNames="relative min-w-full w-max"
             placeholderChildren={<div className="w-full pointer-events-none" style={{ height: `${BLOCK_HEIGHT}px` }} />}
             shouldRecordHeights={false}
           >
             <BlockRow
-              key={blockId}
               blockId={blockId}
               showAllBlocks={showAllBlocks}
               blockUpdateHandler={blockUpdateHandler}
               handleScrollToBlock={handleScrollToBlock}
               enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock}
               selectionHelpers={selectionHelpers}
               ganttContainerRef={ganttContainerRef}
             />
           </RenderIfVisible>
-        </>
+        </React.Fragment>
web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx (1)

44-45: Improve variable naming convention

The variable ISSUE_RELATION_OPTIONS uses uppercase naming convention typically reserved for static constants, but it's actually a dynamic value from a hook. Consider using camelCase to better reflect its nature.

-  const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
+  const issueRelationOptions = useTimeLineRelationOptions();
-  const activityRelations = getValidKeysFromObject(ISSUE_RELATION_OPTIONS);
+  const activityRelations = getValidKeysFromObject(issueRelationOptions);
web/helpers/array.helper.ts (2)

95-99: Enhance type safety and documentation.

The JSDoc comments could be more descriptive with examples and the types could be more specific.

 /**
  * Returns Valid keys from object whose value is not falsy
+ * @param obj - The object to extract keys from
+ * @returns Array of keys whose corresponding values are truthy
+ * @example
+ * getValidKeysFromObject({ a: 1, b: 0, c: true, d: '' })
+ * // returns ['a', 'c']
  */
-export const getValidKeysFromObject = (obj: any) => {
+export const getValidKeysFromObject = <T extends Record<string, unknown>>(obj: T): (keyof T)[] => {

94-104: Good architectural fit with Timeline relation processing.

This utility function aligns well with the PR objectives for handling Timeline relations. Its placement in the array helpers makes sense as it complements existing object/array manipulation utilities.

Consider creating a dedicated test file for this function to ensure it handles all edge cases correctly, especially given its importance in Timeline relation processing.

web/core/components/gantt-chart/chart/views/quarter.tsx (1)

Line range hint 13-13: Consider improving type safety and styling maintainability

The component could benefit from the following improvements:

  1. Replace FC<any> with proper TypeScript types for better type safety
  2. Consider using CSS variables for the background colors to make theme changes more maintainable

Example implementation:

- export const QuarterChartView: FC<any> = observer(() => {
+ interface QuarterChartViewProps {
+   // Add relevant props here
+ }
+ 
+ export const QuarterChartView: FC<QuarterChartViewProps> = observer(() => {

And in your CSS:

:root {
  --highlight-bg-opacity: 0.2;
  --highlight-bg-color: var(--custom-primary-100);
}
web/core/components/gantt-chart/blocks/block-row.tsx (1)

102-102: Consider improving accessibility and error handling.

While the scroll handler implementation is functional, consider these improvements:

  1. Add ARIA attributes to better describe the button's purpose
  2. Add error handling for undefined block scenarios
  3. Consider memoizing the handler to prevent unnecessary re-renders

Here's a suggested improvement:

- onClick={() => handleScrollToBlock(block)}
+ aria-label={`Scroll to ${block?.data?.title || 'block'}`}
+ onClick={() => block && handleScrollToBlock(block)}
web/core/components/gantt-chart/chart/views/month.tsx (1)

95-95: Consider extracting the repeated background style into a constant.

The background color bg-custom-primary-100/20 is used consistently for highlighting the current week in both the subtitle and column sections. To maintain this consistency and make future updates easier, consider extracting this into a constant.

+const CURRENT_WEEK_HIGHLIGHT_CLASS = "bg-custom-primary-100/20";
+
 export const MonthChartView: FC<any> = observer(() => {
   // ... existing code ...
   // Then use it in both places:
   // Line 70:
   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-100/20": weekBlock.today,
+    [CURRENT_WEEK_HIGHLIGHT_CLASS]: weekBlock.today,
   })}
   // Line 95:
   className={cn("h-full overflow-hidden outline-[0.25px] outline outline-custom-border-100", {
-    "bg-custom-primary-100/20": weekBlock.today,
+    [CURRENT_WEEK_HIGHLIGHT_CLASS]: weekBlock.today,
   })}
web/core/components/gantt-chart/views/quarter-view.ts (1)

76-85: Consider extracting date calculation logic.

The date calculation logic is similar between left and right side handling. Consider extracting this into a helper function to:

  1. Reduce code duplication
  2. Centralize date validation
  3. Make the code more maintainable
+const calculateChartDates = (
+  currentDate: Date,
+  referenceDate: Date,
+  range: number,
+  isLeftSide: boolean
+): { minusDate: Date; plusDate: Date } => {
+  if (isLeftSide) {
+    return {
+      minusDate: new Date(currentDate.getFullYear(), currentDate.getMonth() - range / 2, 1),
+      plusDate: new Date(referenceDate.getFullYear(), referenceDate.getMonth() - 1, 1)
+    };
+  }
+  return {
+    minusDate: new Date(referenceDate.getFullYear(), referenceDate.getMonth() + 1, 1),
+    plusDate: new Date(currentDate.getFullYear(), currentDate.getMonth() + range / 2, 1)
+  };
+};

 else if (side === "left") {
   const chartStartDate = renderState.data.startDate;
   const currentDate = targetDate ? targetDate : chartStartDate;
-  minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range / 2, 1);
-  plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth() - 1, 1);
+  const dates = calculateChartDates(currentDate, chartStartDate, range, true);
+  minusDate = dates.minusDate;
+  plusDate = dates.plusDate;

 } else if (side === "right") {
   const chartEndDate = renderState.data.endDate;
   const currentDate = targetDate ? targetDate : chartEndDate;
-  minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth() + 1, 1);
-  plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range / 2, 1);
+  const dates = calculateChartDates(currentDate, chartEndDate, range, false);
+  minusDate = dates.minusDate;
+  plusDate = dates.plusDate;
web/core/components/issues/issue-detail-widgets/relations/content.tsx (2)

66-66: Consider memoizing the relation options.

While the hook usage is correct, consider memoizing the result to prevent unnecessary recalculations on re-renders.

Consider applying this change:

-  const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
+  const ISSUE_RELATION_OPTIONS = React.useMemo(() => useTimeLineRelationOptions(), []);

Line range hint 32-147: Consider adding error boundaries and cleanup.

The component handles complex relation operations but could benefit from:

  1. An error boundary to gracefully handle relation operation failures
  2. Proper cleanup of relation subscriptions/listeners when the component unmounts

Consider wrapping the component with an error boundary and adding cleanup:

// Add at the component level
useEffect(() => {
  return () => {
    // Cleanup any active relation operations
    setIssueCrudState({
      update: { toggle: false, issueId: undefined, issue: undefined },
      delete: { toggle: false, issueId: undefined, issue: undefined }
    });
  };
}, []);
web/core/components/gantt-chart/views/month-view.ts (2)

66-75: Consider refactoring date calculations for better maintainability

While the logic is correct, consider extracting the date calculations into a helper function to improve readability and maintainability. Also, the variable naming could be more consistent.

Consider this refactor:

-    const chartStartDate = renderState.data.startDate;
-    const currentDate = targetDate ? targetDate : chartStartDate;
-
-    minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
-    plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
+    const referenceDate = renderState.data.startDate;
+    const baseDate = targetDate ?? referenceDate;
+    
+    const calculateChartDates = (date: Date, rangeMonths: number) => ({
+      minus: new Date(date.getFullYear(), date.getMonth() - rangeMonths, 1),
+      plus: new Date(referenceDate.getFullYear(), referenceDate.getMonth(), referenceDate.getDate() - 1)
+    });
+    
+    const { minus: minusDate, plus: plusDate } = calculateChartDates(baseDate, range);

83-94: Consider unifying left and right side chart generation logic

The left and right side chart generation logic is similar but mirrored. Consider creating a unified approach to handle both cases.

Here's a suggested approach:

+    type ChartSide = 'left' | 'right';
+    
+    const generateSideChart = (side: ChartSide, referenceDate: Date, targetDate: Date | undefined) => {
+      const baseDate = targetDate ?? referenceDate;
+      const isLeft = side === 'left';
+      
+      return {
+        minus: new Date(
+          isLeft ? baseDate.getFullYear() : referenceDate.getFullYear(),
+          isLeft ? baseDate.getMonth() - range : referenceDate.getMonth(),
+          isLeft ? 1 : referenceDate.getDate() + 1
+        ),
+        plus: new Date(
+          isLeft ? referenceDate.getFullYear() : baseDate.getFullYear(),
+          isLeft ? referenceDate.getMonth() : baseDate.getMonth() + range,
+          isLeft ? referenceDate.getDate() - 1 : 1
+        )
+      };
+    };

-    const chartEndDate = renderState.data.endDate;
-    const currentDate = targetDate ? targetDate : chartEndDate;
-
-    minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
-    plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1);
+    const { minus: minusDate, plus: plusDate } = generateSideChart(
+      'right',
+      renderState.data.endDate,
+      targetDate
+    );

This refactor would:

  1. Reduce code duplication
  2. Make the logic more maintainable
  3. Make it easier to add new side handling in the future if needed
web/core/components/issues/issue-detail/relation-select.tsx (1)

Line range hint 31-158: Consider extracting relation item to a separate component.

The component implementation looks solid and meets all PR objectives. However, for better maintainability, consider extracting the relation item rendering logic (the mapped items JSX) into a separate component. This would:

  • Improve code organization
  • Make testing easier
  • Allow for better reusability

Example structure:

// components/issues/issue-detail/relation-item.tsx
type RelationItemProps = {
  relationIssueId: string;
  workspaceSlug: string;
  disabled: boolean;
  onRemove: () => void;
  className: string;
};

const RelationItem: React.FC<RelationItemProps> = ({ ... }) => {
  // Extract the relation item JSX here
};
web/core/components/gantt-chart/views/week-view.ts (1)

Line range hint 41-99: Consider adding comprehensive date handling tests.

While the implementation successfully addresses the timeline layout and scrolling requirements, the date handling logic would benefit from comprehensive testing. Consider adding test cases for:

  • Month/year boundary transitions
  • Leap years
  • Different time zones
  • Invalid date combinations

Consider extracting the date manipulation logic into a separate utility class with proper validation and error handling. This would:

  1. Centralize date handling logic
  2. Make it easier to test edge cases
  3. Improve maintainability
  4. Enable reuse across other views (day, month, etc.)
web/core/components/gantt-chart/chart/root.tsx (2)

112-115: Review setTimeout usage for scroll handling.

The use of setTimeout for scroll handling could lead to race conditions or flickering. Consider using React's useEffect or requestAnimationFrame for more reliable DOM updates.

Consider this alternative approach:

- setTimeout(() => {
-   handleScrollToCurrentSelectedDate(currentRender.state, currentRender.state.data.currentDate);
- }, 50);
+ useEffect(() => {
+   if (currentRender.state && currentRender.state.data.currentDate) {
+     requestAnimationFrame(() => 
+       handleScrollToCurrentSelectedDate(currentRender.state, currentRender.state.data.currentDate)
+     );
+   }
+ }, [currentRender.state]);

Line range hint 89-152: Overall implementation effectively addresses timeline layout issues.

The changes successfully implement the scrolling functionality mentioned in the PR objectives. The code handles both normal navigation and targeted scrolling to specific blocks, with proper state management and view updates.

A few suggestions for future improvements:

  1. Consider using a ref for the gantt container instead of querySelector
  2. Add error boundaries for DOM operations
  3. Implement more robust scroll handling
web/core/components/gantt-chart/chart/main-content.tsx (1)

116-134: Consider improving scroll handling robustness.

While the implementation works, there are several areas for improvement:

  1. Add error handling for invalid dates
  2. Clean up setTimeout on component unmount
  3. Document or extract the magic number (-4) as a constant
  4. Consider adding loading state during scroll operations

Here's a suggested improvement:

+ const SCROLL_OFFSET = -4; // Offset to align block with left edge
+ const SCROLL_DELAY = 0;   // Delay for scroll animation
+
  const handleScrollToBlock = (block: IGanttBlock) => {
    const scrollContainer = ganttContainerRef.current as HTMLDivElement;
    const scrollToDate = getDate(block.start_date);
    let chartData;

-   if (!scrollContainer || !currentViewData || !scrollToDate) return;
+   if (!scrollContainer || !currentViewData) return;
+   if (!scrollToDate) {
+     console.warn('Invalid date for block:', block.id);
+     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);
    }
    
    const updatedPosition = getItemPositionWidth(chartData ?? currentViewData, block);

-   setTimeout(() => {
+   const timeoutId = setTimeout(() => {
-     if (updatedPosition) scrollContainer.scrollLeft = updatedPosition.marginLeft - 4;
+     if (updatedPosition) scrollContainer.scrollLeft = updatedPosition.marginLeft + SCROLL_OFFSET;
    });
+   
+   return () => clearTimeout(timeoutId);
  };
web/core/store/issue/issue-details/relation.store.ts (1)

92-102: Consider adding input validation and early returns

The implementation is solid with good use of computedFn for memoization and proper null handling. Consider these improvements:

 getRelationCountByIssueId = computedFn(
   (issueId: string, ISSUE_RELATION_OPTIONS: { [key in TIssueRelationTypes]?: TRelationObject }) => {
+    if (!issueId || !ISSUE_RELATION_OPTIONS) return 0;
+
     const issueRelations = this.getRelationsByIssueId(issueId);
+    if (!issueRelations) return 0;
 
     const issueRelationKeys = (Object.keys(issueRelations ?? {}) as TIssueRelationTypes[]).filter(
       (relationKey) => !!ISSUE_RELATION_OPTIONS[relationKey]
     );
 
     return issueRelationKeys.reduce((acc, curr) => acc + (issueRelations?.[curr]?.length ?? 0), 0);
   }
 );
apiserver/plane/app/views/issue/relation.py (1)

Line range hint 336-347: Add error handling for relation removal.

While the addition of timeline relation types is correct, the code could benefit from proper error handling when the relation doesn't exist.

Consider wrapping the relation retrieval in a try-except block:

-        if relation_type in ["blocking", "start_after", "finish_after"]:
-            issue_relation = IssueRelation.objects.get(
-                workspace__slug=slug,
-                project_id=project_id,
-                issue_id=related_issue,
-                related_issue_id=issue_id,
-            )
-        else:
-            issue_relation = IssueRelation.objects.get(
-                workspace__slug=slug,
-                project_id=project_id,
-                issue_id=issue_id,
-                related_issue_id=related_issue,
-            )
+        try:
+            if relation_type in ["blocking", "start_after", "finish_after"]:
+                issue_relation = IssueRelation.objects.get(
+                    workspace__slug=slug,
+                    project_id=project_id,
+                    issue_id=related_issue,
+                    related_issue_id=issue_id,
+                )
+            else:
+                issue_relation = IssueRelation.objects.get(
+                    workspace__slug=slug,
+                    project_id=project_id,
+                    issue_id=issue_id,
+                    related_issue_id=related_issue,
+                )
+        except IssueRelation.DoesNotExist:
+            return Response(
+                {"error": "Issue relation not found"},
+                status=status.HTTP_404_NOT_FOUND
+            )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 20b2a70 and 9c373f6.

📒 Files selected for processing (22)
  • apiserver/plane/app/views/issue/relation.py (2 hunks)
  • web/ce/components/relations/activity.ts (1 hunks)
  • web/ce/components/relations/index.tsx (2 hunks)
  • web/core/components/gantt-chart/blocks/block-row-list.tsx (2 hunks)
  • web/core/components/gantt-chart/blocks/block-row.tsx (2 hunks)
  • web/core/components/gantt-chart/chart/main-content.tsx (5 hunks)
  • web/core/components/gantt-chart/chart/root.tsx (5 hunks)
  • web/core/components/gantt-chart/chart/views/month.tsx (2 hunks)
  • web/core/components/gantt-chart/chart/views/quarter.tsx (2 hunks)
  • web/core/components/gantt-chart/chart/views/week.tsx (2 hunks)
  • web/core/components/gantt-chart/views/month-view.ts (2 hunks)
  • web/core/components/gantt-chart/views/quarter-view.ts (2 hunks)
  • web/core/components/gantt-chart/views/week-view.ts (2 hunks)
  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (2 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/content.tsx (2 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/title.tsx (2 hunks)
  • web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx (2 hunks)
  • web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx (3 hunks)
  • web/core/components/issues/issue-detail/relation-select.tsx (5 hunks)
  • web/core/store/issue/issue-details/relation.store.ts (3 hunks)
  • web/helpers/array.helper.ts (2 hunks)
🔇 Additional comments (43)
web/ce/components/relations/activity.ts (2)

1-2: LGTM: Clean and necessary import.

The type import is well-defined and provides proper type safety for the function parameter.


19-20: LGTM: Explicit return is good practice.

While TypeScript would infer undefined return, being explicit improves code clarity and maintainability.

web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx (2)

6-6: LGTM! Good move to dynamic relation options.

The switch from static ISSUE_RELATION_OPTIONS to dynamic options via hooks aligns well with the Timeline layout improvements and provides more flexibility in handling relations.


27-27: Verify type safety for relation field access.

The type casting of activity.field to TIssueRelationTypes could be unsafe. Consider adding runtime validation.

✅ Verification successful

Let me gather more information about how the activity.field is being used and validated.


Let me check one more thing to understand how the activity type is defined and validated.


Let me try one last search with a corrected pattern.


Based on the gathered information, I can now generate the final response:

Type casting to TIssueRelationTypes is safe in this context

The type casting is safe because:

  1. TIssueRelationTypes is strictly defined as "blocking" | "blocked_by" | "duplicate" | "relates_to"
  2. The ISSUE_RELATION_OPTIONS object is type-safe and only contains these specific relation types
  3. The optional chaining (?.) provides additional runtime safety if the field is undefined
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check all possible values of activity.field in the codebase
rg -A 2 "activity\.field.*relation" 

Length of output: 241


Script:

#!/bin/bash
# Check ISSUE_RELATION_OPTIONS definition and usage
rg "ISSUE_RELATION_OPTIONS.*=" -A 5

# Check if there's any validation for activity.field
ast-grep --pattern 'activity.field'

# Check the type definition of the activity prop/parameter
ast-grep --pattern 'activity: $_'

Length of output: 8160


Script:

#!/bin/bash
# Check the type definitions related to activity
rg "type.*Activity.*{" -A 10

# Check if there's any validation before the activity reaches this component
ast-grep --pattern 'function getRelationActivityContent($_) {
  $$$
}'

Length of output: 201


Script:

#!/bin/bash
# Check the type definitions related to activity
rg "interface.*Activity" -A 10
rg "type.*Activity" -A 10

# Check how activity.field is validated in the relation component
ast-grep --pattern 'const RelationActivity = ({ activity }: $_) => {
  $$$
}'

Length of output: 47610

web/ce/components/relations/index.tsx (1)

6-6: Verify the activity module existence.

Let's ensure the activity module exists to prevent potential runtime errors.

✅ Verification successful

The activity module exists and is correctly referenced

The activity module exists at web/ce/components/relations/activity.ts and is correctly imported in index.tsx. The export statement is valid and will work as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of the activity module
# Expected: The activity module should exist in the same directory

fd --type f "activity.{ts,tsx}" "$(dirname web/ce/components/relations/index.tsx)"

Length of output: 424


Script:

#!/bin/bash
# Let's try with glob pattern to find the activity module
fd -g "activity.{ts,tsx}" "web/ce/components/relations"

# Also search for any imports of activity to verify its existence
rg "from ['\"].*activity['\"]" "web/ce/components/relations"

Length of output: 226

web/core/components/issues/issue-detail-widgets/relations/title.tsx (2)

9-10: LGTM: Clean import addition

The new import for useTimeLineRelationOptions is properly structured and follows the project's import conventions.


25-25: Consider adding explicit type for relation options

While the code functions correctly, consider adding explicit typing for ISSUE_RELATION_OPTIONS to improve type safety and code maintainability.

Let's verify the consistency of the getRelationCountByIssueId function signature across the codebase:

Also applies to: 27-27

web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx (1)

9-9: LGTM! Verify hook usage across Timeline components.

The switch to useTimeLineRelationOptions hook aligns with the dynamic approach mentioned in the PR objectives.

Let's verify this hook's usage across Timeline components:

✅ Verification successful

Hook implementation and usage are consistent

The verification shows that:

  • The hook is correctly implemented in web/ce/components/relations/index.tsx as a simple wrapper around ISSUE_RELATION_OPTIONS
  • It's being used consistently in the quick-action-button component
  • The hook is imported and used across multiple Timeline-related components as seen in the first search results
  • No legacy static imports of ISSUE_RELATION_OPTIONS were found
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the hook is consistently used across Timeline components
# and there are no remaining static imports of ISSUE_RELATION_OPTIONS

# Test 1: Find all Timeline-related components using this hook
rg -l "useTimeLineRelationOptions"

# Test 2: Check for any remaining static imports that might need updating
rg "import.*ISSUE_RELATION_OPTIONS.*from.*plane-web/components/relations"

Length of output: 680


Script:

#!/bin/bash
# Let's verify the hook implementation and its usage pattern

# Check the hook implementation
ast-grep --pattern 'export const useTimeLineRelationOptions = $_'

# Check how the hook is being used in components
rg -A 5 "useTimeLineRelationOptions" web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx
rg -A 5 "useTimeLineRelationOptions" web/ce/components/relations/index.tsx

Length of output: 876

web/core/components/gantt-chart/blocks/block-row-list.tsx (3)

8-8: LGTM!

The IGanttBlock type import is correctly added and properly grouped with other type imports.


22-30: LGTM!

Props destructuring is clean and consistent with the existing pattern.


49-49: Verify scroll behavior integration.

Since this PR aims to fix scrolling to blocks beyond chart limits, please ensure that:

  1. The scroll behavior works correctly when blocks are outside the visible area
  2. The scroll position calculation takes into account the chart's boundaries
  3. The scrolling is smooth and doesn't cause layout shifts
✅ Verification successful

Let me try another search strategy to verify the scroll behavior integration.


Let me try one more search focusing specifically on the Gantt chart scroll functionality.


Scroll behavior integration is properly implemented

The implementation in main-content.tsx shows a robust scroll handling mechanism that:

  1. Handles blocks outside visible area by checking if the scroll target date is beyond current view boundaries
  2. Updates the chart view accordingly using updateCurrentViewRenderPayload
  3. Properly propagates the scroll handler through the component hierarchy (main-content → block-row-list → block-row)
  4. Includes visibility checks to prevent unnecessary scrolling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for scroll-related implementations and tests
echo "Searching for scroll-related implementations:"
rg -l "scroll" --type ts --type tsx

echo "\nSearching for specific scroll handler implementation:"
ast-grep --pattern 'handleScrollToBlock = (block: $_) => { $$$ }'

Length of output: 353


Script:

#!/bin/bash
# Search for scroll-related implementations with proper file extensions
echo "Searching for scroll-related implementations:"
rg -l "scroll" --type typescript --type tsx

echo "\nSearching for handleScrollToBlock implementation:"
rg -A 5 "handleScrollToBlock" --type typescript --type tsx

echo "\nSearching for scroll-related test files:"
fd -e test.ts -e test.tsx | xargs rg "scroll" -l

Length of output: 7986


Script:

#!/bin/bash
# Search for scroll-related code in Gantt chart components
echo "Searching for scroll handler in Gantt chart components:"
rg -A 10 "handleScrollToBlock" web/core/components/gantt-chart/

echo "\nSearching for scroll-related code in block components:"
rg -A 5 "scroll" web/core/components/gantt-chart/blocks/

Length of output: 8771

web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (2)

13-14: LGTM: Clean import organization

The new import is well-organized with a clear section comment.


36-37: Consider performance optimization and verify consistency

While the implementation aligns with the PR's objective of fixing relation handling, consider these improvements:

  1. The hook result could be memoized if the options are static:
-  const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
+  const ISSUE_RELATION_OPTIONS = React.useMemo(() => useTimeLineRelationOptions(), []);
  1. The method signature change for getRelationCountByIssueId needs consistent updates across the codebase.
web/core/components/issues/issue-detail/issue-activity/activity/activity-list.tsx (2)

3-4: LGTM: Clean import organization

The new imports are well-organized and properly categorized under helpers and hooks sections.

Also applies to: 9-9


44-45: Consider adding error handling for the timeline options hook

The useTimeLineRelationOptions hook result is used directly without any error handling. Consider adding error handling to gracefully handle cases where the hook fails or returns unexpected data.

-  const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
-  const activityRelations = getValidKeysFromObject(ISSUE_RELATION_OPTIONS);
+  const issueRelationOptions = useTimeLineRelationOptions();
+  const activityRelations = issueRelationOptions 
+    ? getValidKeysFromObject(issueRelationOptions)
+    : [];
web/helpers/array.helper.ts (1)

100-104: LGTM! Input validation and implementation are solid.

The function implementation:

  • Properly validates input with comprehensive checks
  • Correctly handles edge cases
  • Uses efficient lodash isEmpty for object validation
  • Returns expected results with a clean filter operation
web/core/components/gantt-chart/chart/views/week.tsx (3)

52-52: Improved dark mode compatibility for today's date highlight

The change to bg-custom-primary-100/20 is a good improvement as it provides better contrast and compatibility with dark mode through opacity-based styling.


74-74: Enhanced theme consistency for chart background

The addition of bg-custom-background-100 ensures consistent background styling that properly adapts to both light and dark modes.


Line range hint 79-85: Verify visual clarity when today falls on weekends

While the change to bg-custom-primary-100/20 improves dark mode support, please verify that the highlight remains visually distinct when today's column overlaps with weekend columns (which have bg-custom-background-90).

To help verify this, you can check how it looks by running this layout with different dates:

✅ Verification successful

Let's also check for any existing color-related styling or theme configurations:

And let's look for any existing visual tests or documentation:


Based on the search results, I can now provide a conclusive response about the visual clarity concern when today's column overlaps with weekend columns:

No visual conflict between weekend and today's highlight

The implementation ensures visual distinction through layered styling:

  • Weekend columns use bg-custom-background-90 with an outline
  • Today's column uses bg-custom-primary-100/20 (20% opacity primary color)
  • The weekend background is rendered as a child div inside the column, while today's highlight is on the column itself

This layering approach means:

  1. The styles won't conflict as they're applied at different levels
  2. Both visual indicators will be visible when they overlap
  3. The opacity-based highlight for today's column will still be distinguishable over the weekend's background
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any test files or stories that might help visualize this component
fd -e test.tsx -e stories.tsx -e spec.tsx | grep -i "gantt\|timeline\|week"

Length of output: 77


Script:

#!/bin/bash
# Let's try a broader search to find any test or story files related to the Gantt chart component
fd "gantt|week|timeline" -e tsx -e ts

# Also search for any visual styling tests or stories in the web/core directory
fd -t f . web/core/components/gantt-chart -e tsx -e ts

Length of output: 3329


Script:

#!/bin/bash
# Search for color-related styling configurations
rg "bg-custom-(primary|background)" web/core/components/gantt-chart -A 2 -B 2

Length of output: 22011


Script:

#!/bin/bash
# Search for any documentation or test files
fd "(test|spec|stories|README)" web/core/components/gantt-chart

Length of output: 65

web/core/components/gantt-chart/chart/views/quarter.tsx (2)

83-83: Consistent styling applied to month columns

The same background styling is correctly applied to the month columns, maintaining visual consistency with the header section.


59-59: Improved visual consistency for dark mode

The change from bg-custom-primary-10 to bg-custom-primary-100/20 provides better contrast in both light and dark modes by using opacity instead of a fixed color shade.

Let's verify if this color class is consistently used across other timeline views:

✅ Verification successful

Background color class is consistently used across all timeline views

The verification confirms that the bg-custom-primary-100/20 class is uniformly applied across all timeline views (week, month, and quarter) for highlighting today's date blocks, maintaining visual consistency throughout the Gantt chart component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent usage of the background color class across timeline views
rg "bg-custom-primary-\d+/20" web/core/components/gantt-chart/

Length of output: 786

web/core/components/gantt-chart/blocks/block-row.tsx (3)

13-13: LGTM: Import addition is appropriate.

The addition of IGanttBlock type import aligns with the new prop type requirements.


19-19: LGTM: Prop type definition is well-structured.

The handleScrollToBlock prop type is properly defined and aligns with the PR's scrolling functionality objective.


26-26: LGTM: Props destructuring is correct.

The handleScrollToBlock prop is properly destructured alongside other related props.

web/core/components/gantt-chart/chart/views/month.tsx (1)

70-70: Background color change improves dark mode compatibility.

The switch to bg-custom-primary-100/20 provides better contrast control through explicit opacity, which is beneficial for dark mode support.

Let's verify the styling in both light and dark modes:

✅ Verification successful

Based on the search results, I can see that bg-custom-primary-100/20 is widely used throughout the codebase for various UI elements, but I don't see any dark mode specific overrides for this color. Let me verify if there are any dark mode color configurations in the theme system.


Let me check the tailwind configuration files to understand how the primary colors are defined and if there are any specific dark mode configurations.


Let me check how the primary colors are defined in the CSS variables, as the tailwind config shows they are using CSS variables for colors.


Based on the comprehensive analysis of the codebase, I can now provide a final response regarding the background color change:

Background color change is appropriate for dark mode compatibility

The change from bg-custom-primary-10 to bg-custom-primary-100/20 is a valid improvement because:

  1. The codebase uses a CSS variable-based theming system with proper dark mode support (darkMode: "class" in Tailwind config)
  2. The new color uses opacity (/20) which works better with dark mode as it preserves contrast by being semi-transparent
  3. The color values are defined consistently across the application using CSS variables, ensuring theme consistency
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any theme-specific overrides or related styles
# that might affect this background color

# Look for related color definitions or theme variables
rg -g '*.{css,scss,ts,tsx}' 'custom-primary-100'

# Check for any dark mode specific overrides
rg -g '*.{css,scss,ts,tsx}' 'dark:bg-custom-primary'

Length of output: 37758


Script:

#!/bin/bash
# Check for theme configuration files and dark mode color definitions
rg -g '*.{js,ts,tsx,json}' -i "darkMode|dark.*theme|dark:.*primary"

# Look for any tailwind configuration files that might define these colors
fd -e js -e ts -e json tailwind.config

Length of output: 2631


Script:

#!/bin/bash
# Check the content of tailwind config files for color definitions
cat packages/tailwind-config-custom/tailwind.config.js
cat web/tailwind.config.js

Length of output: 17849


Script:

#!/bin/bash
# Look for CSS files that define the primary color variables
rg -g '*.{css,scss}' '\-\-color\-primary'

Length of output: 11011

web/core/components/gantt-chart/views/quarter-view.ts (2)

22-22: LGTM: Function signature change enhances flexibility.

The addition of the optional targetDate parameter allows for more precise control over chart generation, which aligns with the PR's objective of implementing scrolling to specific blocks.


58-68: Verify date handling edge cases.

While the logic for left-side chart generation looks correct, please ensure that:

  1. chartStartDate is always defined when accessed
  2. Date arithmetic produces valid results when targetDate is at month boundaries
✅ Verification successful

Based on the search results, I can see that chartStartDate is accessed from renderState.data.startDate in the Gantt chart views. Let me verify the initialization of this data structure.


Let me try a different approach to find the renderState initialization.


Let me check the payload initialization to ensure startDate is properly defined.


Let me check the ChartDataType definition to understand how startDate is initialized.


Based on the code analysis, I can now provide a final response regarding the date handling verification:

Date handling in quarter view is properly implemented and safe.

The code is well-structured with proper safeguards:

  • chartStartDate is guaranteed to be defined as it comes from renderState.data.startDate, which is a required field in the ChartDataTypeData interface
  • The date arithmetic is safe with proper boundary handling:
    • Left-side date calculations use proper month arithmetic with range/2
    • Right-side date calculations maintain proper date boundaries
  • The getMonthsBetweenTwoDates is only called after validating both minusDate and plusDate are defined
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all places where startDate is set or validated
rg -A 3 'startDate.*=' --type ts

Length of output: 19226


Script:

#!/bin/bash
# Search for renderState initialization and data structure
ast-grep --pattern 'renderState = {
  $$$
  data: {
    $$$
    startDate: $_,
    $$$
  },
  $$$
}'

Length of output: 102


Script:

#!/bin/bash
# Search for renderState initialization in the gantt chart views
rg -A 5 'renderState\s*=' web/core/components/gantt-chart/views/

Length of output: 5802


Script:

#!/bin/bash
# Search for quarterPayload initialization and type definition
rg -B 5 -A 5 'quarterPayload|IQuarterPayload' web/core/components/gantt-chart/views/

Length of output: 2318


Script:

#!/bin/bash
# Search for ChartDataType definition and its usage
rg -B 3 -A 5 'ChartDataType|type.*Chart.*=' web/core/components/gantt-chart/

Length of output: 19567

web/core/components/issues/issue-detail-widgets/relations/content.tsx (1)

13-13: LGTM! Verify hook usage across components.

The switch to useTimeLineRelationOptions hook aligns with the PR's objective of fixing relation handling. This change provides more dynamic and context-aware relation options.

Let's verify this hook's usage across other components:

✅ Verification successful

Migration to useTimeLineRelationOptions hook is consistent across the codebase

The verification shows that:

  • No legacy static imports of ISSUE_RELATION_OPTIONS remain in the codebase
  • The new useTimeLineRelationOptions hook is consistently used across all relevant components
  • The hook is properly defined in web/ce/components/relations/index.tsx
  • All components using the hook follow the same pattern of initializing ISSUE_RELATION_OPTIONS
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of the new hook across components
# and ensure no legacy ISSUE_RELATION_OPTIONS imports remain

# Search for any remaining static imports
echo "Checking for any remaining static ISSUE_RELATION_OPTIONS imports:"
rg "import.*ISSUE_RELATION_OPTIONS.*from.*@/plane-web/components/relations"

# Search for new hook usage
echo -e "\nChecking usage of new useTimeLineRelationOptions hook:"
rg "useTimeLineRelationOptions"

Length of output: 2571

web/core/components/gantt-chart/views/month-view.ts (2)

33-33: LGTM: Function signature enhancement

The addition of the optional targetDate parameter improves the function's flexibility in generating charts for specific dates, which aligns with the PR's objective of enhancing scrolling functionality.


Line range hint 33-94: Verify timeline layout integration

The changes look good but let's verify the integration with the timeline layout to ensure proper handling of all scenarios.

✅ Verification successful

Based on the gathered information, I can now generate the final response:

Timeline layout integration is properly maintained

The changes to generateMonthChart function are well-integrated with the timeline layout system:

  • The function is properly exported through the monthView object and maintains its interface
  • Timeline drag functionality remains independent of the chart generation logic
  • The changes only affect internal chart generation without impacting the broader timeline layout system
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for timeline-related components that might be affected by these changes

# Look for timeline components that use this chart generation
echo "Searching for timeline components using month chart generation..."
rg -l "monthView.generateChart" --type ts --type tsx

# Look for tests related to timeline and month view
echo "Searching for related test files..."
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx | rg -i "(timeline|month.*view|gantt)"

Length of output: 392


Script:

#!/bin/bash
# Let's try a different approach to find related files and usages

# Search for files containing 'generateMonthChart' function calls
echo "Searching for generateMonthChart usages..."
rg "generateMonthChart" --type ts -l

# Search for month view related files
echo "Searching for month view related files..."
rg "MonthView" --type ts -l

# Look for timeline layout components
echo "Searching for timeline layout files..."
fd -e ts -e tsx | rg -i "timeline|gantt"

# Search for test files specifically
echo "Searching for test files..."
fd . -t f | rg "(test|spec)\.(ts|tsx)$"

Length of output: 4331


Script:

#!/bin/bash
# Let's examine the integration points more closely

# Check how the month view is integrated with the chart root
echo "Checking month view integration in chart root..."
rg -A 5 "month-view" web/core/components/gantt-chart/chart/root.tsx

# Look for the actual usage of generateMonthChart
echo "Checking generateMonthChart usage context..."
rg -B 2 -A 2 "generateMonthChart" web/core/components/gantt-chart/views/month-view.ts

# Check the timeline drag helper implementation
echo "Checking timeline drag implementation..."
cat web/core/components/gantt-chart/chart/timeline-drag-helper.tsx

# Check the types definition
echo "Checking related type definitions..."
rg "ChartDataType|IMonthView" web/core/components/gantt-chart/types/index.ts

Length of output: 1668

web/core/components/issues/issue-detail/relation-select.tsx (3)

18-21: LGTM! Well-organized imports.

The new imports for timeline relations are properly organized and align with the PR's objective of improving relation handling.


46-46: Verify hook memoization for performance.

The switch to dynamic relation options using useTimeLineRelationOptions is a good improvement. However, let's ensure the hook is properly memoized to prevent unnecessary re-renders.

Also applies to: 74-75


114-114: Verify dark mode compatibility.

The dynamic class names and placeholders from currRelationOption look good. This change aligns with the PR's goal of improving dark mode support.

Also applies to: 145-145

web/core/components/gantt-chart/views/week-view.ts (2)

41-41: LGTM: Function signature change maintains backward compatibility.

The addition of the optional targetDate parameter enhances the function's flexibility while maintaining backward compatibility. This change aligns well with the PR objective of implementing scrolling to specific blocks.


74-83: 🛠️ Refactor suggestion

Review date boundary handling in left-side calculations.

The current implementation has potential edge cases:

  1. Setting minusDate to the first day of the month might skip weeks if the target date isn't at the start of a month
  2. Month decrement operation doesn't handle year boundaries explicitly

Let's verify the date handling:

Consider this safer implementation:

-    minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
+    // Safely handle month/year boundaries
+    const targetMonth = new Date(currentDate.getTime());
+    targetMonth.setMonth(targetMonth.getMonth() - range);
+    minusDate = new Date(targetMonth.getFullYear(), targetMonth.getMonth(), currentDate.getDate());
web/core/components/gantt-chart/chart/root.tsx (2)

Line range hint 89-99: LGTM: Function signature update enhances chart navigation.

The addition of the optional targetDate parameter aligns well with the PR objective of enabling scrolling to specific blocks. The implementation maintains backward compatibility with existing calls.


141-145: 🛠️ Refactor suggestion

Consider adding null checks and error handling for DOM operations.

The DOM query for #gantt-container could return null if the component hasn't mounted yet. Consider adding error boundaries or defensive programming.

Let's verify the container's existence across the codebase:

Consider this safer implementation:

 const updateItemsContainerWidth = (width: number) => {
   const scrollContainer = document.querySelector("#gantt-container") as HTMLDivElement;
   if (!scrollContainer) return;
-  setItemsContainerWidth(width + scrollContainer?.scrollLeft);
+  setItemsContainerWidth(width + (scrollContainer.scrollLeft || 0));
 };
web/core/components/gantt-chart/chart/main-content.tsx (3)

8-13: LGTM: Import additions are appropriate.

The new imports support the added scroll-to-block functionality and type safety improvements.

Also applies to: 21-21


53-57: LGTM: Props type changes enhance scroll functionality.

The updated signature with optional targetDate and explicit return type improves type safety while supporting the new scroll-to-block feature.


197-197: LGTM: Clean integration of scroll functionality.

The handleScrollToBlock prop is correctly passed to GanttChartRowList, maintaining good component composition.

web/core/store/issue/issue-details/relation.store.ts (2)

8-9: LGTM: Clean import addition

The import of TRelationObject is properly placed and necessary for the updated method signature.


43-46: Verify all callers of getRelationCountByIssueId

The method signature change is well-typed but introduces a breaking change. Let's verify all callers have been updated.

✅ Verification successful

All callers have been properly updated with the new parameter

The verification shows that all callers of getRelationCountByIssueId are already passing the required ISSUE_RELATION_OPTIONS parameter:

  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx
  • web/core/components/issues/issue-detail-widgets/relations/title.tsx

Both implementations are using the method with the correct signature, making this a safe change.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of getRelationCountByIssueId to verify they've been updated with the new parameter

# Search for method calls
echo "Searching for getRelationCountByIssueId usage patterns..."
rg "getRelationCountByIssueId\(" --type ts --type tsx -A 2

Length of output: 213


Script:

#!/bin/bash
# Let's try a different approach using ripgrep without file type filters
echo "Searching for getRelationCountByIssueId usage patterns..."
rg "getRelationCountByIssueId\(" -A 2

# Also search for any file containing this method name to ensure we don't miss anything
echo -e "\nSearching for files containing the method name..."
rg -l "getRelationCountByIssueId"

# Let's also check the interface definition location
echo -e "\nSearching for interface or type definitions..."
ast-grep --pattern 'interface $_ {
  $$$
  getRelationCountByIssueId
  $$$
}'

Length of output: 1393

apiserver/plane/app/views/issue/relation.py (1)

Line range hint 321-329: LGTM! Verify timeline relation serialization.

The addition of "start_after" and "finish_after" to the response serializer condition correctly handles timeline-specific relations. This aligns with the PR objective of fixing relation creation bugs.

Let's verify the serializer implementations:

✅ Verification successful

LGTM! The serializer implementation is correct for timeline relations

Both serializers (RelatedIssueSerializer and IssueRelationSerializer) include all necessary fields for timeline relations:

  • They both expose relation_type which handles the timeline-specific types ("start_after", "finish_after")
  • The model supports these relation types in its RELATION_CHOICES
  • Both serializers include issue details (id, name, project_id) needed for timeline visualization
  • The different serializer choice in the response is intentional as they provide different perspectives of the relation (issue vs. related_issue as the source)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that RelatedIssueSerializer and IssueRelationSerializer handle timeline relations correctly
# Expected: RelatedIssueSerializer should have fields necessary for timeline relations

# Search for serializer definitions
rg -A 10 "class (Related|Issue)RelationSerializer"

Length of output: 1751


Script:

#!/bin/bash
# Let's also check for RelatedIssueSerializer which was not found in the previous search
rg -A 10 "class RelatedIssueSerializer"

# Let's also check the IssueRelation model to understand the available fields
rg -A 10 "class IssueRelation"

Length of output: 6959

Comment on lines +6 to +17
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 `;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix critical bug in relates_to case and improve message consistency.

There are several issues in the switch statement:

  1. The relates_to case is missing a return statement, making it always return undefined
  2. Message formatting could be more consistent across cases

Apply these fixes:

   switch (activity.field) {
     case "blocking":
-      return activity.old_value === "" ? `marked this issue is blocking issue ` : `removed the blocking issue `;
+      return activity.old_value === "" ? `marked this issue as blocking ` : `removed blocking relation from `;
     case "blocked_by":
       return activity.old_value === ""
-        ? `marked this issue is being blocked by `
-        : `removed this issue being blocked by issue `;
+        ? `marked this issue as blocked by `
+        : `removed blocked by relation from `;
     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 activity.old_value === "" ? `marked this issue as related to ` : `removed relation from `;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 `;
}
switch (activity.field) {
case "blocking":
return activity.old_value === "" ? `marked this issue as blocking ` : `removed blocking relation from `;
case "blocked_by":
return activity.old_value === ""
? `marked this issue as blocked by `
: `removed blocked by relation from `;
case "duplicate":
return activity.old_value === "" ? `marked this issue as duplicate of ` : `removed this issue as a duplicate of `;
case "relates_to":
return activity.old_value === "" ? `marked this issue as related to ` : `removed relation from `;
}

<span className="font-medium text-custom-text-100">{activity.old_value}.</span>
)}
</>
{activityContent}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Found issue with relation type handling

There's a bug in the getRelationActivityContent implementation where the relates_to case is missing a return statement, which could lead to undefined content. Here's the problematic code:

case "relates_to":
  activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `; // missing return

All relation types from TIssueRelationTypes ("blocking", "blocked_by", "duplicate", "relates_to") are covered in the switch statement, but the relates_to case needs to be fixed.

🔗 Analysis chain

Verify handling of all relation types.

The simplified content rendering looks good, but let's ensure getRelationActivityContent handles all possible relation types correctly.


Let me gather more information about the relation content handling.


Let me check the complete implementation to verify all relation types are handled.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find the implementation of getRelationActivityContent
ast-grep --pattern 'function getRelationActivityContent($_) {
  $$$
}'

# Find all relation types used in the codebase
rg "TIssueRelationTypes.*=.*['\"](\w+)['\"]"

Length of output: 241


Script:

#!/bin/bash
# Find the implementation of getRelationActivityContent and relation handling
rg -A 10 "getRelationActivityContent" 

# Find any switch/case or if-else handling of relation types
ast-grep --pattern 'switch ($relation) {
  $$$
}'

# Also check for if-else patterns
ast-grep --pattern 'if ($relation === $_) {
  $$$
}'

Length of output: 3763


Script:

#!/bin/bash
# Get the complete implementation of getRelationActivityContent
rg -A 20 "export const getRelationActivityContent" web/ce/components/relations/activity.ts

# Also find any relation type constants or enums
rg "ISSUE_RELATION_OPTIONS" -A 5

Length of output: 11528

Comment on lines +42 to +60
{Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => {
if (!item) return <></>;

return (
<CustomMenu.MenuItem
key={index}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleOnClick(item.key as TIssueRelationTypes);
}}
>
<div className="flex items-center gap-2">
{item.icon(12)}
<span>{item.label}</span>
</div>
</CustomMenu.MenuItem>
);
})}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider improving menu item rendering robustness.

While the null check is a good addition, there are a few areas that could be improved:

  1. Using array index as key could cause issues with React's reconciliation if items are reordered
  2. The icon rendering assumes the icon function exists
-      {Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => {
+      {Object.values(ISSUE_RELATION_OPTIONS).map((item) => {
         if (!item) return <></>;
 
         return (
           <CustomMenu.MenuItem
-            key={index}
+            key={item.key}
             onClick={(e) => {
               e.preventDefault();
               e.stopPropagation();
               handleOnClick(item.key as TIssueRelationTypes);
             }}
           >
             <div className="flex items-center gap-2">
-              {item.icon(12)}
+              {item.icon ? item.icon(12) : null}
               <span>{item.label}</span>
             </div>
           </CustomMenu.MenuItem>
         );
       })}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => {
if (!item) return <></>;
return (
<CustomMenu.MenuItem
key={index}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleOnClick(item.key as TIssueRelationTypes);
}}
>
<div className="flex items-center gap-2">
{item.icon(12)}
<span>{item.label}</span>
</div>
</CustomMenu.MenuItem>
);
})}
{Object.values(ISSUE_RELATION_OPTIONS).map((item) => {
if (!item) return <></>;
return (
<CustomMenu.MenuItem
key={item.key}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleOnClick(item.key as TIssueRelationTypes);
}}
>
<div className="flex items-center gap-2">
{item.icon ? item.icon(12) : null}
<span>{item.label}</span>
</div>
</CustomMenu.MenuItem>
);
})}

Comment on lines +67 to 68
case activityRelations.find((field) => field === activityField):
return <IssueRelationActivity {...componentDefaultProps} />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize relation field check

The current implementation using array.find() is less efficient and harder to read. Using array.includes() would be more appropriate for this use case as we're only checking for membership.

-    case activityRelations.find((field) => field === activityField):
+    case activityRelations.includes(activityField) ? activityField : undefined:

This change:

  1. Improves performance by avoiding unnecessary iteration
  2. Makes the intent clearer - we're checking if the field is a valid relation
  3. Maintains the same behavior when no match is found
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case activityRelations.find((field) => field === activityField):
return <IssueRelationActivity {...componentDefaultProps} />;
case activityRelations.includes(activityField) ? activityField : undefined:
return <IssueRelationActivity {...componentDefaultProps} />;

Comment on lines +91 to +99
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Review date boundary handling in right-side calculations.

Similar to the left-side calculations, there are potential edge cases:

  1. Setting plusDate to the first day of the month might create inconsistent week ranges
  2. Month increment operation doesn't handle year boundaries explicitly

Consider this safer implementation:

-    plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1);
+    // Safely handle month/year boundaries
+    const targetMonth = new Date(currentDate.getTime());
+    targetMonth.setMonth(targetMonth.getMonth() + range);
+    plusDate = new Date(targetMonth.getFullYear(), targetMonth.getMonth(), currentDate.getDate());

Additionally, consider adding validation to ensure startDate is always before endDate:

if (startDate >= endDate) {
  throw new Error('Invalid date range: startDate must be before endDate');
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants