Skip to content

[WEB-2442] feat: Revamp Timeline Layout#5915

Merged
SatishGandham merged 24 commits intopreviewfrom
chore/issue-listing-relations
Oct 28, 2024
Merged

[WEB-2442] feat: Revamp Timeline Layout#5915
SatishGandham merged 24 commits intopreviewfrom
chore/issue-listing-relations

Conversation

@rahulramesha
Copy link
Contributor

@rahulramesha rahulramesha commented Oct 25, 2024

This PR Revamps the Timeline layout to have three views, instead of previously available month view. These also include other minor improvements in performance and UX

The new Timeline Views are as follows,

Week View

Screen.Recording.2024-10-25.at.7.57.37.PM.mov

Month View

Screen.Recording.2024-10-25.at.7.58.30.PM.mov

Quarter View

Screen.Recording.2024-10-25.at.7.59.26.PM.mov

Summary by CodeRabbit

  • New Features

    • Introduced new API endpoints for detailed issue retrieval and bulk date updates.
    • Added a new serializer to handle related issue data.
    • Enhanced Gantt chart functionality with new draggable components and resizing capabilities.
    • Implemented a centralized definition for issue relation options.
  • Bug Fixes

    • Improved handling of issue relations and visibility in the sidebar.
  • Documentation

    • Updated type definitions and constants for better clarity and usability.
  • Chores

    • Removed deprecated components and streamlined code structure for better maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2024

Walkthrough

This pull request introduces several enhancements and modifications across various components of the application. Key changes include the addition of new serializers and endpoints for issue management, updates to the handling of issue relations, and the introduction of new components for Gantt chart functionality. Additionally, several existing components and files have been removed or refactored to streamline the codebase, particularly in the Gantt chart section, which now utilizes a more structured approach to data management and rendering.

Changes

File Path Change Summary
apiserver/plane/app/serializers/base.py Added RelatedIssueSerializer and updated expansion mapping logic for "issue_related" field.
apiserver/plane/app/urls/issue.py Introduced IssueDetailEndpoint and IssueBulkUpdateDateEndpoint URL patterns.
apiserver/plane/app/views/__init__.py Added imports for IssueDetailEndpoint and IssueBulkUpdateDateEndpoint.
apiserver/plane/app/views/issue/base.py Added IssueDetailEndpoint and IssueBulkUpdateDateEndpoint classes with respective methods.
apiserver/plane/app/views/issue/relation.py Enhanced IssueRelationViewSet with new filters and updated relation type handling.
apiserver/plane/bgtasks/issue_activities_task.py Updated create_issue_relation_activity to use get_inverse_relation for determining inverse relationship type.
apiserver/plane/db/models/issue.py Added new relation types "start_before" and "finish_before" to RELATION_CHOICES.
apiserver/plane/utils/issue_relation_mapper.py Introduced get_inverse_relation and get_actual_relation functions.
packages/tailwind-config-custom/tailwind.config.js Added 2xs font size, updated spacing utilities to 90%, and added new onboarding gradient properties.
packages/types/src/dashboard.d.ts Updated import for TIssueRelationTypes source path.
packages/types/src/issues/issue.d.ts Added IssueRelation type and updated TIssue type with new relation properties.
packages/types/src/issues/issue_relation.d.ts Updated to import TIssueRelationTypes instead of local definition.
packages/types/src/view-props.d.ts Updated TIssueParams type to include "expand" option.
web/ce/components/gantt-chart/dependency/blockDraggables/index.ts Re-exported all entities from left-draggable and right-draggable.
web/ce/components/gantt-chart/dependency/blockDraggables/left-draggable.tsx Added LeftDependencyDraggable component.
web/ce/components/gantt-chart/dependency/blockDraggables/right-draggable.tsx Added RightDependencyDraggable component.
web/ce/components/gantt-chart/dependency/dependency-paths.tsx Introduced TimelineDependencyPaths component.
web/ce/components/gantt-chart/dependency/draggable-dependency-path.tsx Added TimelineDraggablePath component.
web/ce/components/gantt-chart/dependency/index.ts Re-exported all entities from related modules.
web/ce/components/gantt-chart/index.ts Re-exported all entities from ./dependency.
web/ce/constants/gantt-chart.ts Introduced REVERSE_RELATIONS constant for issue relation types.
web/ce/constants/index.ts Added new export for constants from various modules.
web/ce/constants/issues.ts Added ENABLE_ISSUE_DEPENDENCIES constant.
web/ce/store/timeline/base-timeline.store.ts Introduced MobX-based store for managing Gantt chart timeline.
web/ce/types/gantt-chart.ts Added TIssueRelationTypes type alias.
web/ce/types/index.ts Exported gantt-chart module.
web/core/components/api-token/modal/form.tsx Updated return type of getExpiryDate function.
web/core/components/core/render-if-visible-HOC.tsx Added forceRender property to RenderIfVisible component.
web/core/components/cycles/gantt-chart/blocks.tsx Removed CycleGanttBlock and CycleGanttSidebarBlock components.
web/core/components/cycles/gantt-chart/cycles-list-layout.tsx Removed CyclesListGanttChartView component.
web/core/components/cycles/gantt-chart/index.ts Removed index file that re-exported components.
web/core/components/cycles/index.ts Removed export for gantt-chart module.
web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx Removed issueTypeId property from IssueIdentifier component.
web/core/components/gantt-chart/blocks/block-row-list.tsx Added GanttChartRowList component.
web/core/components/gantt-chart/blocks/block-row.tsx Added BlockRow component.
web/core/components/gantt-chart/blocks/block.tsx Updated GanttChartBlock component to streamline props and logic.
web/core/components/gantt-chart/blocks/blocks-list.tsx Updated GanttChartBlocksProps type and GanttChartBlocksList component.
web/core/components/gantt-chart/chart/header.tsx Updated GanttChartHeader to use useTimeLineChartStore for state management.
web/core/components/gantt-chart/chart/main-content.tsx Updated GanttChartMainContent to streamline props and rendering logic.
web/core/components/gantt-chart/chart/root.tsx Updated GanttChartRoot to integrate MobX and new props structure.
web/core/components/gantt-chart/sidebar/cycles/block.tsx Removed CyclesSidebarBlock component.
web/core/components/gantt-chart/sidebar/cycles/sidebar.tsx Removed CycleGanttSidebar component.
web/core/components/gantt-chart/sidebar/index.ts Removed export for cycles module.
web/core/components/gantt-chart/sidebar/issues/block.tsx Updated IssuesSidebarBlock to use new store hook for duration calculation.
web/core/components/gantt-chart/sidebar/issues/sidebar.tsx Updated IssueGanttSidebar to retrieve getBlockById from the new store.
web/core/components/gantt-chart/sidebar/modules/block.tsx Updated ModulesSidebarBlock to use blockId instead of block.
web/core/components/gantt-chart/sidebar/modules/sidebar.tsx Updated rendering logic to pass blockId directly.
web/core/components/gantt-chart/sidebar/root.tsx Removed getBlockById from GanttChartSidebar props.
web/core/components/gantt-chart/types/index.ts Updated IGanttBlock interface and related types.
web/core/components/gantt-chart/views/index.ts Updated export statements for view components.
web/core/components/gantt-chart/views/month-view.ts Updated month view data handling and introduced new interfaces.
web/core/components/gantt-chart/views/quarter-view.ts Introduced quarter view data handling functionality.
web/core/components/gantt-chart/views/year-view.ts Removed year view functionality.
web/core/components/issues/issue-detail-widgets/relations/content.tsx Exported ISSUE_RELATION_OPTIONS constant.

Suggested labels

⚙️backend

Suggested reviewers

  • sriramveeraghanta
  • SatishGandham
  • pushya22

🐇 In the meadow, changes bloom,
New serializers chase away the gloom.
Gantt charts dance with blocks anew,
With every update, our hopes renew.
Let's hop along, with joy and cheer,
For every change brings us near! 🌼


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 feat: Revamp Timeline Layout [WEB-2442] feat: Revamp Timeline Layout Oct 25, 2024
@rahulramesha rahulramesha added 🌟enhancement New feature or request 🌐frontend labels Oct 25, 2024
@rahulramesha rahulramesha marked this pull request as ready for review October 25, 2024 14:43
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: 57

🧹 Outside diff range and nitpick comments (56)
web/core/components/gantt-chart/constants.ts (1)

5-5: LGTM: New breadcrumbs height constant enhances navigation.

The addition of GANTT_BREADCRUMBS_HEIGHT supports the PR's objective of revamping the Timeline layout by providing dedicated space for navigation between Week, Month, and Quarter views.

Consider implementing responsive breakpoints if the combined height of header (48px) and breadcrumbs (40px) becomes too large on smaller screens.

web/core/components/gantt-chart/contexts/index.tsx (1)

1-13: Consider documenting the timeline architecture.

As this is part of a larger Timeline revamp, it would be beneficial to:

  1. Add JSDoc comments explaining how this context fits into the new Week/Month/Quarter view architecture
  2. Consider creating a README.md in the gantt-chart directory documenting the overall architecture and data flow

Would you like me to help generate the documentation structure?

web/core/components/gantt-chart/chart/timeline-drag-helper.tsx (1)

1-7: Consider organizing imports following best practices.

The imports could be better organized by grouping them into:

  1. External dependencies (react, mobx)
  2. Internal hooks
  3. Constants
 import { RefObject } from "react";
 import { observer } from "mobx-react";
-// hooks
+
 import { useAutoScroller } from "@/hooks/use-auto-scroller";
 import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
-//
+
 import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
apiserver/plane/utils/issue_relation_mapper.py (1)

1-10: Add docstring with type hints for better code documentation.

The function would benefit from a clear docstring explaining its purpose, parameters, and return value.

Consider adding:

 def get_inverse_relation(relation_type):
+    """
+    Returns the inverse relation type for a given relation.
+    
+    Args:
+        relation_type (str): The relation type to inverse
+        
+    Returns:
+        str: The inverse relation type, or the original if no inverse exists
+    """
web/core/components/gantt-chart/types/index.ts (1)

24-28: Add JSDoc comments to document the new interface.

The new IBlockUpdateDependencyData interface would benefit from documentation explaining its purpose and usage in dependency management.

+/**
+ * Interface for updating block dependencies in the Gantt chart
+ * @property id - The unique identifier of the block
+ * @property start_date - Optional new start date for the dependency
+ * @property target_date - Optional new target date for the dependency
+ */
 export interface IBlockUpdateDependencyData {
   id: string;
   start_date?: string;
   target_date?: string;
 }
web/core/components/gantt-chart/blocks/blocks-list.tsx (1)

1-14: LGTM! Good architectural improvements.

The removal of props like getBlockById and blockUpdateHandler in favor of a single async updateBlockDates function shows good architectural evolution. This change:

  • Simplifies the component's API
  • Provides better control over block updates through Promise-based operations
  • Suggests a move towards more centralized state management

Consider documenting the expected format and validation requirements for the IBlockUpdateDependencyData type to help other developers integrate with this component.

web/core/components/gantt-chart/sidebar/modules/block.tsx (2)

25-26: Consider improving visibility check readability.

The visibility check could be more explicit about its purpose.

- const isBlockVisibleOnChart = !!block.start_date && !!block.target_date;
+ const hasValidDateRange = !!block.start_date && !!block.target_date;
+ const isBlockVisibleOnChart = hasValidDateRange;

Line range hint 28-57: Consider extracting styles and duration text for better maintainability.

The component could benefit from:

  1. Moving styles to constants
  2. Extracting the duration text to a reusable component

Example refactor:

// constants.ts
export const SIDEBAR_BLOCK_STYLES = {
  wrapper: "group w-full flex items-center gap-2 pr-4",
  active: "bg-custom-background-90",
  dragging: "rounded bg-custom-background-80"
};

// DurationText.tsx
interface DurationTextProps {
  days: number;
}

export const DurationText: React.FC<DurationTextProps> = ({ days }) => (
  <div className="flex-shrink-0 text-sm text-custom-text-200">
    {days} day{days > 1 ? "s" : ""}
  </div>
);
web/core/components/gantt-chart/blocks/block-row-list.tsx (2)

11-18: Consider improving type safety for blockUpdateHandler.

The blockUpdateHandler prop uses any type for the block parameter, which bypasses TypeScript's type checking. Consider defining a specific interface for the block structure to improve type safety and maintainability.

Example improvement:

interface IGanttBlock {
  id: string;
  // add other block properties
}

export type GanttChartBlocksProps = {
  // ... other props
  blockUpdateHandler: (block: IGanttBlock, payload: IBlockUpdateData) => void;
  // ... remaining props
};

28-30: Consider extracting magic numbers to constants.

The horizontal and vertical offset values (100 and 200) should be defined as named constants for better maintainability and reusability.

Example improvement:

const HORIZONTAL_SCROLL_OFFSET = 100;
const VERTICAL_SCROLL_OFFSET = 200;

// Then use in component:
horizontalOffset={HORIZONTAL_SCROLL_OFFSET}
verticalOffset={VERTICAL_SCROLL_OFFSET}
web/core/components/gantt-chart/helpers/blockResizables/right-resizable.tsx (1)

34-38: Consider extracting z-index values to constants.

The component uses hardcoded z-index values (z-[10], z-[6], z-[5]). These should be extracted to constants or a configuration file to maintain consistency across the application.

Create a constants file for z-index values:

// constants/z-index.ts
export const ZINDEX = {
  TOOLTIP: 10,
  RESIZE_HANDLE: 6,
  RESIZE_INDICATOR: 5,
} as const;

Then update the component:

- className="z-[10] absolute flex..."
+ className={`z-[${ZINDEX.TOOLTIP}] absolute flex...`}

Also applies to: 49-56

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

32-35: Consider adding type safety for the height value.

The Row component's height styling looks good, using a constant for consistency. However, we could enhance type safety.

-  style={{ height: `${GANTT_BREADCRUMBS_HEIGHT}px` }}
+  style={{ height: GANTT_BREADCRUMBS_HEIGHT ? `${GANTT_BREADCRUMBS_HEIGHT}px` : undefined }}
packages/types/src/issues/issue.d.ts (2)

5-5: Consider relocating TIssueRelationTypes to avoid cross-package dependencies.

The import path suggests that TIssueRelationTypes is defined in the plane-web package. Since this is a core type definition used for issue relations, it would be more appropriate to define it within the types package to maintain a clear separation of concerns and prevent potential circular dependencies.

Consider moving TIssueRelationTypes from plane-web to this package, possibly in a new file like issue-relation-types.ts.


44-50: Add JSDoc comments to document the IssueRelation type.

While the type definition is well-structured, adding documentation would improve maintainability and help other developers understand the purpose of each field, especially relation_type and sequence_id.

Consider adding documentation like this:

+/**
+ * Represents a relationship between two issues
+ */
 export type IssueRelation = {
+  /** Unique identifier for the relation */
   id: string;
+  /** Name of the related issue */
   name: string;
+  /** Project identifier containing the related issue */
   project_id: string;
+  /** Type of relationship between issues */
   relation_type: TIssueRelationTypes;
+  /** Order of the relation in the list */
   sequence_id: number;
 };
web/core/components/gantt-chart/root.tsx (1)

Line range hint 1-28: Well-structured integration of MobX for state management!

The addition of MobX observer pattern and the well-typed props interface provides a solid foundation for reactive state management. This aligns well with the Timeline revamp objectives.

Consider documenting the feature toggle props (enable*) in JSDoc to help other developers understand when to use each flag.

web/core/components/core/render-if-visible-HOC.tsx (1)

Line range hint 69-75: Optimize height recording when forceRender is true

The component still records heights even when forceRender is true, which may be unnecessary overhead.

Consider skipping height recording when forcing render:

  useEffect(() => {
-   if (intersectionRef.current && isVisible && shouldRecordHeights) {
+   if (intersectionRef.current && isVisible && shouldRecordHeights && !forceRender) {
      window.requestIdleCallback(() => {
        if (intersectionRef.current) placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`;
      });
    }
-  }, [isVisible, intersectionRef, shouldRecordHeights]);
+  }, [isVisible, intersectionRef, shouldRecordHeights, forceRender]);
web/core/components/gantt-chart/helpers/add-block.tsx (1)

Line range hint 85-97: Enhance accessibility for the add button.

Consider improving accessibility for keyboard and screen reader users:

 <button
   type="button"
   className="absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-8 w-8 bg-custom-background-80 p-1.5 rounded border border-custom-border-300 grid place-items-center text-custom-text-200 hover:text-custom-text-100"
   style={{
     marginLeft: `${buttonXPosition}px`,
   }}
   onClick={handleButtonClick}
+  aria-label={`Add block starting from ${buttonStartDate ? renderFormattedDate(buttonStartDate) : 'selected date'}`}
+  onKeyDown={(e) => {
+    if (e.key === 'ArrowLeft') setButtonXPosition(prev => Math.max(0, prev - currentViewData.data.dayWidth));
+    if (e.key === 'ArrowRight') setButtonXPosition(prev => prev + currentViewData.data.dayWidth);
+  }}
 >
web/core/components/gantt-chart/sidebar/issues/block.tsx (1)

30-31: Consider adding fallback for undefined position width.

While the visibility check is good, there's no explicit handling for cases where block?.position?.width might be undefined. Consider adding a fallback value or additional validation.

Suggested improvement:

-  const duration = isBlockVisibleOnChart ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
+  const duration = isBlockVisibleOnChart && block?.position?.width !== undefined
+    ? getNumberOfDaysFromPosition(block?.position?.width)
+    : undefined;
web/core/components/gantt-chart/data/index.ts (3)

6-12: Standardize day abbreviations according to conventions.

The abbreviation "Th" for Thursday should be changed to "Thu" to follow the common three-letter abbreviation convention used in most date libraries.

-  { key: 4, shortTitle: "thurs", title: "thursday", abbreviation: "Th" },
+  { key: 4, shortTitle: "thurs", title: "thursday", abbreviation: "Thu" },

16-28: Standardize month abbreviations to three letters.

The abbreviation "Sept" for September should be "Sep" to maintain consistency with the three-letter abbreviation pattern used for other months.

-  { key: 8, shortTitle: "sept", title: "september", abbreviation: "Sept" },
+  { key: 8, shortTitle: "sept", title: "september", abbreviation: "Sep" },

64-94: Enhance documentation for view configurations.

The view configurations would benefit from clearer documentation:

  1. Standardize comment style (all should end with periods).
  2. Add explanations for the significant differences in dayWidth values.
  3. Clarify the approxFilterRange impact on view behavior.
 {
   key: "week",
   title: "Week",
   data: {
     startDate: new Date(),
     currentDate: new Date(),
     endDate: new Date(),
-    approxFilterRange: 4, // it will preview week dates with weekends highlighted with 1 week limitations ex: title (Wed 1, Thu 2, Fri 3)
+    approxFilterRange: 4, // Controls the week view range. Shows 4 weeks with weekends highlighted. Example: "Wed 1, Thu 2, Fri 3".
     dayWidth: 60, // Wider width for detailed daily view
   },
 },
 {
   key: "month",
   title: "Month",
   data: {
     startDate: new Date(),
     currentDate: new Date(),
     endDate: new Date(),
-    approxFilterRange: 6, // it will preview monthly all dates with weekends highlighted with no limitations ex: title (1, 2, 3)
+    approxFilterRange: 6, // Controls the month view range. Shows 6 months with all dates and highlighted weekends. Example: "1, 2, 3".
     dayWidth: 20, // Medium width for monthly overview
   },
 },
 {
   key: "quarter",
   title: "Quarter",
   data: {
     startDate: new Date(),
     currentDate: new Date(),
     endDate: new Date(),
-    approxFilterRange: 18, // it will preview week starting dates all months data and there is 3 months limitation for preview ex: title (2, 9, 16, 23, 30)
+    approxFilterRange: 18, // Controls the quarter view range. Shows 18 months with week starting dates. Example: "2, 9, 16, 23, 30".
     dayWidth: 5, // Compact width for quarterly overview
   },
 },
packages/types/src/dashboard.d.ts (1)

Line range hint 1-150: Consider enhancing types for Timeline views

Given that this PR introduces Week, Month, and Quarter views for Timeline, consider:

  1. Adding specific widget types for Timeline views
  2. Including view-specific configuration types
  3. Adding types for Timeline-specific filters and responses

This would improve type safety and developer experience when working with the new Timeline features.

Example additions to consider:

export type TTimelineViewType = "week" | "month" | "quarter";

export type TTimelineWidgetFilters = {
  view_type: TTimelineViewType;
  custom_dates?: string[];
  duration?: EDurationFilters;
};

export type TTimelineWidgetResponse = {
  // Add specific response structure for Timeline widgets
};
web/core/components/gantt-chart/chart/views/month.tsx (3)

3-11: LGTM! Consider adding type safety to the component props.

The hook and type changes align well with the new Timeline layout implementation. However, the component is using FC<any> which bypasses TypeScript's type checking.

Consider defining a proper interface for the component props:

interface MonthChartViewProps {
  // Add any props if needed, or leave empty if none required
}

export const MonthChartView: FC<MonthChartViewProps> = observer(() => {

28-61: Consider adding ARIA labels for better accessibility.

The month header implementation looks good, but could benefit from improved accessibility.

Consider adding ARIA labels to improve screen reader experience:

 <div
   key={`month-${monthBlock?.month}-${monthBlock?.year}`}
+  aria-label={`Month: ${monthBlock?.title}`}
   className="flex outline-[0.5px] outline outline-custom-border-200"
   style={{ width: `${monthBlock.days * currentViewData?.data.dayWidth}px` }}
 >

62-88: Consider memoizing week blocks for better performance.

The week header implementation looks good, but could benefit from memoization to prevent unnecessary re-renders.

Consider using useMemo for the week blocks:

const weekBlocks = useMemo(
  () => weeks?.map((weekBlock) => (
    <div
      key={`sub-title-${weekBlock.startDate}-${weekBlock.endDate}`}
      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,
        }
      )}
      style={{ width: `${currentViewData?.data.dayWidth * 7}px` }}
    >
      {/* ... existing content ... */}
    </div>
  )),
  [weeks, currentViewData?.data.dayWidth]
);
apiserver/plane/app/views/__init__.py (1)

133-134: Consider adding type hints to improve code maintainability.

Since these endpoints are crucial for the Timeline Layout feature, consider adding type hints to improve code maintainability and IDE support.

-    IssueDetailEndpoint,
-    IssueBulkUpdateDateEndpoint,
+    IssueDetailEndpoint,  # type: Type[ViewSet]
+    IssueBulkUpdateDateEndpoint,  # type: Type[ViewSet]
🧰 Tools
🪛 Ruff

133-133: .issue.base.IssueDetailEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


134-134: .issue.base.IssueBulkUpdateDateEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

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

Line range hint 23-45: Consider enhancing type safety for relation options.

The export of ISSUE_RELATION_OPTIONS is a good move for reusability. However, we could improve type safety.

Consider adding a type definition for the relation options:

type IssueRelationType = {
  key: 'blocked_by' | 'blocking' | 'relates_to' | 'duplicate';
  label: string;
  icon: (size: number) => JSX.Element;
  className: string;
};

export const ISSUE_RELATION_OPTIONS: IssueRelationType[] = [
  // ... existing options
];

Line range hint 89-91: Consider enhancing error handling.

While the null check for relations is good, consider adding more comprehensive error handling for edge cases.

Consider implementing:

  1. Error boundaries to catch rendering errors
  2. Loading states for async operations
  3. Fallback UI for failed relation fetches
// Example error boundary implementation
class RelationsErrorBoundary extends React.Component {
  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <div className="text-red-500">Failed to load relations</div>;
    }
    return this.props.children;
  }
}

Line range hint 47-63: Consider simplifying modal state management.

The current modal state management works but could be simplified for better maintainability.

Consider using a discriminated union for modal state:

type ModalState =
  | { type: 'none' }
  | { type: 'update'; issue: TIssue }
  | { type: 'delete'; issue: TIssue };

const [modalState, setModalState] = useState<ModalState>({ type: 'none' });

This would simplify your conditional rendering and state updates while maintaining type safety.

web/core/components/api-token/modal/form.tsx (2)

Line range hint 53-65: Remove unnecessary undefined from return type.

The function's implementation only returns either a string (when a valid date option is found) or null (when no option is found). The undefined in the return type signature is unnecessary and could mislead consumers of this function.

Apply this change:

-const getExpiryDate = (val: string): string | null | undefined => {
+const getExpiryDate = (val: string): string | null => {

Line range hint 190-195: Add null check before displaying expiry date.

The expiry date text is displayed without checking if getExpiryDate returns null, which could result in "Expires null" being shown to users.

Apply this change:

{!neverExpires && (
  <span className="text-xs text-custom-text-400">
    {expiredAt === "custom"
      ? customDate
        ? `Expires ${renderFormattedDate(customDate)}`
        : null
      : expiredAt
-       ? `Expires ${getExpiryDate(expiredAt ?? "")}`
+       ? (() => {
+           const expiryDate = getExpiryDate(expiredAt ?? "");
+           return expiryDate ? `Expires ${expiryDate}` : null;
+         })()
        : null}
  </span>
)}
apiserver/plane/app/serializers/base.py (3)

Line range hint 93-110: Consider using a set for many-to-many field names.

The list of fields requiring many=True is growing and could be better organized. Consider using a class-level set for better performance and readability.

class DynamicBaseSerializer(BaseSerializer):
    MANY_TO_MANY_FIELDS = {
        "members",
        "assignees",
        "labels",
        "issue_cycle",
        "issue_relation",
        "issue_inbox",
        "issue_reactions",
        "issue_attachment",
        "issue_link",
        "sub_issues",
        "issue_related",
    }

    def _filter_fields(self, fields):
        # ...
        many = field in self.MANY_TO_MANY_FIELDS
        self.fields[field] = expansion[field](many=many)

Line range hint 140-161: Refactor to_representation for better maintainability and error handling.

The method is handling multiple responsibilities and could benefit from being broken down into smaller, focused methods. Additionally, error handling could be improved when accessing attributes.

Consider breaking down the method:

class DynamicBaseSerializer(BaseSerializer):
    def _expand_field(self, instance, field_name, expansion_map):
        try:
            value = getattr(instance, field_name)
            serializer_class = expansion_map[field_name]
            return serializer_class(value, many=isinstance(value, list)).data
        except AttributeError:
            return getattr(instance, f"{field_name}_id", None)
        except Exception as e:
            logger.error(f"Error expanding field {field_name}: {str(e)}")
            return None

    def to_representation(self, instance):
        response = super().to_representation(instance)
        if not self.expand:
            return response

        expansion_map = get_serializer_map()
        for expand in self.expand:
            if expand in self.fields and expand in expansion_map:
                response[expand] = self._expand_field(instance, expand, expansion_map)

        return response

Line range hint 1-161: Changes approved with recommendations for improvement.

The changes to add RelatedIssueSerializer and update the expansion logic are functionally correct. However, consider implementing the suggested improvements:

  1. Extract serializer imports and mappings to a separate module
  2. Use class-level constants for configuration
  3. Break down complex methods into smaller, focused ones
  4. Improve error handling

These changes will make the code more maintainable and robust while reducing duplication.

apiserver/plane/app/urls/issue.py (1)

318-322: Consider grouping bulk operation endpoints together.

While the endpoint is well-structured, consider moving it near other bulk operation endpoints (around line 90-100) for better code organization and maintainability.

# Move this block:
-    path(
-        "workspaces/<str:slug>/projects/<uuid:project_id>/issue-dates/",
-        IssueBulkUpdateDateEndpoint.as_view(),
-        name="project-issue-dates",
-    ),

# After the existing bulk operations around line 90:
     path(
         "workspaces/<str:slug>/projects/<uuid:project_id>/bulk-archive-issues/",
         BulkArchiveIssuesEndpoint.as_view(),
         name="bulk-archive-issues",
     ),
+    path(
+        "workspaces/<str:slug>/projects/<uuid:project_id>/issue-dates/",
+        IssueBulkUpdateDateEndpoint.as_view(),
+        name="project-issue-dates",
+    ),
apiserver/plane/app/views/issue/relation.py (1)

297-297: Consider logging ignored conflicts.

The ignore_conflicts=True in bulk_create silently ignores duplicate relations. Consider logging these for debugging purposes.

+from django.db import IntegrityError
+from plane.utils.logger import logger
+
+# Track attempted and created relations
+attempted_relations = len(issues)
+created_relations = len(issue_relation)
+
+if created_relations < attempted_relations:
+    logger.info(
+        f"Ignored {attempted_relations - created_relations} duplicate relations "
+        f"for issue {issue_id} in project {project_id}"
+    )
packages/tailwind-config-custom/tailwind.config.js (2)

Line range hint 397-401: Consider documenting gradient variables.

The new onboarding gradient backgrounds are added, but their CSS variable definitions (--gradient-onboarding-*) should be documented for consistency.

Consider adding a comment block above the backgroundImage section to document the expected values of these CSS variables:

+      // Onboarding gradient variables should be defined as CSS gradients
+      // Example: --gradient-onboarding-100: linear-gradient(to right, #color1, #color2)
       backgroundImage: {
         "onboarding-gradient-100": "var( --gradient-onboarding-100)",
         "onboarding-gradient-200": "var( --gradient-onboarding-200)",
         "onboarding-gradient-300": "var( --gradient-onboarding-300)",
       },

Line range hint 397-401: Fix whitespace in gradient variable references.

There's an extra space after var( in the gradient definitions which should be removed for consistency.

       backgroundImage: {
-        "onboarding-gradient-100": "var( --gradient-onboarding-100)",
-        "onboarding-gradient-200": "var( --gradient-onboarding-200)",
-        "onboarding-gradient-300": "var( --gradient-onboarding-300)",
+        "onboarding-gradient-100": "var(--gradient-onboarding-100)",
+        "onboarding-gradient-200": "var(--gradient-onboarding-200)",
+        "onboarding-gradient-300": "var(--gradient-onboarding-300)",
       },
apiserver/plane/db/models/issue.py (1)

282-283: LGTM! Consider adding temporal constraint validations.

The new relation types "start_before" and "finish_before" are well-named and align with the timeline/Gantt chart requirements.

Consider implementing the following validations in your model or service layer:

  1. Prevent circular dependencies (e.g., A start_before B, B start_before A)
  2. Validate conflicting temporal constraints
  3. Handle cascading updates when related issue dates change

Example validation in the model:

def clean(self):
    super().clean()
    if self.relation_type in ['start_before', 'finish_before']:
        # Prevent circular dependencies
        if IssueRelation.objects.filter(
            issue=self.related_issue,
            related_issue=self.issue,
            relation_type=self.relation_type
        ).exists():
            raise ValidationError(
                f"Circular {self.relation_type} dependency detected"
            )
        
        # Validate temporal constraints
        if self.relation_type == 'start_before':
            if (self.issue.start_date and 
                self.related_issue.start_date and 
                self.issue.start_date > self.related_issue.start_date):
                raise ValidationError(
                    "Issue cannot start after its dependent issue"
                )
web/core/components/gantt-chart/blocks/block.tsx (1)

76-76: Ensure placeholder height matches BLOCK_HEIGHT

The placeholder's height is set to h-8, which may not correspond to the BLOCK_HEIGHT used for the block's height. To maintain visual consistency, consider using BLOCK_HEIGHT for the placeholder's height.

Apply this diff:

-            placeholderChildren={<div className="h-8 w-full bg-custom-background-80 rounded" />}
+            placeholderChildren={
+              <div
+                style={{ height: `${BLOCK_HEIGHT}px` }}
+                className="w-full bg-custom-background-80 rounded"
+              />
+            }
web/core/components/gantt-chart/views/helpers.ts (1)

21-24: Simplify getNumberOfDaysInMonth function.

The function can be simplified by returning the result directly without creating an intermediate date variable.

Apply this diff to simplify the function:

-export const getNumberOfDaysInMonth = (month: number, year: number) => {
-  const date = new Date(year, month + 1, 0);
-  return date.getDate();
-};
+export const getNumberOfDaysInMonth = (month: number, year: number) => new Date(year, month + 1, 0).getDate();
web/core/components/gantt-chart/blocks/block-row.tsx (1)

35-65: Verify the completeness of the dependency array in useEffect

In the useEffect hook, the dependency array is [block], but the effect relies on properties like block.position and block.id. If these sub-properties can change without block itself changing (e.g., if block is a mutable object), the effect may not run as expected.

Consider the following options:

  • If block is immutable or its reference changes when any property changes, the current dependency array is sufficient.

  • If properties like block.position can change without block's reference changing, include them in the dependency array:

    - }, [block]);
    + }, [block, block.position, block.id]);

This ensures the effect runs whenever any of the dependencies change.

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

102-102: Correct typographical errors in the comment

There's a typo in the comment on line 102: "og" should be "of", and "it's" should be "its".

Apply this diff to fix the comment:

- * Group array of Months into Quarters, returns an array og Quarters and it's children Months
+ * Group array of Months into Quarters, returns an array of Quarters and its children Months

37-37: Simplify condition by removing unnecessary null checks

The condition if (minusDate && plusDate) is unnecessary since minusDate and plusDate are always defined Date objects. You can remove this condition to simplify the code.

Apply this diff:

- if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
+ filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);

57-57: Simplify condition by removing unnecessary null checks

Similarly, the condition if (minusDate && plusDate) can be removed as both variables are always defined.

Apply this diff:

- if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
+ filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);

72-72: Simplify condition by removing unnecessary null checks

Again, remove the unnecessary condition if (minusDate && plusDate) to simplify the code.

Apply this diff:

- if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
+ filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
web/core/components/gantt-chart/views/month-view.ts (1)

48-48: Remove Redundant Condition Checks

The conditions if (minusDate && plusDate) at lines 48, 66, and 80 are redundant because minusDate and plusDate are always defined as Date objects and thus always truthy. Consider removing these checks to simplify the code.

Also applies to: 66-66, 80-80

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

97-99: Rename 'monthData' to 'weekData' for clarity

In the context of the week view, using monthData as a variable name can be confusing. Renaming it to weekData improves readability and consistency.

Apply this diff to rename the variable:

-      const scrollWidth =
-        filteredDates
-          .map((monthData: any) => monthData.children.length)
+      const scrollWidth =
+        filteredDates
+          .map((weekData: any) => weekData.children.length)
web/core/components/gantt-chart/chart/root.tsx (1)

134-136: Clarify arithmetic expression with parentheses

To improve readability and ensure correct operator precedence, consider adding parentheses to the scrollWidth calculation:

scrollWidth =
  (Math.abs(daysDifference) * currentState.data.dayWidth) -
  ((clientVisibleWidth / 2) - currentState.data.dayWidth) +
  (SIDEBAR_WIDTH / 2);

This clarifies the intended order of operations.

web/core/components/gantt-chart/chart/main-content.tsx (2)

Line range hint 82-85: Avoid including mutable refs in useEffect dependency array

Including ganttContainerRef?.current in the dependency array of the useEffect hook may cause unnecessary re-runs of the effect because changes to ref.current do not trigger re-renders. Since refs are mutable and their .current property doesn't cause a re-render when it changes, it's best to exclude it from the dependencies.

Apply this diff to fix the dependency array:

-  }, [ganttContainerRef?.current]);
+  }, []);

133-133: Provide context for non-removable ID

The comment // DO NOT REMOVE THE ID indicates the importance of the id attribute on the <div>. Adding a brief explanation about why this id should not be removed will help future maintainers understand its necessity.

For example:

-  // DO NOT REMOVE THE ID
+  // DO NOT REMOVE THE ID: Required for automated testing
web/ce/store/timeline/base-timeline.store.ts (4)

184-185: Remove unnecessary optional chaining on blockData properties

After the null check at line 177 (if (!blockData) continue;), blockData is guaranteed to be defined. The use of optional chaining (blockData?.start_date) on lines 184-185 and 191-192 is unnecessary and inconsistent with how other properties are accessed (e.g., blockData.name).

Apply this diff to improve consistency:

// Lines 184-185
-    start_date: blockData?.start_date ?? undefined,
-    target_date: blockData?.target_date ?? undefined,
+    start_date: blockData.start_date ?? undefined,
+    target_date: blockData.target_date ?? undefined,

// Lines 191-192
-            start_date: blockData?.start_date ?? undefined,
-            target_date: blockData?.target_date ?? undefined,
+            start_date: blockData.start_date ?? undefined,
+            target_date: blockData.target_date ?? undefined,

Also applies to: 191-192


245-245: Correct typo in comment: "wile" should be "while"

There's a typographical error in the comment for updateBlockPosition.

Apply this diff to fix the typo:

-     * updates the block's position such as marginLeft and width wile dragging
+     * updates the block's position such as marginLeft and width while dragging

218-218: Correct typo in comment: "updates dates" should be "updated dates"

The comment for getUpdatedPositionAfterDrag contains a typo.

Apply this diff to correct the comment:

-     * returns updates dates of blocks post drag.
+     * returns updated dates of blocks post drag.

62-62: Consider removing underscore prefix from constructor parameter

The parameter _rootStore in the constructor uses an underscore prefix, which is typically reserved for denoting private variables. Since the parameter is being assigned to an instance property, consider removing the underscore for clarity and consistency.

Apply this diff:

-  constructor(_rootStore: CoreRootStore) {
+  constructor(rootStore: CoreRootStore) {
     makeObservable(this, {
       // ...
     });
     this.initGantt();

-    this.rootStore = _rootStore;
+    this.rootStore = rootStore;
   }
apiserver/plane/app/views/issue/base.py (1)

1127-1160: Prevent duplicate entries in issues_to_update list

An issue may be appended to issues_to_update multiple times if both start_date and target_date are provided in the update. While bulk_update can handle duplicates, it's more efficient to ensure each issue is added only once.

Consider modifying the code to append the issue after processing both dates:

issues_to_update = []
for update in updates:
    # existing code...

    updated = False
    if start_date:
        # existing code...
        issue.start_date = start_date
        updated = True

    if target_date:
        # existing code...
        issue.target_date = target_date
        updated = True

    if updated:
        issues_to_update.append(issue)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6d08cf2 and 24db506.

📒 Files selected for processing (82)
  • apiserver/plane/app/serializers/base.py (4 hunks)
  • apiserver/plane/app/urls/issue.py (3 hunks)
  • apiserver/plane/app/views/init.py (1 hunks)
  • apiserver/plane/app/views/issue/base.py (1 hunks)
  • apiserver/plane/app/views/issue/relation.py (3 hunks)
  • apiserver/plane/bgtasks/issue_activities_task.py (3 hunks)
  • apiserver/plane/db/models/issue.py (1 hunks)
  • apiserver/plane/utils/issue_relation_mapper.py (1 hunks)
  • packages/tailwind-config-custom/tailwind.config.js (1 hunks)
  • packages/types/src/dashboard.d.ts (1 hunks)
  • packages/types/src/issues/issue.d.ts (2 hunks)
  • packages/types/src/issues/issue_relation.d.ts (1 hunks)
  • packages/types/src/view-props.d.ts (1 hunks)
  • web/ce/components/gantt-chart/dependency/blockDraggables/index.ts (1 hunks)
  • web/ce/components/gantt-chart/dependency/blockDraggables/left-draggable.tsx (1 hunks)
  • web/ce/components/gantt-chart/dependency/blockDraggables/right-draggable.tsx (1 hunks)
  • web/ce/components/gantt-chart/dependency/dependency-paths.tsx (1 hunks)
  • web/ce/components/gantt-chart/dependency/draggable-dependency-path.tsx (1 hunks)
  • web/ce/components/gantt-chart/dependency/index.ts (1 hunks)
  • web/ce/components/gantt-chart/index.ts (1 hunks)
  • web/ce/constants/gantt-chart.ts (1 hunks)
  • web/ce/constants/index.ts (1 hunks)
  • web/ce/constants/issues.ts (1 hunks)
  • web/ce/store/timeline/base-timeline.store.ts (1 hunks)
  • web/ce/types/gantt-chart.ts (1 hunks)
  • web/ce/types/index.ts (1 hunks)
  • web/core/components/api-token/modal/form.tsx (1 hunks)
  • web/core/components/core/render-if-visible-HOC.tsx (2 hunks)
  • web/core/components/cycles/gantt-chart/blocks.tsx (0 hunks)
  • web/core/components/cycles/gantt-chart/cycles-list-layout.tsx (0 hunks)
  • web/core/components/cycles/gantt-chart/index.ts (0 hunks)
  • web/core/components/cycles/index.ts (0 hunks)
  • web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx (0 hunks)
  • web/core/components/gantt-chart/blocks/block-row-list.tsx (1 hunks)
  • web/core/components/gantt-chart/blocks/block-row.tsx (1 hunks)
  • web/core/components/gantt-chart/blocks/block.tsx (1 hunks)
  • web/core/components/gantt-chart/blocks/blocks-list.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/header.tsx (2 hunks)
  • web/core/components/gantt-chart/chart/main-content.tsx (4 hunks)
  • web/core/components/gantt-chart/chart/root.tsx (6 hunks)
  • web/core/components/gantt-chart/chart/timeline-drag-helper.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/bi-week.tsx (0 hunks)
  • web/core/components/gantt-chart/chart/views/day.tsx (0 hunks)
  • web/core/components/gantt-chart/chart/views/hours.tsx (0 hunks)
  • web/core/components/gantt-chart/chart/views/index.ts (0 hunks)
  • web/core/components/gantt-chart/chart/views/month.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/quarter.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/week.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/year.tsx (0 hunks)
  • web/core/components/gantt-chart/constants.ts (1 hunks)
  • web/core/components/gantt-chart/contexts/index.tsx (1 hunks)
  • web/core/components/gantt-chart/data/index.ts (2 hunks)
  • web/core/components/gantt-chart/helpers/add-block.tsx (3 hunks)
  • web/core/components/gantt-chart/helpers/blockResizables/left-resizable.tsx (1 hunks)
  • web/core/components/gantt-chart/helpers/blockResizables/right-resizable.tsx (1 hunks)
  • web/core/components/gantt-chart/helpers/blockResizables/use-gantt-resizable.ts (1 hunks)
  • web/core/components/gantt-chart/helpers/draggable.tsx (1 hunks)
  • web/core/components/gantt-chart/hooks/index.ts (0 hunks)
  • web/core/components/gantt-chart/hooks/use-gantt-chart.ts (0 hunks)
  • web/core/components/gantt-chart/index.ts (0 hunks)
  • web/core/components/gantt-chart/root.tsx (4 hunks)
  • web/core/components/gantt-chart/sidebar/cycles/block.tsx (0 hunks)
  • web/core/components/gantt-chart/sidebar/cycles/index.ts (0 hunks)
  • web/core/components/gantt-chart/sidebar/cycles/sidebar.tsx (0 hunks)
  • web/core/components/gantt-chart/sidebar/index.ts (0 hunks)
  • web/core/components/gantt-chart/sidebar/issues/block.tsx (2 hunks)
  • web/core/components/gantt-chart/sidebar/issues/sidebar.tsx (3 hunks)
  • web/core/components/gantt-chart/sidebar/modules/block.tsx (1 hunks)
  • web/core/components/gantt-chart/sidebar/modules/sidebar.tsx (1 hunks)
  • web/core/components/gantt-chart/sidebar/root.tsx (0 hunks)
  • web/core/components/gantt-chart/types/index.ts (3 hunks)
  • web/core/components/gantt-chart/views/bi-week-view.ts (0 hunks)
  • web/core/components/gantt-chart/views/day-view.ts (0 hunks)
  • web/core/components/gantt-chart/views/helpers.ts (2 hunks)
  • web/core/components/gantt-chart/views/hours-view.ts (0 hunks)
  • web/core/components/gantt-chart/views/index.ts (1 hunks)
  • web/core/components/gantt-chart/views/month-view.ts (2 hunks)
  • web/core/components/gantt-chart/views/quarter-view.ts (1 hunks)
  • web/core/components/gantt-chart/views/quater-view.ts (0 hunks)
  • web/core/components/gantt-chart/views/week-view.ts (1 hunks)
  • web/core/components/gantt-chart/views/year-view.ts (0 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/content.tsx (1 hunks)
⛔ Files not processed due to max files limit (25)
  • web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx
  • web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx
  • web/core/components/issues/issue-detail/relation-select.tsx
  • web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx
  • web/core/components/issues/relations/issue-list-item.tsx
  • web/core/components/issues/relations/issue-list.tsx
  • web/core/components/modules/gantt-chart/modules-list-layout.tsx
  • web/core/components/ui/loader/layouts/gantt-layout-loader.tsx
  • web/core/hooks/use-auto-scroller.tsx
  • web/core/hooks/use-timeline-chart.ts
  • web/core/layouts/auth-layout/project-wrapper.tsx
  • web/core/services/issue/issue.service.ts
  • web/core/services/issue/issue_relation.service.ts
  • web/core/store/issue/helpers/base-issues.store.ts
  • web/core/store/issue/helpers/issue-filter-helper.store.ts
  • web/core/store/issue/issue-details/relation.store.ts
  • web/core/store/issue/issue-details/root.store.ts
  • web/core/store/root.store.ts
  • web/core/store/timeline/index.ts
  • web/core/store/timeline/issues-timeline.store.ts
  • web/core/store/timeline/modules-timeline.store.ts
  • web/ee/components/gantt-chart/index.ts
  • web/ee/store/timeline/base-timeline.store.ts
  • web/helpers/date-time.helper.ts
  • web/helpers/issue.helper.ts
💤 Files with no reviewable changes (23)
  • web/core/components/cycles/gantt-chart/blocks.tsx
  • web/core/components/cycles/gantt-chart/cycles-list-layout.tsx
  • web/core/components/cycles/gantt-chart/index.ts
  • web/core/components/cycles/index.ts
  • web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx
  • web/core/components/gantt-chart/chart/views/bi-week.tsx
  • web/core/components/gantt-chart/chart/views/day.tsx
  • web/core/components/gantt-chart/chart/views/hours.tsx
  • web/core/components/gantt-chart/chart/views/index.ts
  • web/core/components/gantt-chart/chart/views/year.tsx
  • web/core/components/gantt-chart/hooks/index.ts
  • web/core/components/gantt-chart/hooks/use-gantt-chart.ts
  • web/core/components/gantt-chart/index.ts
  • web/core/components/gantt-chart/sidebar/cycles/block.tsx
  • web/core/components/gantt-chart/sidebar/cycles/index.ts
  • web/core/components/gantt-chart/sidebar/cycles/sidebar.tsx
  • web/core/components/gantt-chart/sidebar/index.ts
  • web/core/components/gantt-chart/sidebar/root.tsx
  • web/core/components/gantt-chart/views/bi-week-view.ts
  • web/core/components/gantt-chart/views/day-view.ts
  • web/core/components/gantt-chart/views/hours-view.ts
  • web/core/components/gantt-chart/views/quater-view.ts
  • web/core/components/gantt-chart/views/year-view.ts
✅ Files skipped from review due to trivial changes (9)
  • packages/types/src/issues/issue_relation.d.ts
  • web/ce/components/gantt-chart/dependency/blockDraggables/index.ts
  • web/ce/components/gantt-chart/dependency/dependency-paths.tsx
  • web/ce/components/gantt-chart/dependency/draggable-dependency-path.tsx
  • web/ce/components/gantt-chart/dependency/index.ts
  • web/ce/components/gantt-chart/index.ts
  • web/ce/constants/index.ts
  • web/ce/constants/issues.ts
  • web/core/components/gantt-chart/views/index.ts
🧰 Additional context used
🪛 Ruff
apiserver/plane/app/views/__init__.py

133-133: .issue.base.IssueDetailEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


134-134: .issue.base.IssueBulkUpdateDateEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

🪛 GitHub Check: lint-web
web/ce/components/gantt-chart/dependency/blockDraggables/left-draggable.tsx

[warning] 9-9:
'props' is defined but never used

web/ce/components/gantt-chart/dependency/blockDraggables/right-draggable.tsx

[warning] 8-8:
'props' is defined but never used

🪛 Biome
web/ce/store/timeline/base-timeline.store.ts

[error] 187-187: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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

[error] 27-34: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

web/core/components/gantt-chart/helpers/blockResizables/use-gantt-resizable.ts

[error] 107-107: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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

[error] 151-151: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (81)
web/ce/types/index.ts (1)

3-3: LGTM! Verify type imports in the new Gantt chart views.

The addition of the Gantt chart module export aligns well with the Timeline layout revamp objectives. The change follows TypeScript best practices for module organization.

Let's verify the Gantt chart type imports in the new view components:

✅ Verification successful

Types are properly exported and used across the Gantt chart components

The verification confirms that:

  • The gantt-chart.ts exports TIssueRelationTypes which is correctly re-exported through index.ts
  • The view components (week, month, quarter) properly import their required types from the types module
  • The type system is well-organized with:
    • Common types imported via ../types
    • View-specific interfaces defined in their respective files
    • Proper type sharing between views (e.g., IMonthBlock used in quarter view)

The barrel file export addition is working as intended, maintaining a clean type organization across the Gantt chart implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the new Timeline views are properly importing types from the Gantt chart module
# Expected: Find imports from "../types" or "../../types" in the new view components

# Search for Timeline view components and their type imports
rg -g '*.tsx' -g '*.ts' -A 3 'import.*from.*types' | rg -C 3 '(Week|Month|Quarter)View'

Length of output: 1104


Script:

#!/bin/bash
# Let's verify the actual implementation and imports in the view components
# to ensure they're properly using the exported types

# First, check the content of the gantt-chart types file
echo "=== Checking gantt-chart.ts types ==="
fd "gantt-chart.ts" --type f --exec cat {}

# Then, check the implementation of view components
echo -e "\n=== Checking view implementations ==="
fd "(week|month|quarter)-view.ts" --type f --exec cat {}

Length of output: 16349

web/core/components/gantt-chart/constants.ts (1)

3-3: Verify the impact of reduced header height.

The header height reduction from 60px to 48px aligns with modern UI trends, but we should ensure this change doesn't affect the layout negatively.

✅ Verification successful

Header height change appears safe to implement

The reduced HEADER_HEIGHT (48px) is consistently used across all timeline views (Week, Month, Quarter) and the sidebar component. No hardcoded 60px heights were found that could conflict with this change. The constant is properly encapsulated within the Gantt chart components and its usage is uniform throughout the implementation.

  • Used in:
    • web/core/components/gantt-chart/sidebar/root.tsx
    • web/core/components/gantt-chart/chart/views/week.tsx
    • web/core/components/gantt-chart/chart/views/month.tsx
    • web/core/components/gantt-chart/chart/views/quarter.tsx
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for components that might be affected by the HEADER_HEIGHT change
rg -l "HEADER_HEIGHT" --type ts --type tsx

# Check for any hardcoded height values that might conflict
rg "height: *60" --type ts --type tsx

Length of output: 138


Script:

#!/bin/bash
# Search for components that might be affected by the HEADER_HEIGHT change
rg -l "HEADER_HEIGHT"

# Check for any hardcoded height values that might conflict
rg "height: *60"

# Look for style definitions that might use this constant
rg "height.*HEADER_HEIGHT"

Length of output: 832

web/ce/constants/gantt-chart.ts (2)

1-1: LGTM! Clean type import.

The import statement follows TypeScript best practices by explicitly importing the type.


3-8: ⚠️ Potential issue

Verify alignment with backend relation types.

The backend has added new relation types "start_before" and "finish_before" in IssueRelation class, but these aren't reflected in the REVERSE_RELATIONS mapping. This could lead to inconsistencies in relation handling.

Let's verify the relation types in the backend:

Consider adding the new relation types with their corresponding reverse relations:

export const REVERSE_RELATIONS: { [key in TIssueRelationTypes]: TIssueRelationTypes } = {
  blocked_by: "blocking",
  blocking: "blocked_by",
  relates_to: "relates_to",
  duplicate: "duplicate",
+ start_before: "finish_after",
+ finish_before: "start_after",
};

Note: Please verify the correct reverse relations for the new types with the backend team.

web/core/components/gantt-chart/contexts/index.tsx (2)

1-1: LGTM! Clean and focused imports.

The imports are minimal and only include the necessary React hooks.


3-6: Verify the completeness of timeline types.

The enum ETimeLineTypeType currently defines ISSUE and MODULE types, but the PR objectives mention Week, Month, and Quarter views. Could you clarify if:

  1. These are different concerns (content type vs. view type)?
  2. Additional timeline types need to be added to support the new views?

Let's check for any related timeline type definitions:

web/core/components/gantt-chart/chart/timeline-drag-helper.tsx (2)

9-11: LGTM! Type definition is clear and well-structured.

The Props interface is concise and uses appropriate TypeScript types.


16-16: Verify the auto-scrolling behavior across different views.

Since this PR introduces multiple timeline views (Week, Month, Quarter), ensure that the auto-scrolling behavior works correctly across all views.

✅ Verification successful

Let me analyze the results from the shell scripts to verify the auto-scrolling behavior across different views.

The results show that:

  1. The useAutoScroller hook is view-agnostic and takes the same parameters regardless of the view being used.
  2. SIDEBAR_WIDTH and HEADER_HEIGHT are constants defined in constants.ts and are used consistently across all views (Week, Month, Quarter).
  3. The auto-scrolling behavior is implemented in a view-independent manner, as it only depends on container dimensions and scroll positions.

Auto-scrolling implementation is view-independent and works consistently across all views.

The auto-scrolling behavior is correctly implemented to work with any view because:

  • It uses fixed constants (SIDEBAR_WIDTH = 360px, HEADER_HEIGHT = 48px) that are shared across all views
  • The scrolling logic in useAutoScroller hook operates on container dimensions and scroll positions, which are view-agnostic
  • All views (Week, Month, Quarter) use the same container structure and dimensions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for view-specific configurations or adjustments in auto-scrolling

# Test: Look for any view-specific logic in the auto-scroller hook
rg -A 5 "useAutoScroller" 

# Test: Check for any view-specific constants or configurations
rg -A 5 "SIDEBAR_WIDTH|HEADER_HEIGHT"

Length of output: 17561

web/core/components/gantt-chart/types/index.ts (4)

30-30: LGTM! View types align with PR objectives.

The simplified view options ("week", "month", "quarter") directly match the PR requirements for the Timeline layout revamp.


51-51: LGTM! Improved property naming.

The rename from width to dayWidth better describes the property's purpose and improves code clarity.


37-37: Verify the usage of the new abbreviation field.

The addition of the abbreviation field suggests enhanced display options. Ensure it's being used appropriately in responsive layouts or space-constrained views.

#!/bin/bash
# Check where and how the abbreviation field is being used
ast-grep --pattern 'abbreviation'

4-4: Verify date type changes across the application.

The change from Date to string type for start_date and target_date could impact date handling across the application. Ensure that:

  1. All consumers of these fields handle string dates correctly
  2. Date parsing and formatting are consistent throughout the application

Also applies to: 10-11

✅ Verification successful

Based on the code analysis, I can now provide a final response:

The date type change from Date to string is consistent with the codebase's date handling

The change from Date to string type in the Gantt chart interface is correct and aligns with the codebase's date handling patterns:

  1. The Gantt chart components consistently handle dates as strings and convert them to Date objects only when needed for calculations using the getDate() utility
  2. All date-related operations in the Gantt chart use string-based dates that get formatted/parsed using utilities like renderFormattedPayloadDate()
  3. The type change maintains consistency with other interfaces in the codebase that also use string dates (e.g. IBlockUpdateData, IBlockUpdateDependencyData)

The change improves type safety by:

  • Avoiding timezone/serialization issues with Date objects
  • Matching the actual runtime data format used throughout the application
  • Maintaining consistency with the rest of the codebase's date handling approach
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of start_date and target_date properties
# to verify date handling consistency

echo "Checking usage of start_date and target_date in the codebase:"
rg -A 3 "start_date|target_date" --type ts --type tsx

echo "Checking date parsing/formatting utilities:"
rg -A 3 "new Date\(|Date\." --type ts --type tsx

Length of output: 519


Script:

#!/bin/bash
# Search for direct usage of start_date and target_date properties
# to verify date handling consistency
echo "Checking usage of start_date and target_date in the codebase:"
rg -A 3 "start_date|target_date"

echo -e "\nChecking date parsing/formatting utilities:"
rg -A 3 "new Date\(|Date\."

echo -e "\nChecking for files related to Gantt chart:"
fd "gantt" --type f

Length of output: 373323


Script:

#!/bin/bash
# Search for date handling in Gantt chart related files
echo "Checking date handling in Gantt chart files:"
rg -A 3 "start_date|target_date" web/core/components/gantt-chart/

echo -e "\nChecking Gantt chart type definitions:"
rg -A 3 "interface.*Block|type.*Block" web/core/components/gantt-chart/

Length of output: 9420

web/core/components/gantt-chart/blocks/blocks-list.tsx (2)

16-29: LGTM! Clean component structure.

Good use of React Fragment to avoid unnecessary DOM nesting. The props destructuring is clean and matches the type definition perfectly.


30-48: Verify showAllBlocks behavior across different timeline views.

The showAllBlocks prop appears to control block visibility, which is crucial for the new Week/Month/Quarter views. Let's verify its implementation across different views.

✅ Verification successful

Let me gather more specific information about the showAllBlocks implementation.


Based on the search results, I can now provide a conclusive response about the showAllBlocks behavior.

showAllBlocks implementation is correctly handling block visibility across views

The verification shows that showAllBlocks is properly implemented:

  • In GanttChartBlock, blocks are hidden if they don't have both start and target dates when showAllBlocks is false
  • The prop is consistently passed down through the component hierarchy from GanttChartRoot to individual blocks
  • The visibility logic is uniform across all views (Week/Month/Quarter) as it's handled at the block level
  • The implementation follows a clear pattern: if (!block || (!showAllBlocks && !(block.start_date && block.target_date))) return null

This confirms that the showAllBlocks behavior is working as intended for controlling block visibility across different timeline views.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how showAllBlocks is used across different timeline views

# Search for showAllBlocks usage in timeline-related components
echo "Checking showAllBlocks usage in timeline views..."
rg -l 'showAllBlocks.*Week|Month|Quarter' web/

# Check for view-specific rendering logic
echo "Checking view-specific rendering conditions..."
ast-grep --pattern 'showAllBlocks ? $$$'

Length of output: 1986


Script:

#!/bin/bash
# Check the implementation of showAllBlocks in GanttChartBlock and related components
echo "Checking GanttChartBlock implementation..."
rg -A 5 "showAllBlocks" web/core/components/gantt-chart/

# Check how showAllBlocks affects block visibility
echo "Checking block visibility logic..."
ast-grep --pattern 'const $_ = ({ showAllBlocks, $$$ }) => {
  $$$
}'  web/core/components/gantt-chart/

# Check view-specific implementations
echo "Checking view implementations..."
rg -A 10 "export (class|const) (Week|Month|Quarter)View" web/core/components/gantt-chart/views/

Length of output: 13872

web/core/components/gantt-chart/sidebar/modules/sidebar.tsx (2)

34-44: LGTM! Performance improvement in block handling.

The refactoring improves performance by avoiding redundant getBlockById calls within the map function. The direct passing of blockId also promotes better unidirectional data flow.


42-42: Verify ModulesSidebarBlock implementation.

Ensure that ModulesSidebarBlock properly handles the blockId prop and implements necessary memoization to prevent unnecessary re-renders.

✅ Verification successful

ModulesSidebarBlock implementation is properly handling props and optimized

The component is well-implemented with the following characteristics:

  • Properly handles the blockId prop to fetch and display block data
  • Uses MobX observer for efficient reactive updates
  • Implements proper null checks and conditional rendering
  • Has mouse event handlers for block activation state
  • Renders duration information only when needed

The component doesn't need explicit memoization because:

  1. It's already wrapped with MobX observer which optimizes re-renders
  2. Props are simple primitive values (blockId and isDragging)
  3. All state management is handled through MobX store
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify ModulesSidebarBlock implementation and its usage of blockId

# Test: Check ModulesSidebarBlock implementation
ast-grep --pattern 'const ModulesSidebarBlock = $_($_) {
  $$$
}'

# Test: Check for React.memo usage
rg -A 5 'memo.*ModulesSidebarBlock'

Length of output: 105


Script:

#!/bin/bash
# Let's try different approaches to locate ModulesSidebarBlock implementation

# Search for ModulesSidebarBlock definition with different patterns
rg -A 20 "ModulesSidebarBlock.*=.*\(" 

# Search for any file containing ModulesSidebarBlock
fd ModulesSidebarBlock

# Search for exports containing ModulesSidebarBlock
rg "export.*ModulesSidebarBlock"

# Search for any usage of ModulesSidebarBlock
rg "ModulesSidebarBlock"

Length of output: 2655


Script:

#!/bin/bash
# Let's check the Props interface and any potential performance optimizations

# Check Props interface definition
rg -B 10 "interface Props" web/core/components/gantt-chart/sidebar/modules/block.tsx

# Check if there are any performance related hooks or memoization
rg -A 5 "useMemo|useCallback|memo" web/core/components/gantt-chart/sidebar/modules/block.tsx

# Check observer implementation since we see it's used
rg "observer" web/core/components/gantt-chart/sidebar/modules/block.tsx

Length of output: 366


Script:

#!/bin/bash
# Let's get the complete file content to analyze the implementation thoroughly

# Get the full content of block.tsx
cat web/core/components/gantt-chart/sidebar/modules/block.tsx

# Check how the store is implemented since it's using MobX
rg -A 10 "useTimeLineChartStore" web/core/components/gantt-chart/sidebar/modules/block.tsx

Length of output: 2712

web/core/components/gantt-chart/sidebar/modules/block.tsx (1)

1-16: LGTM! Clean imports and improved prop interface.

The transition from passing the entire block object to just the blockId is a good practice that reduces prop drilling and centralizes state management.

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

27-34: Great use of virtualization for performance optimization!

The implementation of RenderIfVisible with appropriate offset values ensures smooth scrolling and optimal performance by only rendering rows that are visible in the viewport.

🧰 Tools
🪛 Biome

[error] 27-34: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

web/core/components/gantt-chart/helpers/blockResizables/left-resizable.tsx (4)

1-18: LGTM! Well-structured type definitions and imports.

The props interface is well-defined with clear types and the imports are properly organized.


19-23: LGTM! Clean component setup with proper state management.

The component correctly implements the observer pattern and uses appropriate state management for UI interactions.


28-30: LGTM! Clear resize state handling.

The resize state logic is straightforward and the early return pattern is well applied.


24-26: Verify date handling for edge cases.

While the date calculation logic looks correct, consider adding error handling for invalid dates or positions.

Run this script to check for potential edge cases in date handling across the codebase:

✅ Verification successful

Based on the script results, I can now generate the final response since I have enough information about the date handling implementation across the codebase.

Date handling implementation is properly safeguarded

The current implementation already has sufficient null checks and edge case handling:

  • The position check prevents undefined access: position ? getDateFromPositionOnGantt(...) : undefined
  • The store implementation has a guard for missing view data: if (!this.currentViewData) return
  • The date formatting is safely handled: date ? renderFormattedDate(date) : undefined
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of getDateFromPositionOnGantt to understand edge case handling
rg -A 3 "getDateFromPositionOnGantt" 

# Search for any existing error handling patterns for date calculations
ast-grep --pattern 'getDateFromPositionOnGantt($_)'

Length of output: 3970

web/core/components/gantt-chart/helpers/blockResizables/right-resizable.tsx (1)

10-18: ⚠️ Potential issue

Consider making the position prop required or add runtime validation.

The optional position prop is used directly in calculations (line 25) without comprehensive null checking. This could lead to runtime errors if the prop is undefined.

Let's verify the usage of this component to see if position is always provided:

Consider either:

  1. Making the position prop required:
 type RightResizableProps = {
   enableBlockRightResize: boolean;
   handleBlockDrag: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, dragDirection: "left" | "right" | "move") => void;
   isMoving: "left" | "right" | "move" | undefined;
-  position?: {
+  position: {
     marginLeft: number;
     width: number;
   };
 };
  1. Or adding runtime validation:
if (!position) {
  console.warn('Position prop is required for proper date calculation');
  return null;
}
web/core/components/gantt-chart/chart/header.tsx (2)

3-12: LGTM! Well-organized imports with clear grouping.

The import changes align with the Timeline layout revamp, and the organization follows good practices with clear grouping using comments.


29-29: Verify consistent hook usage across Gantt chart components.

The switch from useGanttChart to useTimeLineChartStore looks good, but let's verify consistent usage across related components.

✅ Verification successful

Hook usage is consistent across Gantt chart components

The verification shows that:

  • No instances of useGanttChart remain in the codebase
  • useTimeLineChartStore is consistently imported from "@/hooks/use-timeline-chart" across all Gantt chart components
  • The hook is properly used to access various store properties and methods throughout the components
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that useGanttChart has been replaced with useTimeLineChartStore across Gantt chart components

echo "Checking for any remaining useGanttChart usage..."
rg "useGanttChart" "web/core/components/gantt-chart"

echo "Verifying useTimeLineChartStore implementation in Gantt chart components..."
rg "useTimeLineChartStore" "web/core/components/gantt-chart"

Length of output: 4501

packages/types/src/issues/issue.d.ts (1)

59-60: Clarify the distinction between issue_relation and issue_related.

The purpose of having two separate arrays for relations isn't immediately clear. The naming could be more explicit about the direction of the relationships.

Let's check if these fields are used consistently in the codebase:

Consider renaming these fields to be more explicit about their roles:

-  issue_relation?: IssueRelation[];
-  issue_related?: IssueRelation[];
+  outgoing_relations?: IssueRelation[];
+  incoming_relations?: IssueRelation[];
web/core/components/gantt-chart/root.tsx (3)

Line range hint 31-52: Clean component implementation with proper defaults!

The observer HOC is correctly applied, and the props destructuring with sensible defaults enhances maintainability.


63-84: Clean and efficient prop forwarding!

The render logic maintains a clear separation of concerns by passing all necessary props to ChartViewRoot without unnecessary manipulation.


Line range hint 1-86: Verify integration with new Timeline views.

Since this PR introduces Week, Month, and Quarter views, we should verify that this Gantt chart component properly integrates with all three views.

web/core/components/core/render-if-visible-HOC.tsx (2)

16-16: LGTM: Clean prop type addition

The forceRender optional boolean prop is well-defined and properly integrated into the Props type.


33-33: LGTM: Proper default value assignment

The default value of false for forceRender maintains the original behavior by default.

web/core/components/gantt-chart/helpers/add-block.tsx (3)

11-14: LGTM! Good architectural improvement.

The transition to useTimeLineChartStore indicates a move towards more centralized state management, which is beneficial for managing multiple timeline views.


64-65: 🛠️ Refactor suggestion

Optimize mousemove handler to prevent unnecessary recreations.

The current implementation may cause performance issues due to the circular dependency between the mousemove handler and buttonXPosition.

- }, [buttonXPosition, currentViewData]);
+ }, [currentViewData]); // Remove buttonXPosition dependency

Likely invalid or redundant comment.


41-43: ⚠️ Potential issue

Review the numberOfDays logic for different views.

The current implementation has several potential issues:

  1. Only quarter view is explicitly handled
  2. Week and month views default to 1 day, which might not align with user expectations
  3. The magic number 7 for quarter view lacks explanation

Consider implementing view-specific spans:

- let numberOfDays = 1;
- if (currentView === "quarter") numberOfDays = 7;
+ const VIEW_SPANS = {
+   week: 1,
+   month: 3,
+   quarter: 7
+ };
+ const numberOfDays = VIEW_SPANS[currentView] ?? 1;
✅ Verification successful

The previous search was too specific. Let's broaden our search to understand the view-related logic better and verify if the current implementation aligns with the codebase's patterns.


The output shows that there's a VIEWS_LIST being used with the view types, and we need to understand if there are any existing configurations for time spans. This will help us verify if the current implementation aligns with the codebase's patterns.


The current numberOfDays implementation is correct and consistent with the codebase

The implementation aligns with the codebase's design where:

  1. Week view uses 1 day as the default span (confirmed by week-view.ts implementation showing individual day blocks)
  2. Month view uses 1 day as it operates on week-based blocks (7 days) internally
  3. Quarter view uses 7 days which matches the larger time scale and grid implementation in quarter-view.ts

The current implementation is intentionally simple and works with the view-specific rendering logic in the respective view components.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any configurations or constants defining these view-specific spans
rg -A 2 "DEFAULT_SPAN" || rg -A 2 "VIEW_SPANS"

Length of output: 43


Script:

#!/bin/bash
# Let's search for view-related constants and configurations
rg -g '*.{ts,tsx,js,jsx}' -B 2 -A 2 'currentView.*=.*("week"|"month"|"quarter")'

# Also search for any view-related types or interfaces
ast-grep --pattern 'type $_ = "week" | "month" | "quarter"'

# Check the component's props or type definitions
fd -e ts -e tsx gantt-chart --exec rg -l "type.*Props" {}

Length of output: 2304


Script:

#!/bin/bash
# Let's check the VIEWS_LIST constant and any related view configurations
rg -g '*.{ts,tsx}' -B 2 -A 5 'VIEWS_LIST.*='

# Also search for any time-span or day-related configurations in the gantt chart
rg -g '*.{ts,tsx}' 'days?' web/core/components/gantt-chart/

Length of output: 18657

web/core/components/gantt-chart/sidebar/issues/block.tsx (2)

11-11: LGTM: Good architectural decision to use centralized timeline store.

The switch to useTimeLineChartStore aligns well with the timeline revamp, providing a centralized state management solution that can support all timeline views (Week, Month, Quarter).


27-27: Verify duration calculations across different timeline views.

The switch to position-based duration calculation using getNumberOfDaysFromPosition needs verification across Week, Month, and Quarter views to ensure consistent behavior.

Let's verify the implementation:

✅ Verification successful

Let me gather more information about the timeline store implementation to understand how the position-based calculations work.


Let me try different search patterns to locate the store implementation.


Let me search for the implementation of getNumberOfDaysFromPosition to understand how it handles different timeline views.


Let me try different search patterns to find the timeline implementation.


Based on the code found, I can now generate a final response:

Position-based duration calculation is view-independent and consistent.

The implementation of getNumberOfDaysFromPosition in base-timeline.store.ts shows that the duration calculation is consistent across all views (week/month/quarter) since it uses the view-specific dayWidth from currentViewData. The calculation is:

  • Simply divides the position width by the current view's dayWidth value
  • Uses Math.round() to handle any floating-point precision issues
  • Returns undefined for invalid inputs (no position or no view data)

This ensures accurate duration calculations regardless of the selected timeline view, as each view provides its own appropriate dayWidth scaling factor.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of getNumberOfDaysFromPosition to understand the calculation logic
ast-grep --pattern 'getNumberOfDaysFromPosition($_)'

# Look for test files covering this functionality
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts timeline

Length of output: 451


Script:

#!/bin/bash
# Search for the store implementation to understand the calculation logic
ast-grep --pattern 'getNumberOfDaysFromPosition = $_'

# Look for the timeline store implementation
rg -l "useTimeLineChartStore" --type ts --type tsx

# Search for view-related logic in the store
rg "view.*timeline" --type ts --type tsx -A 3

Length of output: 210


Script:

#!/bin/bash
# Search for the store implementation
rg "getNumberOfDaysFromPosition" -A 5

# Look for timeline store files
fd timeline store

# Search for view-related constants or types
rg "type.*View.*=|enum.*View" -A 3

Length of output: 292426


Script:

#!/bin/bash
# Search for the timeline store implementation
rg -l "useTimeLineChartStore" --type ts --type tsx -A 3

# Search for timeline view-related code
rg "currentViewData" --type ts --type tsx -A 3

# Search for timeline view types
rg "type.*View.*=.*week.*month.*quarter" --type ts --type tsx

Length of output: 250


Script:

#!/bin/bash
# Search for timeline store and view-related code
rg "getNumberOfDaysFromPosition" -A 5

# Search for timeline view types
rg "type.*TGanttViews" -A 3

# Search for timeline store implementation
fd timeline store

Length of output: 3734

web/core/components/gantt-chart/data/index.ts (2)

30-34: LGTM! Quarter data structure is well-implemented.

The quarters data structure follows a consistent pattern and provides clear quarter representations with appropriate titles and abbreviations.


Line range hint 1-94: Implementation successfully supports Timeline layout revamp.

The changes effectively support the PR objectives by providing well-structured data and configurations for Week, Month, and Quarter views. The implementation maintains consistency and provides appropriate customization options for each view type.

web/core/components/gantt-chart/sidebar/issues/sidebar.tsx (4)

8-17: LGTM: Import changes align with performance objectives.

The addition of RenderIfVisible and GanttLayoutLIstItem components supports the PR's performance improvement goals through optimized rendering.


92-107: LGTM: Drag and drop implementation.

The drag-and-drop functionality is well-maintained within the virtualized list, preserving the existing interaction patterns while benefiting from the performance improvements.


84-90: Verify RenderIfVisible offset values.

The horizontal (100) and vertical (200) offset values seem arbitrary. These values determine how far from the viewport edges we start rendering items.

#!/bin/bash
# Description: Check for consistent offset values across similar components

# Search for other RenderIfVisible usage to compare offset values
rg -A 2 "horizontalOffset.*verticalOffset" "web/core"

47-47: Verify consistent hook usage across components.

The migration from prop-based to hook-based block retrieval is a significant architectural change.

✅ Verification successful

Hook migration is consistent across the codebase

The verification shows that:

  • No interfaces or prop destructuring patterns for getBlockById were found, indicating complete migration away from prop-based approach
  • useTimeLineChart with ETimeLineTypeType.ISSUE is consistently used in issue-related components
  • The hook is properly integrated into the component hierarchy through the store context
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that other components have also migrated to using useTimeLineChart
# for block retrieval instead of receiving getBlockById as a prop

# Search for any remaining prop-based usage
rg "getBlockById.*Props" "web/core/components"

# Search for hook adoption
rg "useTimeLineChart.*getBlockById" "web/core/components"

Length of output: 510


Script:

#!/bin/bash
# Let's try a more comprehensive search strategy

# 1. Search for getBlockById in props or interfaces
ast-grep --pattern 'interface $_ {
  $$$
  getBlockById$$$
  $$$
}'

# 2. Search for getBlockById prop destructuring
ast-grep --pattern '{ $$$getBlockById$$$}' 

# 3. Search for useTimeLineChart hook usage
rg "useTimeLineChart" -A 2 "web/core"

# 4. Search specifically for the ISSUE enum usage with the hook
rg "useTimeLineChart.*ISSUE" "web/core"

Length of output: 13079

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

14-16: LGTM! Clean implementation of the new store hook.

The transition to useTimeLineChartStore with proper typing looks good.


18-26: LGTM! Good error handling and clean data preparation.

The null check and date calculations are well implemented. The code is defensive and handles edge cases appropriately.


89-102: Verify week column widths across different screen sizes.

The week columns implementation looks good, but let's ensure it works well across different screen sizes and zoom levels.

Let's check for any responsive design issues:

✅ Verification successful

Based on the search results, I can now generate the final response as we have sufficient information about the width handling in the Gantt chart component.

Week column widths implementation is correctly handled

The week columns implementation uses a robust width calculation system:

  • Width is dynamically calculated using currentViewData?.data.dayWidth * 7 for each week block
  • The component is part of a larger responsive system with proper scroll handling and container width management
  • Width calculations are consistently applied across different views (week, month, quarter)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for responsive design related code and viewport handling
rg -A 3 "useResizeObserver|useWindowSize|@media|viewport" web/core/components/gantt-chart/

Length of output: 92


Script:

#!/bin/bash
# Let's check for responsive design patterns and width calculations in the gantt chart component
rg -A 5 "width|resize|screen|responsive|zoom" web/core/components/gantt-chart/chart/

Length of output: 6143


Script:

#!/bin/bash
# Let's also check for any viewport or dimension-related hooks/utilities
fd . web/core/components/gantt-chart/ -e ts -e tsx --exec rg -l "useRef|useState|useEffect.*width|dimension|viewport|screen|resize"

Length of output: 792


Script:

#!/bin/bash
# Check the parent component structure and any container width handling
ast-grep --pattern 'const $_ = $$$; return (<div $$$>{$$$}</div>)' web/core/components/gantt-chart/chart/views/month.tsx

Length of output: 122

packages/types/src/view-props.d.ts (1)

81-82: LGTM: Addition of "expand" parameter

The addition of the "expand" parameter to TIssueParams aligns with the enhanced issue relation handling mentioned in the AI summary.

apiserver/plane/app/views/__init__.py (1)

133-134: Verify the imports are properly registered in URL configurations.

The newly added endpoints align well with the Timeline Layout revamp requirements. However, let's ensure they are properly registered and accessible.

✅ Verification successful

The imports are properly registered and used in the URL configurations

The verification confirms that both endpoints are:

  • Defined in apiserver/plane/app/views/issue/base.py
  • Imported and re-exported in apiserver/plane/app/views/__init__.py
  • Properly registered in URL patterns in apiserver/plane/app/urls/issue.py
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the new endpoints are registered in URL configurations
# and check for any existing usages.

# Test 1: Check URL configuration
echo "Checking URL patterns..."
rg -l "IssueDetailEndpoint|IssueBulkUpdateDateEndpoint" "apiserver/plane/app/urls/"

# Test 2: Check for any existing usage
echo "Checking for endpoint usage..."
rg "IssueDetailEndpoint|IssueBulkUpdateDateEndpoint" --type py

Length of output: 894

🧰 Tools
🪛 Ruff

133-133: .issue.base.IssueDetailEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


134-134: .issue.base.IssueBulkUpdateDateEndpoint imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

apiserver/plane/app/urls/issue.py (2)

24-26: LGTM! Clean import additions.

The new imports are properly organized and follow the existing pattern.


45-49: LGTM! Well-structured endpoint definition.

The URL pattern follows RESTful conventions and maintains consistency with other endpoints in the file.

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

41-41: LGTM: Good practice to centralize relation type mapping.

The addition of get_actual_relation utility function helps standardize relation type handling across the codebase.


273-277: LGTM: Good error handling for relation_type.

The early validation with clear error message is a good practice.


235-266: Verify relation type consistency.

The response data structure assumes that relation types are consistently mapped (e.g., "start_before" maps to "start_after"). Consider adding validation to ensure this consistency.

packages/tailwind-config-custom/tailwind.config.js (4)

292-292: LGTM: New font size addition aligns with Timeline layout needs.

The addition of the "2xs" (9px) font size provides better control for compact UI elements in the Timeline views.


Line range hint 308-396: Consistent spacing scale maintains visual harmony.

The 90% scaling has been consistently applied across all spacing utilities (padding, margin, space, gap). The mathematical calculations are accurate, and the values maintain proper proportions.


Line range hint 308-396: Verify the impact of spacing changes on responsive layouts.

The global reduction in spacing could affect component layouts and responsive behavior, particularly in dense UI areas like the Timeline.

#!/bin/bash
# Search for components using spacing utilities
echo "Checking components using spacing utilities..."
rg -l "\b(p|m|space|gap)-[0-9]" --type ts --type tsx

# Look for responsive design implementations
echo "Checking responsive design patterns..."
rg -l "@media|sm:|md:|lg:|xl:" --type ts --type tsx

Line range hint 292-307: Verify the impact of font size scaling on existing components.

While scaling down font sizes to 90% makes sense for the Timeline layout, this global change could affect readability in other parts of the application.

✅ Verification successful

Based on the search results, I can now provide a final response:

Font size scaling appears to be a deliberate design choice with widespread usage

The codebase shows extensive usage of text size utilities across numerous components (200+ files), and the font size scaling is part of a comprehensive design system where spacing, padding, and other related properties are consistently scaled to 90%. This appears to be an intentional design decision that maintains proportional relationships across the UI.

Key observations:

  • The scaling is applied uniformly across the entire design system
  • Text utilities are used consistently throughout the application
  • No accessibility-related issues or guidelines were found that would conflict with this scaling
  • The scaled sizes maintain readable proportions (e.g., base font at 0.9rem is still within acceptable readability ranges)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing font size utility usage that might be affected
echo "Checking components using font size utilities..."
rg -l "text-\b(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)\b" --type ts --type tsx

# Check for any accessibility-related font size requirements in documentation
echo "Checking for accessibility guidelines..."
fd -e md -e mdx | xargs rg -l "font.*size|text.*size|accessibility"

Length of output: 449


Script:

#!/bin/bash
# Search for existing font size utility usage that might be affected
echo "Checking components using font size utilities..."
rg -l "text-\b(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)\b"

# Look for any existing font size customizations in Tailwind configs
echo "Checking other Tailwind configurations..."
fd tailwind.config.js | xargs cat

# Check for any accessibility-related font size requirements in documentation
echo "Checking for accessibility guidelines..."
fd README.md | xargs rg -i "font|text|accessibility"

Length of output: 58994

apiserver/plane/bgtasks/issue_activities_task.py (2)

34-34: LGTM! Good separation of concerns.

The addition of get_inverse_relation import improves code organization by centralizing the relation mapping logic in a dedicated utility module.


Line range hint 1398-1412: Improved maintainability with centralized relation mapping.

The changes enhance the code by:

  1. Using get_inverse_relation utility to determine inverse relationships
  2. Removing hardcoded relation type mapping
  3. Making the code more maintainable and consistent

Let's verify the implementation of the inverse relation mapper:

✅ Verification successful

Centralized relation mapping implementation verified successfully

The code changes are well-implemented with a centralized relation mapping in apiserver/plane/utils/issue_relation_mapper.py. The get_inverse_relation function maintains a comprehensive bidirectional mapping for all relation types:

  • start_after ↔ start_before
  • finish_after ↔ finish_before
  • blocked_by ↔ blocking

The implementation is being used correctly in the issue activities task to maintain consistency in bidirectional relationships between issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of get_inverse_relation function
# Test: Search for the function definition and its usage
ast-grep --pattern 'def get_inverse_relation($_) {
  $$$
}'

Length of output: 61


Script:

#!/bin/bash
# Let's try searching for the function definition using ripgrep
rg -p "def get_inverse_relation" -A 10

# Also search for any relation type mapping or constants
rg -p "RELATION_TYPE|relation_type" -A 5

Length of output: 15132

web/core/components/gantt-chart/helpers/draggable.tsx (3)

1-11: Imports and dependencies are correctly structured

The imported modules are appropriate and necessary for the functionality of the component.


50-52: Ensure handleBlockDrag is properly invoked on block movement

The onMouseDown event handler correctly checks enableBlockMove before invoking handleBlockDrag. This prevents unintended drag actions when moving is disabled.


37-62: Component structure and rendering logic are appropriate

The structure of the component uses the new LeftResizable and RightResizable components effectively, improving modularity and separation of concerns.

web/core/components/gantt-chart/views/helpers.ts (3)

Line range hint 28-39: Verify correctness of week number calculation in getWeekNumberByDate.

The current implementation of getWeekNumberByDate may not accurately calculate the ISO week number for all dates due to variations in week definitions across different locales.

Would you like assistance in verifying this function or refactoring it to use a standard library like date-fns for reliable week number calculations?


117-120: Great job on structuring the timeline view helpers.

Exporting the timelineViewHelpers with the views for week, month, and quarter enhances modularity and makes it easier to manage different timeline perspectives.


95-95: ⚠️ Potential issue

Handle zero value in positionDaysDifference.

In the getItemPositionWidth function, the check if (!positionDaysDifference) return; will cause the function to return when positionDaysDifference is zero. This means items starting on the chart's start date won't be rendered.

Consider modifying the condition to properly handle zero:

-  if (!positionDaysDifference) return;
+  if (positionDaysDifference < 0) return;

This ensures that items with a valid positionDaysDifference of zero are processed correctly.

Likely invalid or redundant comment.

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

13-14: LGTM

The state management using useTimeLineChartStore and the mapping of renderView to weekBlocks is appropriate and aligns with the overall architecture.


12-12: 🛠️ Refactor suggestion

Avoid using any type in component definition

Using any as the props type in the component can lead to a loss of type safety. Specify the appropriate props interface for the WeekChartView component to enhance type checking and maintainability.

Apply this diff to define and use a specific props interface:

+ interface WeekChartViewProps {
+   // Define the props used by WeekChartView here, or use an empty object if there are none.
+ }

- export const WeekChartView: FC<any> = observer(() => {
+ export const WeekChartView: FC<WeekChartViewProps> = observer(() => {

Likely invalid or redundant comment.

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

50-51: Prevent unnecessary state updates in the IntersectionObserver callback

The setIsHidden and setIsBlockHiddenOnLeft functions are called on every intersection change, even if the values haven't changed, potentially causing redundant re-renders.

[performance]

Before updating the state, compare the new values with the current state to prevent unnecessary updates:

- setIsHidden(!entry.isIntersecting);
- setIsBlockHiddenOnLeft(entry.boundingClientRect.left < 0);
+ const newIsHidden = !entry.isIntersecting;
+ if (isHidden !== newIsHidden) {
+   setIsHidden(newIsHidden);
+ }
+ const newIsBlockHiddenOnLeft = entry.boundingClientRect.left < 0;
+ if (isBlockHiddenOnLeft !== newIsBlockHiddenOnLeft) {
+   setIsBlockHiddenOnLeft(newIsBlockHiddenOnLeft);
+ }

84-121: Overall implementation is robust and well-structured

The BlockRow component is well-designed, with clear state management and effective use of hooks. The component handles conditional rendering and user interactions efficiently.

web/core/components/gantt-chart/helpers/blockResizables/use-gantt-resizable.ts (1)

1-124: Overall implementation is sound and well-structured

The useGanttResizable hook is well-implemented, effectively managing the resizing and dragging functionality for Gantt chart blocks. The use of useRef and useState provides efficient state management, and event listeners are appropriately added and removed to prevent memory leaks.

🧰 Tools
🪛 Biome

[error] 107-107: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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

123-124: Ensure 'quarterNumber' is within valid range when accessing 'quarters' array

After adjusting quarterNumber to be 1-based, verify that accessing quarters[quarterNumber - 1] is within the valid index range of the quarters array. This prevents potential undefined errors.

Consider adding a check to ensure quarterNumber - 1 is not out of bounds:

const index = quarterNumber - 1;
if (index >= 0 && index < quarters.length) {
  const quarterData = quarters[index];
  // existing code
} else {
  // Handle invalid quarterNumber
}
web/core/components/gantt-chart/views/month-view.ts (2)

33-90: Well-Structured Date Handling in generateMonthChart

The generateMonthChart function efficiently handles date ranges based on the side parameter, enhancing the flexibility and maintainability of the month view generation.


141-153: Confirm Uniqueness in Merged Payloads

In the mergeMonthRenderPayloads function, months and weeks are merged using uniqBy with keys based on month-year and week start-end times. Ensure that these keys reliably prevent duplicates, especially during edge cases like daylight saving time changes or time zone differences.

Run the following script to check for potential duplicate entries:

✅ Verification successful

Based on the code analysis, I can now provide a final response:

The Uniqueness Keys are Properly Implemented

The uniqueness implementation is reliable and handles edge cases appropriately:

  • For months: The key ${monthBlock.month}_${monthBlock.year} is sufficient as months are identified by their numerical month and year values, which are timezone-independent.
  • For weeks: The key ${weekBlock.startDate.getTime()}_${weekBlock.endDate.getTime()} uses Unix timestamps (milliseconds since epoch) which are timezone-safe and DST-aware, ensuring unique identification of week blocks.

The codebase shows consistent timestamp handling across the gantt chart component, with proper date arithmetic in related files (helpers.ts, week-view.ts) supporting this implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that merged months and weeks have unique keys.

# Test: List all month keys and check for duplicates.
echo "Month keys:"
node -e "
  const months = [...]; // Replace with actual months array
  const keys = months.map(m => \`\${m.month}_\${m.year}\`);
  console.log(keys);
"

# Test: List all week keys and check for duplicates.
echo "Week keys:"
node -e "
  const weeks = [...]; // Replace with actual weeks array
  const keys = weeks.map(w => \`\${w.startDate.getTime()}_\${w.endDate.getTime()}\`);
  console.log(keys);
"

Length of output: 534


Script:

#!/bin/bash
# Let's analyze the implementation and usage of mergeMonthRenderPayloads

# First, let's find the interface definition for IMonthView
rg -t typescript "interface IMonthView" -A 10

# Then, let's find all usages of mergeMonthRenderPayloads
rg -t typescript "mergeMonthRenderPayloads"

# Let's also check for any test files that might validate this functionality
fd -e test.ts -e spec.ts | xargs rg "mergeMonthRenderPayloads"

# Finally, let's look for any timezone or date-related utility functions that might be used
rg -t typescript "getTime|timezone|UTC" -A 2 web/core/components/gantt-chart/

Length of output: 574


Script:

#!/bin/bash
# Let's try without typescript file type specification

# Find the interface definition
echo "=== IMonthView Interface ==="
rg "interface IMonthView" -A 10 web/core/components/gantt-chart/

# Look for the implementation context
echo "=== mergeMonthRenderPayloads Context ==="
rg "mergeMonthRenderPayloads" -B 5 -A 10 web/core/components/gantt-chart/

# Check for date/time handling
echo "=== Date/Time Usage ==="
rg "getTime|timezone|UTC" -A 2 web/core/components/gantt-chart/

# Look for any tests or specs
echo "=== Test Files Content ==="
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) -exec grep -l "monthView\|IMonthView" {} \; | xargs cat

Length of output: 4917

web/core/components/gantt-chart/chart/root.tsx (6)

7-9: LGTM: Updated import to useTimeLineChartStore

The import statement has been correctly updated to include the new hook.


12-13: LGTM: Added necessary type imports

The necessary types and helpers have been correctly imported from "../types" and "../views".


32-32: Added updateBlockDates prop to ChartViewRootProps

The optional updateBlockDates prop has been appropriately added to the component's props.


59-59: Destructured updateBlockDates from props

The updateBlockDates prop is correctly destructured from props, ensuring it can be utilized within the component.


66-66: Updated to use useTimeLineChartStore hook

The component now utilizes the useTimeLineChartStore hook, reflecting the updated state management approach.


176-176: Propagated updateBlockDates to GanttChartMainContent

The updateBlockDates function is correctly passed down to GanttChartMainContent, ensuring that block dates can be updated as needed.

web/core/components/gantt-chart/chart/main-content.tsx (2)

96-103: Verify the logic in 'onScroll' function calculations

The calculation of approxRangeLeft may not behave as intended. The condition scrollLeft >= clientWidth + 1000 might not correctly determine when to update the view. Review the logic to ensure that approxRangeLeft and approxRangeRight correctly reflect when to call updateCurrentViewRenderPayload.

Consider checking the calculations with different scroll positions to confirm their correctness.


121-121: Confirm 'ganttContainerRef' compatibility in 'TimelineDragHelper'

The TimelineDragHelper component receives ganttContainerRef as a prop. Verify that this ref aligns with the expected type and usage within TimelineDragHelper to ensure proper functionality.

web/ce/store/timeline/base-timeline.store.ts (1)

284-285: ⚠️ Potential issue

Implement logic for getIsCurrentDependencyDragging or remove placeholder

The method getIsCurrentDependencyDragging currently returns false for all inputs, suggesting it's a placeholder. Leaving it unimplemented may cause confusion or unintended behavior. If this method is not yet needed, consider removing it until it's ready to be implemented, or add a TODO comment indicating future work.

@@ -0,0 +1 @@
export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to";
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

⚠️ Potential issue

The temporal relation types are not used in the frontend Gantt chart implementation

Based on the evidence:

  1. Backend RELATION_CHOICES explicitly includes start_before and finish_before relations
  2. These temporal relations are actively used in the backend API (plane/app/views/issue/relation.py)
  3. The frontend type TIssueRelationTypes is missing these temporal relations
  4. The REVERSE_RELATIONS constant in web/ce/constants/gantt-chart.ts relies on this type, making temporal relations crucial for Gantt chart functionality

Please update the type definition to include all relation types:

-export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to";
+export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to" | "start_before" | "finish_before";
🔗 Analysis chain

Add missing temporal relation types for timeline functionality.

The type definition appears to be missing temporal relation types that exist in the backend model (start_before and finish_before). Given that this PR focuses on timeline views, these temporal relations could be crucial for proper Gantt chart functionality.

Apply this diff to include all relation types:

-export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to";
+export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to" | "start_before" | "finish_before";

Let's verify the relation types used in the codebase:


Let me gather more information about the actual relation types defined in the backend models.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of temporal relation types in the codebase
# Expected: Find instances where these relation types might be needed

# Check for temporal relation patterns in TypeScript/JavaScript files
echo "Checking for temporal relation patterns:"
rg -t ts -t js "start_before|finish_before" 

# Check for relation type definitions in Python files
echo -e "\nChecking backend relation definitions:"
rg -t py "RELATION_CHOICES|relation_type"

Length of output: 5420


Script:

#!/bin/bash
# Check the actual relation choices in the models
echo "Checking RELATION_CHOICES definition:"
rg -A 10 "RELATION_CHOICES = \(" apiserver/plane/db/models/issue.py

# Check if these temporal relations are already used in the frontend
echo -e "\nChecking frontend usage of relation types:"
rg -t ts "TIssueRelationTypes" -A 2

Length of output: 14654

Comment on lines +8 to +13
export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);

const initializeStore = () => {
const newGanttViewStore = ganttViewStore ?? new GanttStore();
if (typeof window === "undefined") return newGanttViewStore;
if (!ganttViewStore) ganttViewStore = newGanttViewStore;
return newGanttViewStore;
};

type GanttStoreProviderProps = {
children: React.ReactNode;
};
export const useTimeLineType = () => {
const timelineType = useContext(TimeLineTypeContext);

export const GanttStoreProvider: FC<GanttStoreProviderProps> = ({ children }) => {
const store = initializeStore();
return <GanttStoreContext.Provider value={store}>{children}</GanttStoreContext.Provider>;
return timelineType;
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

Add type safety and error handling to the timeline context.

The current implementation could be improved with better type safety and error handling:

Consider this enhanced implementation:

-export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);
+export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);
+if (process.env.NODE_ENV !== 'production') {
+  TimeLineTypeContext.displayName = 'TimeLineTypeContext';
+}

 export const useTimeLineType = () => {
   const timelineType = useContext(TimeLineTypeContext);
+  if (timelineType === undefined) {
+    throw new Error('useTimeLineType must be used within a TimeLineTypeContext.Provider');
+  }
   return timelineType;
 };

This change:

  1. Adds a descriptive display name for better debugging
  2. Ensures the hook is used within a provider
  3. Makes the return type non-nullable, improving type safety
📝 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
export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);
const initializeStore = () => {
const newGanttViewStore = ganttViewStore ?? new GanttStore();
if (typeof window === "undefined") return newGanttViewStore;
if (!ganttViewStore) ganttViewStore = newGanttViewStore;
return newGanttViewStore;
};
type GanttStoreProviderProps = {
children: React.ReactNode;
};
export const useTimeLineType = () => {
const timelineType = useContext(TimeLineTypeContext);
export const GanttStoreProvider: FC<GanttStoreProviderProps> = ({ children }) => {
const store = initializeStore();
return <GanttStoreContext.Provider value={store}>{children}</GanttStoreContext.Provider>;
return timelineType;
export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);
if (process.env.NODE_ENV !== 'production') {
TimeLineTypeContext.displayName = 'TimeLineTypeContext';
}
export const useTimeLineType = () => {
const timelineType = useContext(TimeLineTypeContext);
if (timelineType === undefined) {
throw new Error('useTimeLineType must be used within a TimeLineTypeContext.Provider');
}
return timelineType;
};

Comment on lines +12 to +18
export const TimelineDragHelper = observer((props: Props) => {
const { ganttContainerRef } = props;
const { isDragging } = useTimeLineChartStore();

useAutoScroller(ganttContainerRef, isDragging, SIDEBAR_WIDTH, HEADER_HEIGHT);
return <></>;
});
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 enhancing component documentation and type safety.

While the implementation is correct, it could benefit from additional documentation and type safety improvements.

+/**
+ * Helper component that enables auto-scrolling functionality during drag operations in the Gantt chart.
+ * @param props.ganttContainerRef - Reference to the Gantt chart container element
+ */
-export const TimelineDragHelper = observer((props: Props) => {
+export const TimelineDragHelper: React.FC<Props> = observer((props) => {
   const { ganttContainerRef } = props;
   const { isDragging } = useTimeLineChartStore();
 
   useAutoScroller(ganttContainerRef, isDragging, SIDEBAR_WIDTH, HEADER_HEIGHT);
   return <></>;
 });
+
+TimelineDragHelper.displayName = "TimelineDragHelper";
📝 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
export const TimelineDragHelper = observer((props: Props) => {
const { ganttContainerRef } = props;
const { isDragging } = useTimeLineChartStore();
useAutoScroller(ganttContainerRef, isDragging, SIDEBAR_WIDTH, HEADER_HEIGHT);
return <></>;
});
/**
* Helper component that enables auto-scrolling functionality during drag operations in the Gantt chart.
* @param props.ganttContainerRef - Reference to the Gantt chart container element
*/
export const TimelineDragHelper: React.FC<Props> = observer((props) => {
const { ganttContainerRef } = props;
const { isDragging } = useTimeLineChartStore();
useAutoScroller(ganttContainerRef, isDragging, SIDEBAR_WIDTH, HEADER_HEIGHT);
return <></>;
});
TimelineDragHelper.displayName = "TimelineDragHelper";

Comment on lines +2 to +9
relation_mapping = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocked_by": "blocking",
"blocking": "blocked_by",
"start_before": "start_after",
"finish_before": "finish_after",
}
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 defining relation mapping as a module-level constant.

Since the relation mapping is static, it should be defined as a module-level constant to improve maintainability and prevent recreation on each function call.

Consider refactoring to:

+INVERSE_RELATION_MAPPING = {
+    "start_after": "start_before",
+    "finish_after": "finish_before",
+    "blocked_by": "blocking",
+    "blocking": "blocked_by",
+    "start_before": "start_after",
+    "finish_before": "finish_after",
+}
+
 def get_inverse_relation(relation_type):
-    relation_mapping = {
-        "start_after": "start_before",
-        "finish_after": "finish_before",
-        "blocked_by": "blocking",
-        "blocking": "blocked_by",
-        "start_before": "start_after",
-        "finish_before": "finish_after",
-    }
-    return relation_mapping.get(relation_type, relation_type)
+    return INVERSE_RELATION_MAPPING.get(relation_type, relation_type)
📝 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
relation_mapping = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocked_by": "blocking",
"blocking": "blocked_by",
"start_before": "start_after",
"finish_before": "finish_after",
}
INVERSE_RELATION_MAPPING = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocked_by": "blocking",
"blocking": "blocked_by",
"start_before": "start_after",
"finish_before": "finish_after",
}
def get_inverse_relation(relation_type):
return INVERSE_RELATION_MAPPING.get(relation_type, relation_type)

Comment on lines +13 to +24
def get_actual_relation(relation_type):
# This function is used to get the actual relation type which is store in database
actual_relation = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocking": "blocked_by",
"blocked_by": "blocked_by",
"start_before": "start_before",
"finish_before": "finish_before",
}

return actual_relation.get(relation_type, relation_type)
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

Improve function documentation and consider mapping optimization.

The function has several areas for improvement:

  1. The current comment should be converted to a proper docstring
  2. Some mappings appear redundant (e.g., "blocked_by" -> "blocked_by")
  3. The mapping should be a module-level constant like suggested for get_inverse_relation

Consider this refactoring:

+ACTUAL_RELATION_MAPPING = {
+    "start_after": "start_before",
+    "finish_after": "finish_before",
+    "blocking": "blocked_by",
+    "start_before": "start_before",
+    "finish_before": "finish_before",
+}
+
 def get_actual_relation(relation_type):
-    # This function is used to get the actual relation type which is store in database
-    actual_relation = {
-        "start_after": "start_before",
-        "finish_after": "finish_before",
-        "blocking": "blocked_by",
-        "blocked_by": "blocked_by",
-        "start_before": "start_before",
-        "finish_before": "finish_before",
-    }
-
-    return actual_relation.get(relation_type, relation_type)
+    """
+    Maps a relation type to its canonical form as stored in the database.
+    
+    Args:
+        relation_type (str): The relation type to normalize
+        
+    Returns:
+        str: The normalized relation type for database storage
+    """
+    return ACTUAL_RELATION_MAPPING.get(relation_type, relation_type)
📝 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
def get_actual_relation(relation_type):
# This function is used to get the actual relation type which is store in database
actual_relation = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocking": "blocked_by",
"blocked_by": "blocked_by",
"start_before": "start_before",
"finish_before": "finish_before",
}
return actual_relation.get(relation_type, relation_type)
ACTUAL_RELATION_MAPPING = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocking": "blocked_by",
"start_before": "start_before",
"finish_before": "finish_before",
}
def get_actual_relation(relation_type):
"""
Maps a relation type to its canonical form as stored in the database.
Args:
relation_type (str): The relation type to normalize
Returns:
str: The normalized relation type for database storage
"""
return ACTUAL_RELATION_MAPPING.get(relation_type, relation_type)

Comment on lines +157 to +158
<ActiveChartView />
{currentViewData && (
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 lazy loading 'ActiveChartView' for performance

If ActiveChartView is a large component, utilizing React.lazy and Suspense could improve the initial load performance by code-splitting and lazy loading the component when needed.

Example implementation:

+import React, { lazy, Suspense } from "react";
...
-const ActiveChartView = CHART_VIEW_COMPONENTS[currentView];
+const ActiveChartView = lazy(() => Promise.resolve({ default: CHART_VIEW_COMPONENTS[currentView] }));
...
return (
  ...
+  <Suspense fallback={<div>Loading...</div>}>
      <ActiveChartView />
+  </Suspense>
  ...
);

Committable suggestion was skipped due to low confidence.

Comment on lines +41 to +42
getUpdatedPositionAfterDrag: (id: string, ignoreDependencies?: boolean) => IBlockUpdateDependencyData[];
updateBlockPosition: (id: string, deltaLeft: number, deltaWidth: number, ignoreDependencies?: boolean) => void;
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

Ensure method signatures match between interface and implementation

The methods getUpdatedPositionAfterDrag and updateBlockPosition in the IBaseTimelineStore interface include an ignoreDependencies?: boolean parameter, but this parameter is missing in the implementation within BaseTimeLineStore. This discrepancy can lead to type inconsistencies and unexpected behaviors.

Apply this diff to align the method signatures:

// In BaseTimeLineStore class

-getUpdatedPositionAfterDrag = (id: string) => {
+getUpdatedPositionAfterDrag = (id: string, ignoreDependencies?: boolean) => {

-updateBlockPosition = (id: string, deltaLeft: number, deltaWidth: number) => {
+updateBlockPosition = (id: string, deltaLeft: number, deltaWidth: number, ignoreDependencies?: boolean) => {

Also applies to: 222-222

currentView: TGanttViews;
currentViewData: ChartDataType | undefined;
activeBlockId: string | null;
renderView: any;
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

⚠️ Potential issue

Specify a concrete type instead of using any for renderView

The property renderView and its related methods use the any type, which can undermine TypeScript's type safety benefits. Defining a specific type for renderView will enhance code reliability and maintainability.

Consider defining a type for renderView:

// Define a type for renderView data
type RenderViewType = YourSpecificTypeHere;

// Update the property and method signatures
renderView: RenderViewType = [];

updateRenderView: (data: RenderViewType) => void;

Also applies to: 58-58, 143-143


updates = request.data.get("updates", [])

issue_ids = [update["id"] for update in updates]
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

Add validation for the presence of the "id" key in updates

The code assumes that every update in updates contains an "id" key. If an update is missing this key, a KeyError will be raised. To prevent this, consider adding validation to handle missing "id" keys gracefully.

Apply this diff to fix the issue:

- issue_ids = [update["id"] for update in updates]
+ issue_ids = [update["id"] for update in updates if "id" in update]

...

- issue_id = update["id"]
+ issue_id = update.get("id")
+ if not issue_id:
+     continue  # Skip or handle the error appropriately

Also applies to: 1108-1108


# Fetch all relevant issues in a single query
issues = list(Issue.objects.filter(id__in=issue_ids))
issues_dict = {str(issue.id): issue for issue in issues}
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

Ensure consistent types for issue IDs when accessing issues_dict

Currently, issues_dict uses string representations of issue IDs as keys (str(issue.id)), but issue_id from update["id"] may be a UUID object. This type mismatch can lead to KeyError when retrieving issues from the dictionary. Ensure that both the keys and the lookup use consistent types.

Apply this diff to fix the issue:

- issues_dict = {str(issue.id): issue for issue in issues}
+ issues_dict = {issue.id: issue for issue in issues}

Also applies to: 1110-1110

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: 16

🧹 Outside diff range and nitpick comments (17)
web/ce/components/relations/index.tsx (1)

6-35: Well-structured relation options with room for enhancement.

The implementation provides a type-safe and consistent way to define issue relations with appropriate visual distinctions. However, consider the following improvements:

 export const ISSUE_RELATION_OPTIONS: Record<TIssueRelationTypes, TRelationObject> = {
   relates_to: {
     key: "relates_to",
     label: "Relates to",
-    className: "bg-custom-background-80 text-custom-text-200",
+    className: "bg-custom-background-80 text-custom-text-200",
+    description: "Issues that are related but not blocked",
+    ariaLabel: "Related issue",
     icon: (size) => <RelatedIcon height={size} width={size} className="text-custom-text-200" />,
     placeholder: "Add related issues",
   },
   // Apply similar changes to other relation types...
 };

Consider these enhancements:

  1. Add descriptions to improve documentation
  2. Include aria-labels for better accessibility
  3. Consider extracting color classes into theme constants for better maintainability
web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx (1)

33-40: LGTM! Consider adding type safety to Object.values.

The changes improve the menu's UI with controlled height and use a more idiomatic approach with Object.values(). The implementation is clean and maintains good separation of concerns.

Consider adding explicit typing to improve type safety:

-      {Object.values(ISSUE_RELATION_OPTIONS).map((item, index) => (
+      {Object.values(ISSUE_RELATION_OPTIONS).map((item: typeof ISSUE_RELATION_OPTIONS[keyof typeof ISSUE_RELATION_OPTIONS], index) => (
web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx (1)

25-25: Consider adding null check and extracting icon size

The icon rendering logic looks good, but consider these improvements:

  1. Add a null check for activity.field to prevent potential runtime errors
  2. Extract the hardcoded icon size (14) to a constant or prop for better maintainability
+ const ICON_SIZE = 14;
+
  <IssueActivityBlockComponent
-    icon={activity.field ? ISSUE_RELATION_OPTIONS[activity.field as TIssueRelationTypes].icon(14) : <></>}
+    icon={activity.field && ISSUE_RELATION_OPTIONS[activity.field as TIssueRelationTypes]?.icon(ICON_SIZE) || <></>}
web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (1)

Line range hint 10-15: Consider enhancing type safety for issue-related props.

Consider creating a dedicated interface for the component props that includes validation for the ID formats. This would help catch potential issues at compile-time.

interface IssueDetailWidgetProps {
  workspaceSlug: `${string}-${string}`; // assuming UUID format
  projectId: string;
  issueId: string;
  disabled: boolean;
}
web/core/components/issues/issue-detail-widgets/relations/helper.tsx (1)

Line range hint 19-95: Consider enhancing error handling and user feedback.

The hook implementation is solid, but there are a few areas that could be improved:

  1. The remove operation swallows errors silently
  2. The success/error messages could be more descriptive
  3. The copyText function lacks error handling

Consider applying these improvements:

 copyText: (text: string) => {
   const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
-  copyTextToClipboard(`${originURL}/${text}`).then(() => {
-    setToast({
-      type: TOAST_TYPE.SUCCESS,
-      title: "Link Copied!",
-      message: "Issue link copied to clipboard.",
-    });
-  });
+  copyTextToClipboard(`${originURL}/${text}`)
+    .then(() => {
+      setToast({
+        type: TOAST_TYPE.SUCCESS,
+        title: "Link Copied!",
+        message: "Issue link copied to clipboard.",
+      });
+    })
+    .catch(() => {
+      setToast({
+        type: TOAST_TYPE.ERROR,
+        title: "Error!",
+        message: "Failed to copy issue link to clipboard.",
+      });
+    });
 },

 // In the update operation
 setToast({
   title: "Success!",
   type: TOAST_TYPE.SUCCESS,
-  message: "Issue updated successfully",
+  message: `Issue ${issueId} updated successfully`,
 });

 // In the remove operation
 remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
   try {
     return removeIssue(workspaceSlug, projectId, issueId).then(() => {
       captureIssueEvent({
         eventName: ISSUE_DELETED,
         payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
         path: pathname,
       });
+      setToast({
+        type: TOAST_TYPE.SUCCESS,
+        title: "Success!",
+        message: `Issue ${issueId} deleted successfully`,
+      });
     });
   } catch (error) {
     captureIssueEvent({
       eventName: ISSUE_DELETED,
       payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
       path: pathname,
     });
+    setToast({
+      type: TOAST_TYPE.ERROR,
+      title: "Error!",
+      message: `Failed to delete issue ${issueId}`,
+    });
+    throw error; // Re-throw to allow caller to handle
   }
 },
web/core/components/gantt-chart/chart/views/week.tsx (3)

11-11: Add proper typing for component props instead of using any.

Using FC<any> reduces type safety. Consider creating a proper interface for the component props, even if currently empty.

- export const WeekChartView: FC<any> = observer(() => {
+ interface WeekChartViewProps {}
+ export const WeekChartView: FC<WeekChartViewProps> = observer(() => {

12-13: Improve variable naming clarity.

The variable renderView is being assigned to weekBlocks, which suggests that renderView might not be the most descriptive name in the store. Consider renaming it in the store to match its usage.


16-91: Add ARIA attributes for accessibility.

The calendar-like interface should include appropriate ARIA attributes and roles for better accessibility. Consider adding:

  • role="grid" to the main container
  • role="columnheader" to day headers
  • role="gridcell" to day cells
  • Appropriate aria-label attributes for dates

Example for the main container:

- <div className={`absolute top-0 left-0 h-max w-max flex`}
+ <div 
+   role="grid"
+   aria-label="Week view calendar"
+   className={`absolute top-0 left-0 h-max w-max flex`}
web/core/components/gantt-chart/chart/views/quarter.tsx (1)

12-13: Add type safety to hook destructuring.

Consider adding explicit type annotations to the destructured values from useTimeLineChartStore to ensure type safety throughout the component.

-  const { currentViewData, renderView } = useTimeLineChartStore();
+  const { 
+    currentViewData, 
+    renderView 
+  }: { 
+    currentViewData: TimelineViewData; 
+    renderView: IMonthBlock[] 
+  } = useTimeLineChartStore();
web/core/components/gantt-chart/chart/views/month.tsx (3)

22-25: Consider adding validation for date calculations.

While the date calculations look correct, consider adding validation to ensure months and weeks arrays are not empty before accessing their first elements.

-  const monthsStartDate = new Date(months[0].year, months[0].month, 1);
-  const weeksStartDate = weeks[0].startDate;
+  const monthsStartDate = months.length > 0 ? new Date(months[0].year, months[0].month, 1) : new Date();
+  const weeksStartDate = weeks.length > 0 ? weeks[0].startDate : new Date();

62-87: Enhance accessibility for week headers.

The week headers could benefit from improved accessibility for screen readers.

 <div
   key={`sub-title-${weekBlock.startDate}-${weekBlock.endDate}`}
+  role="columnheader"
+  aria-label={`Week ${weekBlock.weekData.shortTitle}, ${weekBlock.startDate.getDate()}-${weekBlock.endDate.getDate()}`}
   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,
     }
   )}

89-102: Consider virtualization for better performance.

For large datasets, rendering all week columns simultaneously could impact performance. Consider implementing virtualization to render only visible columns.

Consider using a virtualization library like react-window or react-virtualized to optimize the rendering of week columns. This would be especially beneficial when displaying many weeks of data.

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

Line range hint 108-108: Fix incomplete gap value in flex container.

The className has an incomplete gap value: gap-

-<div className="flex flex-col gap-">
+<div className="flex flex-col gap-2">

Line range hint 127-168: Consider extracting modal rendering logic to reduce duplication.

The modal rendering logic for delete and update operations follows a similar pattern. Consider extracting this into a reusable component or helper function to improve maintainability.

Example refactor:

type ModalProps = {
  type: 'delete' | 'update';
  state: TIssueCrudState;
  onClose: () => void;
  onSubmit: (issue?: TIssue) => Promise<void>;
};

const IssueModal: FC<ModalProps> = ({ type, state, onClose, onSubmit }) => {
  if (!state.toggle || !state.issue) return null;

  return type === 'delete' ? (
    <DeleteIssueModal
      isOpen={state.toggle}
      handleClose={onClose}
      data={state.issue}
      onSubmit={onSubmit}
    />
  ) : (
    <CreateUpdateIssueModal
      isOpen={state.toggle}
      onClose={onClose}
      data={state.issue}
      onSubmit={onSubmit}
    />
  );
};
web/core/store/issue/issue-details/relation.store.ts (3)

1-3: Consolidate Lodash Imports

Consider consolidating your Lodash imports into a single statement to improve code readability and maintainability.

Apply this change:

-import get from "lodash/get";
-import set from "lodash/set";
-import uniq from "lodash/uniq";
+import { get, set, uniq } from "lodash";

42-42: Update Interface Documentation

Ensure that the new methods getRelationCountByIssueId, extractRelationsFromIssues, and createCurrentRelation are fully documented in the IIssueRelationStore interface for better clarity and to assist other developers.

Also applies to: 44-45


176-222: Improve Error Logging in createCurrentRelation

In the catch block, consider logging the error or providing more context to aid in debugging if the API call fails. This can help identify issues more quickly during development.

Add error logging:

 catch (e) {
   // Revert back store changes if API fails
   runInAction(() => {
     // revert changes
   });
+  console.error("Failed to create current relation:", e);
   throw e;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 24db506 and bea7602.

📒 Files selected for processing (14)
  • web/ce/components/relations/index.tsx (1 hunks)
  • web/ce/store/timeline/base-timeline.store.ts (1 hunks)
  • web/core/components/gantt-chart/chart/views/month.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/quarter.tsx (1 hunks)
  • web/core/components/gantt-chart/chart/views/week.tsx (1 hunks)
  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (1 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/content.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/helper.tsx (1 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx (2 hunks)
  • web/core/components/issues/issue-detail-widgets/relations/title.tsx (1 hunks)
  • web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx (2 hunks)
  • web/core/components/issues/issue-detail/relation-select.tsx (3 hunks)
  • web/core/store/issue/issue-details/relation.store.ts (8 hunks)
  • web/ee/components/relations/index.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • web/ee/components/relations/index.tsx
🧰 Additional context used
🪛 Biome
web/ce/store/timeline/base-timeline.store.ts

[error] 185-185: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (17)
web/core/components/issues/issue-detail-widgets/relations/title.tsx (1)

20-20: LGTM! Good performance optimization.

The change from getRelationsByIssueId to getRelationCountByIssueId is a nice optimization. Instead of retrieving all relations and counting them, we now directly get the count, which is more efficient especially when dealing with large numbers of relations.

Also applies to: 24-24

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

1-4: LGTM! Well-organized imports.

The imports are properly structured, separating third-party libraries, internal UI components, and type definitions.


6-35: Verify the usage of ISSUE_RELATION_OPTIONS across the codebase.

Let's ensure this centralized configuration is properly utilized by other components.

✅ Verification successful

ISSUE_RELATION_OPTIONS is properly integrated across the codebase

The configuration is correctly utilized throughout the codebase:

  • Used for rendering relation UI elements in relation-select.tsx
  • Powers the relation menu options in quick-action-button.tsx
  • Drives the relation collapsible sections in content.tsx
  • Handles activity tracking in relation.tsx
  • Manages relation state and operations in relation.store.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of ISSUE_RELATION_OPTIONS across the codebase

# Search for imports of ISSUE_RELATION_OPTIONS
echo "Checking imports of ISSUE_RELATION_OPTIONS:"
rg "import.*ISSUE_RELATION_OPTIONS.*from" -A 2

# Search for direct usage of the constant
echo "\nChecking usage of ISSUE_RELATION_OPTIONS:"
rg "ISSUE_RELATION_OPTIONS\." -A 2

Length of output: 2075


Script:

#!/bin/bash
# Let's search for actual usage patterns of ISSUE_RELATION_OPTIONS in the files

echo "Checking content of relation-select.tsx:"
cat "web/core/components/issues/issue-detail/relation-select.tsx"

echo -e "\nChecking content of quick-action-button.tsx:"
cat "web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx"

echo -e "\nChecking content of content.tsx:"
cat "web/core/components/issues/issue-detail-widgets/relations/content.tsx"

echo -e "\nChecking content of relation.tsx:"
cat "web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx"

echo -e "\nChecking content of relation.store.ts:"
cat "web/core/store/issue/issue-details/relation.store.ts"

Length of output: 25700

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

5-7: LGTM: Improved code organization with centralized imports

The migration to centralized imports from @/plane-web namespace improves maintainability by ensuring consistent relation definitions across components.


Line range hint 31-42: Verify handling of all relation types

The component handles 'blocking', 'blocked_by', 'duplicate', and 'relates_to' relations. Let's verify this covers all possible relation types.

✅ Verification successful

Let me gather more information to locate the definition of relation types.


Let me search in the relations-specific files we found to locate the relation types definition.


Let me try one more search without file type restrictions.


Based on the search results, I can now provide a final response. The code shows that all relation types are properly handled:

All relation types are correctly implemented

The component handles all possible relation types as defined in web/ce/types/gantt-chart.ts:

export type TIssueRelationTypes = "blocking" | "blocked_by" | "duplicate" | "relates_to";

The current implementation in relation.tsx covers all these types:

  • blocking
  • blocked_by
  • duplicate
  • relates_to
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if all relation types from ISSUE_RELATION_OPTIONS are handled in the component

# Search for the definition of ISSUE_RELATION_OPTIONS to compare with handled types
ast-grep --pattern 'const ISSUE_RELATION_OPTIONS = {
  $$$
}'

# Search for all relation type handling cases in the component
rg "activity\.field === \".*?\"" "web/core/components/issues/issue-detail/issue-activity/activity/actions/relation.tsx"

Length of output: 354


Script:

#!/bin/bash
# Search for any constants or types related to issue relations
rg -i "relation|issue.*type" --type typescript -A 3

# Search specifically in the issues directory
fd . "web/core/components/issues" -e ts -e tsx --exec rg -l "enum|type|interface.*Relation"

Length of output: 19252


Script:

#!/bin/bash
# Search in relations-related files for type definitions
rg -t tsx -t ts "type.*Relation|enum.*Relation|interface.*Relation|RELATION" web/core/components/issues/relations/
rg -t tsx -t ts "type.*Relation|enum.*Relation|interface.*Relation|RELATION" web/core/components/issues/issue-detail/relation-select.tsx

Length of output: 311


Script:

#!/bin/bash
# Search for relation type definitions across all files
rg "enum.*Relation|RELATION_TYPE.*=|ISSUE_RELATION.*=|type.*Relation.*=|interface.*Relation.*\{" -A 5

# Also search specifically in the constants or types files
fd "types|constants" -e ts -e tsx --exec rg "Relation" {}

Length of output: 8522

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

27-27: LGTM: Improved performance by using relation count.

The change from getRelationsByIssueId to getRelationCountByIssueId is a good optimization as it avoids fetching unnecessary relation data when only the count is needed.


33-33: LGTM: Clean implementation of relations count.

The implementation is clear and maintains a single responsibility of getting the relations count.


37-37: Verify handling of edge cases in relation count.

While the simplified condition issueRelationsCount > 0 is cleaner, please verify that the count accurately reflects valid relations. Consider cases where relations might be marked as deleted or invalid but still counted.

✅ Verification successful

Let me check the relation validation logic further to understand how relations are handled.


Let me check one more aspect to understand how relations are extracted and validated.


The simplified condition is safe and correctly handles relation validity

The getRelationCountByIssueId implementation already handles validation by:

  1. Only counting relations that exist in ISSUE_RELATION_OPTIONS
  2. Relations are properly extracted and validated during extractRelationsFromIssues
  3. The relation types are strictly typed as "blocking", "blocked_by", "duplicate", or "relates_to"

The count accurately reflects valid relations, making the simplified render condition both performant and correct.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for relation validation logic
rg -A 5 "getRelationCountByIssueId" --type ts

Length of output: 3789


Script:

#!/bin/bash
# Check relation types and validation
ast-grep --pattern 'const ISSUE_RELATION_OPTIONS = {
  $$$
}'

# Check how relations are stored and filtered
rg -A 5 "getRelationsByIssueId" --type ts

Length of output: 3148


Script:

#!/bin/bash
# Check relation extraction and validation
rg -A 10 "extractRelationsFromIssues" --type ts

# Check relation types
rg "type TIssueRelationTypes" -A 5 --type ts

Length of output: 5171

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

Line range hint 1-13: LGTM! Clean imports and well-structured type definitions.

The imports are properly organized and the type definitions are comprehensive.

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

15-15: Verify edge case handling in groupMonthsToQuarters.

Please ensure that the groupMonthsToQuarters function properly handles edge cases such as:

  • Empty monthBlocks array
  • Months spanning across years
  • Incomplete quarters
✅ Verification successful

Let me gather more information about the implementation of groupMonthsToQuarters function.


Let me get more context about the interfaces and the quarters constant being used.


Let me get the complete implementation of groupMonthsToQuarters and the quarters constant definition to verify the edge cases handling.


Let me try to get the quarters constant definition with a different approach.


Based on the implementation details gathered, I can now provide the final response:

The groupMonthsToQuarters function properly handles edge cases

The implementation shows robust handling of edge cases:

  • Empty monthBlocks array: Returns an empty array as Object.values() of empty quartersMap
  • Months spanning across years: Handled by using unique quarterKey with year (Q${quarterNumber}-${year})
  • Incomplete quarters: Quarters are built incrementally as months are processed, so partial quarters are valid
  • Current quarter detection: Properly identifies current quarter using todayQuarterNumber and todayYear
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the implementation and tests of groupMonthsToQuarters
ast-grep --pattern 'function groupMonthsToQuarters($_) {
  $$$
}'

# Search for any test files related to this function
rg -l "groupMonthsToQuarters"

Length of output: 207


Script:

#!/bin/bash
# Get the implementation details from both files
rg -A 20 "groupMonthsToQuarters" web/core/components/gantt-chart/views/quarter-view.ts
rg -A 20 "groupMonthsToQuarters" web/core/components/gantt-chart/chart/views/quarter.tsx

# Also search for any interfaces or types related to quarters
rg "interface.*Quarter" web/core/components/gantt-chart/

Length of output: 2405


Script:

#!/bin/bash
# Get the interface definition and quarters constant
rg -A 10 "interface IQuarterMonthBlock" web/core/components/gantt-chart/views/quarter-view.ts
rg -B 5 -A 10 "const quarters" web/core/components/gantt-chart/views/quarter-view.ts

# Get the IMonthBlock interface definition
rg -A 10 "interface IMonthBlock" web/core/components/gantt-chart/

Length of output: 1813


Script:

#!/bin/bash
# Get the complete implementation of groupMonthsToQuarters and quarters constant
rg -B 10 -A 30 "export const groupMonthsToQuarters" web/core/components/gantt-chart/views/quarter-view.ts

# Get the quarters constant definition
rg -B 2 -A 10 "const quarters = {" web/core/components/gantt-chart/views/quarter-view.ts

Length of output: 1515


Script:

#!/bin/bash
# Get the quarters constant definition using a simpler pattern
rg "quarters = \{" -A 10 web/core/components/gantt-chart/views/quarter-view.ts

# Also check if there are any tests for edge cases
rg -l "test|spec|describe.*groupMonthsToQuarters" web/core/components/gantt-chart/

Length of output: 165

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

17-20: LGTM! Proper null checking implemented.

Good defensive programming with the null check before destructuring the monthView data.


28-30: LGTM! Clean layout structure.

The component layout is well-structured with proper conditional rendering and dynamic height calculation.


3-16: LGTM! Verify hook migration completeness.

The migration from useGanttChart to useTimeLineChartStore aligns with the Timeline layout revamp. The imports are properly structured with clear separation of concerns.

Let's verify that all instances of the old hook have been migrated:

✅ Verification successful

Hook migration is complete and consistent

The migration from useGanttChart to useTimeLineChartStore has been fully completed. The search results show:

  • No instances of the old useGanttChart hook remain in the codebase
  • useTimeLineChartStore is consistently used across all Gantt chart components
  • The hook is properly defined in web/core/hooks/use-timeline-chart.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining instances of the old hook
rg "useGanttChart" --type ts --type tsx

Length of output: 67


Script:

#!/bin/bash
# Search for any remaining instances of the old hook using ripgrep without file type filters
rg "useGanttChart"

# Also search for timeline-related hooks to verify the migration
rg "useTimeLineChartStore"

Length of output: 4389

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

5-5: LGTM! Import consolidation and type definitions look good.

The changes properly address the previous duplicate constant issue by importing ISSUE_RELATION_OPTIONS from a centralized location. The new TRelationObject type adds clarity to the relation object structure.

Also applies to: 12-14, 27-33

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

7-19: LGTM! Well-organized imports and type definitions.

The reorganization of imports and centralization of ISSUE_RELATION_OPTIONS improves code maintainability and follows the DRY principle.


Line range hint 1-152: Verify the relationship with Timeline Layout revamp.

This component handles issue relations but seems tangential to the PR's main objective of revamping Timeline Layout with Week, Month, and Quarter views. Could you clarify how these changes contribute to the Timeline Layout revamp?

Let's check for any Timeline-related connections:

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

88-97: Effective Use of computedFn for Memoization

Great use of computedFn from mobx-utils to memoize the getRelationCountByIssueId method. This enhances performance by caching computed values based on inputs.

Comment on lines +8 to +10
// Plane-web
import { ISSUE_RELATION_OPTIONS } from "@/plane-web/components/relations";
import { TIssueRelationTypes } from "@/plane-web/types";
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

Import paths need attention - inconsistent usage detected

The search results reveal inconsistencies in the import paths:

  • Most files use the new @/plane-web/types path for TIssueRelationTypes
  • However, files in web/ce/ still use relative paths (e.g., "../../types", "../types")
  • The plane-web directory doesn't appear to exist in the filesystem (empty fd results)

This suggests:

  • The import paths in the reviewed file are incorrect or premature
  • The codebase is in an inconsistent state regarding these imports
  • The migration to @/plane-web/ structure might be incomplete or incorrect
🔗 Analysis chain

Verify the consistency of new import paths across the codebase.

The migration of types and constants to centralized locations (@/plane-web/types and @/plane-web/components/relations) is a good practice for maintainability. Let's ensure this pattern is consistently followed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that old import paths are not still in use
# and new paths are consistently used across the codebase

# Check for any remaining old-style imports
echo "Checking for old import paths..."
rg "from ['\"]@plane/types['\"].*TIssueRelationTypes"
rg "from ['\"]./helper['\"].*ISSUE_RELATION_OPTIONS"

# Verify new import pattern usage
echo "Checking new import paths usage..."
rg "from ['\"]@/plane-web/types['\"].*TIssueRelationTypes"
rg "from ['\"]@/plane-web/components/relations['\"].*ISSUE_RELATION_OPTIONS"

Length of output: 420


Script:

#!/bin/bash
# Let's try a broader search to understand the usage patterns

# Search for any imports of TIssueRelationTypes
echo "Searching for TIssueRelationTypes imports..."
rg "import.*TIssueRelationTypes" -A 1

# Search for any imports of ISSUE_RELATION_OPTIONS
echo "Searching for ISSUE_RELATION_OPTIONS imports..."
rg "import.*ISSUE_RELATION_OPTIONS" -A 1

# Search for files containing these identifiers
echo "Searching for files containing these identifiers..."
rg "TIssueRelationTypes|ISSUE_RELATION_OPTIONS" --files-with-matches

# Check if the new directories exist
echo "Checking directory structure..."
fd "plane-web"

Length of output: 5150

<div className="relative border-b border-custom-border-200">
<div className="sticky left-0 inline-flex whitespace-nowrap px-2 py-1 text-sm font-medium capitalize">
{_itemRoot?.title}
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
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 incomplete style calculation.

The minHeight style calculation is missing a closing parenthesis.

- style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}
+ style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px)` }}
📝 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
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px)` }}>

Comment on lines +83 to +85
{["sat", "sun"].includes(weekDay?.dayData?.shortTitle) && (
<div className="h-full bg-custom-background-90 outline-[0.25px] outline outline-custom-border-300" />
)}
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

Extract weekend days to constants.

The weekend days check uses a hardcoded array literal. Consider moving this to a constant for better maintainability and reusability.

+ const WEEKEND_DAYS = ['sat', 'sun'] as const;

// Later in the code
- {["sat", "sun"].includes(weekDay?.dayData?.shortTitle) && (
+ {WEEKEND_DAYS.includes(weekDay?.dayData?.shortTitle) && (

Committable suggestion was skipped due to low confidence.

Comment on lines +18 to +19
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
{currentViewData &&
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 moving inline styles to CSS classes.

The component uses multiple inline styles for dimensions and positioning. Consider moving these to CSS classes for better maintainability and reusability.

// Create a new CSS module file: quarter-chart.module.css
+.timelineContainer {
+  min-height: calc(100% + var(--header-height));
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: max-content;
+  width: max-content;
+  display: flex;
+}

+.headerHeight {
+  height: var(--header-height);
+}

+.sidebarOffset {
+  left: var(--sidebar-width);
+}

// Update the component
-<div className={`absolute top-0 left-0 h-max w-max flex`} 
-  style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
+<div className={styles.timelineContainer}>

Also applies to: 29-30, 37-38, 62-62, 85-85

Comment on lines +53 to +74
{quarterBlock?.children?.map((monthBlock, index) => (
<div
key={`sub-title-${rootIndex}-${index}`}
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,
}
)}
style={{ width: `${currentViewData?.data.dayWidth * monthBlock.days}px` }}
>
<div className="space-x-1 flex items-center justify-center text-xs font-medium h-full">
<span
className={cn({
"rounded-lg bg-custom-primary-100 px-2 text-white": monthBlock.today,
})}
>
<div>{_item.title}</div>
</div>
<div className={`relative flex h-full w-full flex-1 justify-center`}>
{_item?.today && <div className="absolute bottom-0 top-0 border border-red-500"> </div>}
</div>
{monthBlock.monthData.shortTitle}
</span>
</div>
))}
</div>
))}
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 rendering performance with memoization.

The nested mapping of quarterBlock.children could benefit from memoization to prevent unnecessary re-renders.

+const MonthColumn = memo(({ 
+  monthBlock, 
+  rootIndex, 
+  index, 
+  dayWidth 
+}: MonthColumnProps) => (
+  <div
+    key={`column-${rootIndex}-${index}`}
+    className={cn("h-full overflow-hidden outline-[0.25px] outline outline-custom-border-100", {
+      "bg-custom-primary-10": monthBlock.today,
+    })}
+    style={{ width: `${dayWidth * monthBlock.days}px` }}
+  />
+));

+const MonthHeader = memo(({ 
+  monthBlock, 
+  rootIndex, 
+  index, 
+  dayWidth 
+}: MonthHeaderProps) => (
+  // ... existing month header JSX
+));

Also applies to: 79-87

Comment on lines +265 to +279
updateBlockPosition = action((id: string, deltaLeft: number, deltaWidth: number) => {
const currBlock = this.blocksMap[id];

if (!currBlock?.position) return;

const newMarginLeft = currBlock.position.marginLeft + deltaLeft;
const newWidth = currBlock.position.width + deltaWidth;

runInAction(() => {
set(this.blocksMap, [id, "position"], {
marginLeft: newMarginLeft ?? currBlock.position?.marginLeft,
width: newWidth ?? currBlock.position?.width,
});
});
});
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

Ensure block dimensions remain valid after position updates

In the updateBlockPosition method, applying deltaWidth can result in a negative newWidth, which may cause rendering issues or errors in the Gantt chart.

Add validation to prevent newWidth from becoming negative:

        const newWidth = currBlock.position.width + deltaWidth;
+       if (newWidth < 0) {
+         // Prevent negative width by setting it to a minimum value or skipping the update
+         return;
+       }

This ensures that the block's width remains logical and the UI remains stable.

Committable suggestion was skipped due to low confidence.

// actions
fetchRelations: action,
createRelation: action,
createCurrentRelation: action,
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

Include New Actions in makeObservable

The methods createCurrentRelation and extractRelationsFromIssues should be included in the makeObservable configuration to ensure they are properly observed by MobX.

Apply this diff to include the new methods:

 makeObservable(this, {
   // observables
   relationMap: observable,
   // computed
   issueRelations: computed,
   // actions
   fetchRelations: action,
   createRelation: action,
+  createCurrentRelation: action,
   removeRelation: action,
+  extractRelationsFromIssues: action,
 });

Also applies to: 67-67

Comment on lines +141 to +158
const reverseRelatedType = REVERSE_RELATIONS[relationType];

const issuesOfRelation = get(this.relationMap, [issueId, relationType]) ?? [];

if (response && response.length > 0)
runInAction(() => {
response.forEach((issue) => {
const issuesOfRelated = get(this.relationMap, [issue.id, reverseRelatedType]);
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issue]);
this.relationMap[issueId][relationType].push(issue.id);
issuesOfRelation.push(issue.id);

if (!issuesOfRelated) {
set(this.relationMap, [issue.id, reverseRelatedType], [issueId]);
} else {
set(this.relationMap, [issue.id, reverseRelatedType], uniq([...issuesOfRelated, issueId]));
}
});
set(this.relationMap, [issueId, relationType], uniq(issuesOfRelation));
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

Handle Undefined Values Safely in createRelation

When accessing properties like issuesOfRelated, ensure that you handle cases where these may be undefined to prevent runtime errors.

Consider using optional chaining or default values to safeguard against undefined properties.

Comment on lines +263 to +306

/**
* Extract Relation from the issue Array objects and store it in this Store
* @param issues
*/
extractRelationsFromIssues = (issues: TIssue[]) => {
try {
runInAction(() => {
for (const issue of issues) {
const { issue_relation, issue_related, id: issueId } = issue;

const issueRelations: { [key in TIssueRelationTypes]?: string[] } = {};

if (issue_relation && Array.isArray(issue_relation) && issue_relation.length) {
for (const relation of issue_relation) {
const { relation_type, id } = relation;

if (!relation_type) continue;

if (issueRelations[relation_type]) issueRelations[relation_type]?.push(id);
else issueRelations[relation_type] = [id];
}
}

if (issue_related && Array.isArray(issue_related) && issue_related.length) {
for (const relation of issue_related) {
const { relation_type, id } = relation;

if (!relation_type) continue;

const reverseRelatedType = REVERSE_RELATIONS[relation_type as TIssueRelationTypes];

if (issueRelations[reverseRelatedType]) issueRelations[reverseRelatedType]?.push(id);
else issueRelations[reverseRelatedType] = [id];
}
}

set(this.relationMap, [issueId], issueRelations);
}
});
} catch (e) {
console.error("Error while extracting issue relations from issues");
}
};
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 extractRelationsFromIssues Method

Consider optimizing the loops within the extractRelationsFromIssues method. Using more efficient looping constructs or array methods can enhance performance, especially with large data sets.

For example, you might replace the for...of loops with forEach or use functional programming methods like map and reduce where appropriate.

Comment on lines +232 to +254
const relationIndex = this.relationMap[issueId]?.[relationType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[issueId][relationType].splice(relationIndex, 1);
this.relationMap[issueId]?.[relationType]?.splice(relationIndex, 1);
});

const response = await this.issueRelationService.deleteIssueRelation(workspaceSlug, projectId, issueId, {
relation_type: relationType,
related_issue,
});

// While removing one relation, reverse of the relation should also be removed
const reverseRelatedType = REVERSE_RELATIONS[relationType];
const relatedIndex = this.relationMap[related_issue]?.[reverseRelatedType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[related_issue]?.[reverseRelatedType]?.splice(relatedIndex, 1);
});

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

Correct Index Reference When Removing Reverse Relations

There's a potential mismatch in index variables when removing the reverse relation. Ensure that you're using relatedIndex when splicing the reverse relation array.

Apply this fix:

 if (relationIndex >= 0)
   runInAction(() => {
-    this.relationMap[related_issue]?.[reverseRelatedType]?.splice(relationIndex, 1);
+    this.relationMap[related_issue]?.[reverseRelatedType]?.splice(relatedIndex, 1);
   });
📝 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
const relationIndex = this.relationMap[issueId]?.[relationType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[issueId][relationType].splice(relationIndex, 1);
this.relationMap[issueId]?.[relationType]?.splice(relationIndex, 1);
});
const response = await this.issueRelationService.deleteIssueRelation(workspaceSlug, projectId, issueId, {
relation_type: relationType,
related_issue,
});
// While removing one relation, reverse of the relation should also be removed
const reverseRelatedType = REVERSE_RELATIONS[relationType];
const relatedIndex = this.relationMap[related_issue]?.[reverseRelatedType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[related_issue]?.[reverseRelatedType]?.splice(relatedIndex, 1);
});
const relationIndex = this.relationMap[issueId]?.[relationType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[issueId]?.[relationType]?.splice(relationIndex, 1);
});
const response = await this.issueRelationService.deleteIssueRelation(workspaceSlug, projectId, issueId, {
relation_type: relationType,
related_issue,
});
// While removing one relation, reverse of the relation should also be removed
const reverseRelatedType = REVERSE_RELATIONS[relationType];
const relatedIndex = this.relationMap[related_issue]?.[reverseRelatedType]?.findIndex(
(_issueId) => _issueId === related_issue
);
if (relationIndex >= 0)
runInAction(() => {
this.relationMap[related_issue]?.[reverseRelatedType]?.splice(relatedIndex, 1);
});

@NarayanBavisetti NarayanBavisetti added the 🔄migrations Contains Migration changes label Oct 28, 2024
@SatishGandham SatishGandham merged commit a88a39f into preview Oct 28, 2024
@SatishGandham SatishGandham deleted the chore/issue-listing-relations branch October 28, 2024 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚙️backend 🌟enhancement New feature or request 🌐frontend 🔄migrations Contains Migration changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants