Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Cycle graphs refactor #5745

Merged
merged 2 commits into from
Oct 3, 2024
Merged

Fix: Cycle graphs refactor #5745

merged 2 commits into from
Oct 3, 2024

Conversation

gakshita
Copy link
Collaborator

@gakshita gakshita commented Oct 3, 2024

Sumary
Community fixes for cycle graphs

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a version property in the cycle interface for enhanced versioning information.
    • Added new functions for improved cycle data formatting based on different versions.
  • Bug Fixes

    • Updated cycle status title from "Active" to "In progress" for better clarity.
  • Refactor

    • Removed the ActiveCycleRoot component to streamline the active cycle display.
    • Enhanced the date handling in helper functions for improved usability.
  • Chores

    • Cleaned up date-related utility functions to simplify the codebase.
    • Added a new dependency for charting capabilities.

Copy link
Contributor

coderabbitai bot commented Oct 3, 2024

Walkthrough

The pull request introduces several modifications across multiple files, primarily focusing on TypeScript type definitions and React components related to cycle management. Key changes include the addition of a version property to the ICycle interface, the removal and re-addition of the actual property in TCycleProgress, and the deletion of the ActiveCycleRoot component. Additionally, the return type of the fetchActiveCycleProgressPro method in the CycleStore class has been updated, and new functions for formatting cycle data have been introduced in the helper files.

Changes

File Change Summary
packages/types/src/cycle/cycle.d.ts - Added version: number to ICycle interface.
- Removed and re-added actual: number in TCycleProgress.
web/core/components/cycles/active-cycle/root.tsx - Removed ActiveCycleRoot component.
web/core/components/cycles/list/root.tsx - Updated CyclesList to call ActiveCycleRoot without props (workspaceSlug, projectId).
web/core/constants/cycle.ts - Updated title in CYCLE_STATUS from "Active" to "In progress" for value: "current".
web/core/store/cycle.store.ts - Updated return type of fetchActiveCycleProgressPro to Promise<void> and marked it as an action.
- Modified getActiveCycleProgress to check for cycle directly.
web/helpers/cycle.helper.ts - Updated formatActiveCycle signature.
- Added formatV1Data and formatV2Data functions for cycle data formatting.
web/helpers/date-time.helper.ts - Removed getToday, dateFormatter, and daysLeft functions.
- Updated generateDateArray to accept `string
web/package.json - Added "recharts": "^2.12.7" dependency.
- Maintained "zxcvbn": "^4.4.2" dependency.

Possibly related PRs

  • [WEB-2007] fix: cycles loading optimization #5207: The changes in this PR involve modifications to the TypeScript type definitions in cycle.d.ts, specifically adding a new interface IProjectDetails and enhancing the ICycle interface. This is related to the main PR, which also modifies the ICycle interface by adding a new property version.
  • fix: unable to added issues to a completed cycle #5348: This PR addresses the removal of a conditional check that prevented adding issues to completed cycles, which relates to the changes in the main PR that involve modifications to the cycle data structure and handling.
  • fix: cycles import issues for ee #5732: This PR resolves import issues related to cycles, which may connect to the changes in the main PR that involve modifications to the cycle data structure and its related components.

Suggested labels

🌟improvement, ⚙️backend, 🌐frontend, 🎨UI / UX

Suggested reviewers

  • SatishGandham

🐇 In the meadow where cycles spin,
A version added, let the fun begin!
With helpers bright and props anew,
Our code is fresh, like morning dew.
So hop along, let progress flow,
In this cycle, watch us grow! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 508934a and 65eaf4e.

📒 Files selected for processing (1)
  • web/package.json (1 hunks)
🔇 Additional comments (2)
web/package.json (2)

68-68: LGTM: Syntax correction

The added comma after the "zxcvbn" dependency is a necessary syntax correction to maintain valid JSON format when adding new dependencies.


69-69: Approve addition of "recharts" dependency

The addition of the "recharts" library (version ^2.12.7) aligns with the PR objective of implementing changes for cycle graphs. This library is commonly used for creating responsive charts in React applications.

Let's verify the usage of this new dependency in the codebase:


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.

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

🧹 Outside diff range and nitpick comments (4)
web/core/constants/cycle.ts (1)

Line range hint 17-26: Consider updating tab name for consistency

With the change of "Active" to "In progress" for the cycle status, consider updating the tab name in CYCLE_TABS_LIST for consistency:

export const CYCLE_TABS_LIST: {
  key: TCycleTabOptions;
  name: string;
}[] = [
  {
    key: "active",
-   name: "Active",
+   name: "In progress",
  },
  {
    key: "all",
    name: "All",
  },
];

This would ensure consistent terminology throughout the application.

web/helpers/cycle.helper.ts (3)

87-118: Ensure consistent handling of extended dates in formatV1Data.

In formatV1Data, when extending the progress data with extendedArray, ensure that dates beyond the cycle's end date are handled appropriately. Currently, only the endDate is added, which may not be necessary if it's already included in data.completion_chart.

Consider checking if endDate is already present and avoid duplication:

 const extendedArray = generateDateArray(endDate, endDate).map((d) => d.date);
+const existingDates = Object.keys(data.completion_chart);
+const datesToAdd = extendedArray.filter((date) => !existingDates.includes(date));
+const progress = [...existingDates, ...datesToAdd].map((p) => { /* ... */ });

1-6: Organize imports for better readability.

Consider grouping and ordering imports logically: external libraries first, then local modules, and helpers last. This improves code readability and maintainability.

Apply this diff to reorder the imports:

+import { isEmpty, orderBy, uniqBy, sortBy } from "lodash";
 import { startOfToday, format } from "date-fns";
-import { isEmpty, orderBy, uniqBy } from "lodash";
-import sortBy from "lodash/sortBy";
 import { ICycle, TCycleFilters } from "@plane/types";
 // helpers
 import { findTotalDaysInRange, generateDateArray, getDate } from "@/helpers/date-time.helper";
 import { satisfiesDateFilter } from "@/helpers/filter.helper";

120-159: Optimize repeated calls to format(startOfToday(), "yyyy-MM-dd").

You're calling format(startOfToday(), "yyyy-MM-dd") multiple times. Assigning it once to a variable reduces function calls and improves performance.

Assign the formatted date once:

 let today: Date | string = format(startOfToday(), "yyyy-MM-dd");
 
 const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : [];
 if (isEmpty(cycle.progress)) return extendedArray;
- today = format(startOfToday(), "yyyy-MM-dd");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ee0dce4 and 508934a.

📒 Files selected for processing (7)
  • packages/types/src/cycle/cycle.d.ts (2 hunks)
  • web/core/components/cycles/active-cycle/root.tsx (0 hunks)
  • web/core/components/cycles/list/root.tsx (1 hunks)
  • web/core/constants/cycle.ts (1 hunks)
  • web/core/store/cycle.store.ts (5 hunks)
  • web/helpers/cycle.helper.ts (2 hunks)
  • web/helpers/date-time.helper.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • web/core/components/cycles/active-cycle/root.tsx
🔇 Additional comments (9)
web/core/constants/cycle.ts (2)

63-63: LGTM: Status title updated for clarity

The change from "Active" to "In progress" for the "current" cycle status is a good improvement. It provides a clearer description of the cycle's state, which aligns better with the "day left" label.


Line range hint 1-126: Verify alignment with PR objectives

The PR is titled "Fix: community changes for cycle graphs", but the change here is more about updating terminology. While this is a valuable change, it's not clear how it relates to cycle graphs or community changes. Could you provide more context on how this change fits into the larger scope of the PR?

web/core/components/cycles/list/root.tsx (1)

31-31: Verify the impact of removing props from ActiveCycleRoot

The change looks good, assuming it's intentional. However, please verify the following:

  1. Ensure that ActiveCycleRoot can function correctly without workspaceSlug and projectId props. It should either fetch its own data or access this information through other means (e.g., context or global state).
  2. Check for any performance implications if ActiveCycleRoot is now responsible for fetching its own data.
  3. Confirm that this change is consistent across the codebase wherever ActiveCycleRoot is used.

To verify the usage of ActiveCycleRoot across the codebase, please run the following script:

If workspaceSlug and projectId are no longer needed in the CyclesList component, consider removing them from its props interface to keep the code clean:

export interface ICyclesList {
  completedCycleIds: string[];
  upcomingCycleIds?: string[] | undefined;
  cycleIds: string[];
- workspaceSlug: string;
- projectId: string;
  isArchived?: boolean;
}

Make sure to update the destructuring in the component accordingly if you make this change.

packages/types/src/cycle/cycle.d.ts (2)

106-106: LGTM. Verify the impact of adding the version property.

The addition of the version: number property to the ICycle interface is a good way to support versioning for cycle entities. This change looks good, but it's important to ensure that it's properly integrated across the codebase.

To verify the impact and usage of this new property, we can run the following script:

#!/bin/bash
# Description: Check for ICycle interface usage and version property handling

# Search for ICycle interface usage
echo "Searching for ICycle interface usage:"
rg --type typescript "ICycle" -A 5 -B 5

# Search for version property usage within objects that might be of ICycle type
echo "\nSearching for version property usage in potential ICycle objects:"
rg --type typescript "version\s*:\s*\d+" -A 2 -B 2

# Search for API endpoints or services that might need to handle the new version property
echo "\nSearching for potential API endpoints or services that might need updating:"
rg --type typescript "(get|post|put|patch|delete).*cycle" -A 5 -B 5

Please review the results of this script to ensure that:

  1. All places using the ICycle interface are aware of the new version property.
  2. Any code creating or updating ICycle objects includes the version property.
  3. API endpoints or services handling cycle data are updated to include the version property in requests/responses if necessary.

49-49: LGTM. Can you clarify the removal and re-addition process?

The actual: number property in the TCycleProgress type looks good and is consistent with other properties in this type. However, the AI summary mentions that this property was removed and then re-added. Could you provide some context on why it was initially removed and then added back? This information might be helpful for future reference and understanding the decision-making process.

To verify if this property is used elsewhere in the codebase, we can run the following script:

web/helpers/date-time.helper.ts (2)

Line range hint 1-390: Address removed functions and update documentation

The AI summary indicates that several functions (getToday, dateFormatter, and daysLeft) have been removed from this file. However, these removals are not visible in the provided code snippet. It's crucial to ensure that these changes are intentional and that their removal doesn't negatively impact the codebase.

To verify the impact of these removals and ensure that they don't break existing functionality, please run the following verification script:

#!/bin/bash
# Description: Verify the usage of removed functions in the codebase

# Search for usage of removed functions
echo "Searching for usage of removed functions:"
rg --type typescript "getToday\(|dateFormatter\(|daysLeft\(" -A 2 -B 1

# Check for any imports of these functions in other files
echo "Checking for imports of removed functions:"
rg --type typescript "import.*\{.*(?:getToday|dateFormatter|daysLeft).*\}.*from.*date-time\.helper" -A 1 -B 1

This script will help identify any remaining usage of the removed functions in the codebase, allowing you to update or remove those occurrences as needed.

Additionally, consider updating the file's documentation or comments to reflect these removals and any other significant changes. This will help maintain clear and up-to-date documentation for other developers working on this file in the future.


Line range hint 367-390: Improve date range generation logic and error handling

The generateDateArray function has been updated to accept both string and Date types, which improves its flexibility. However, there are a few issues that need to be addressed:

  1. The start date is not included in the output array due to immediate increment.
  2. The end date is included twice due to the initial increment and the loop condition.
  3. There's no error handling for invalid date inputs.

Consider applying the following changes to address these issues:

 export const generateDateArray = (startDate: string | Date, endDate: string | Date) => {
-  // Convert the start and end dates to Date objects if they aren't already
-  const start = new Date(startDate);
-  // start.setDate(start.getDate() + 1);
-  const end = new Date(endDate);
-  end.setDate(end.getDate() + 1);
+  // Convert and validate the start and end dates
+  const start = getDate(startDate);
+  const end = getDate(endDate);
+
+  if (!start || !end) {
+    throw new Error("Invalid date input");
+  }
+
+  if (start > end) {
+    throw new Error("Start date must be before or equal to end date");
+  }

   // Create an empty array to store the dates
   const dateArray = [];

   // Use a while loop to generate dates between the range
-  while (start <= end) {
+  const current = new Date(start);
+  while (current <= end) {
     // Push the current date (converted to ISO string for consistency)
     dateArray.push({
-      date: new Date(start).toISOString().split("T")[0],
+      date: current.toISOString().split("T")[0],
     });
     // Increment the date by 1 day (86400000 milliseconds)
-    start.setDate(start.getDate() + 1);
+    current.setDate(current.getDate() + 1);
   }

   return dateArray;
 };

These changes will:

  1. Include the start date in the output array.
  2. Prevent duplicate inclusion of the end date.
  3. Add error handling for invalid date inputs using the existing getDate function.
  4. Ensure that the start date is before or equal to the end date.
  5. Use a separate current date variable for iteration to avoid modifying the original start date.

To ensure that these changes don't break existing functionality, please run the following verification script:

This script will help identify any potential issues with the existing usage of generateDateArray and highlight any areas where direct new Date() calls might be affected by the changes.

web/core/store/cycle.store.ts (2)

401-407: Improved clarity in method comments.

The updates to the comments for getPlotTypeByCycleId, getEstimateTypeByCycleId, setPlotType, and setEstimateType improve clarity by specifying they are for the cycle store. This enhancement aids in code maintenance and understanding.

Also applies to: 419-427


74-74: Verify the impact of changing the return type of fetchActiveCycleProgressPro.

The return type of fetchActiveCycleProgressPro has been changed from Promise<TProgressSnapshot> | Promise<null> to Promise<void>. This change might affect how errors are handled or how data is processed in components that use this method. Please ensure that all callers of this method have been updated accordingly.

To check for potential issues, run the following script:

✅ Verification successful

Change to fetchActiveCycleProgressPro return type correctly handled across the codebase.

The return type of fetchActiveCycleProgressPro has been updated to Promise<void>, and all usages have been adjusted accordingly. No remaining references expect a return value from this method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of fetchActiveCycleProgressPro and verify they handle the new return type correctly
rg "fetchActiveCycleProgressPro" --type typescript -A 5 -B 5

Length of output: 95


Script:

#!/bin/bash
# Search for usages of fetchActiveCycleProgressPro and verify they handle the new return type correctly
rg "fetchActiveCycleProgressPro" --type ts -A 5 -B 5

Length of output: 2721


Script:

#!/bin/bash
# Search for usages of fetchActiveCycleProgressPro in .tsx files to ensure all usages are updated correctly
rg "fetchActiveCycleProgressPro" --type tsx -A 5 -B 5

Length of output: 81


Script:

#!/bin/bash
# Search for usages of fetchActiveCycleProgressPro in .tsx files to ensure all usages are updated correctly
rg "fetchActiveCycleProgressPro" -g "*.tsx" -A 5 -B 5

Length of output: 53

@@ -545,7 +540,7 @@ export class CycleStore implements ICycleStore {
* @param cycleId
* @returns
*/
fetchActiveCycleProgressPro = async (workspaceSlug: string, projectId: string, cycleId: string) => null;
fetchActiveCycleProgressPro = action(async (workspaceSlug: string, projectId: string, cycleId: string) => {});
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

Implement the fetchActiveCycleProgressPro method.

The fetchActiveCycleProgressPro method has been replaced with an empty async function. This implementation doesn't match the method's name or its expected behavior. Please implement the method to fetch the active cycle progress for pro users.

Consider implementing the method similar to the non-pro version, adjusting for any pro-specific features:

fetchActiveCycleProgressPro = action(async (workspaceSlug: string, projectId: string, cycleId: string) => {
  this.progressLoader = true;
  try {
    const progress = await this.cycleService.workspaceActiveCyclesProgressPro(workspaceSlug, projectId, cycleId);
    runInAction(() => {
      set(this.cycleMap, [cycleId], { ...this.cycleMap[cycleId], ...progress });
    });
  } catch (error) {
    console.error("Failed to fetch active cycle progress for pro users", error);
  } finally {
    runInAction(() => {
      this.progressLoader = false;
    });
  }
});

const { cycle, isBurnDown, isTypeIssue } = args;
let today = getToday();
const endDate: Date | string = new Date(cycle.end_date!);
const scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
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

Avoid using the any type for parameter p.

Using any defeats the purpose of TypeScript's static typing and can lead to runtime errors. Consider defining a specific interface or type for p to ensure type safety.

Apply this diff to specify the parameter type:

-const scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
+interface ProgressData {
+  total_issues?: number;
+  total_estimate_points?: number;
+}
+
+const scope = (p: ProgressData, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
📝 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 scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
interface ProgressData {
total_issues?: number;
total_estimate_points?: number;
}
const scope = (p: ProgressData, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);

Comment on lines +80 to +84
const ideal = (date: string, scope: number, cycle: ICycle) =>
Math.floor(
((findTotalDaysInRange(date, cycle.end_date) || 0) /
(findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) *
scope
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

Potential division by zero in the ideal function.

In the ideal function, there's a risk of dividing by zero if findTotalDaysInRange(cycle.start_date, cycle.end_date) returns 0 or undefined. This will result in a NaN value.

Apply this diff to handle the division by zero:

 const ideal = (date: string, scope: number, cycle: ICycle) =>
   Math.floor(
-    ((findTotalDaysInRange(date, cycle.end_date) || 0) /
-      (findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) *
+    ((findTotalDaysInRange(date, cycle.end_date) || 0) /
+      (findTotalDaysInRange(cycle.start_date, cycle.end_date) || 1)) *
       scope
   );

This change ensures that if the denominator is 0 or falsy, it defaults to 1, preventing division by zero.

📝 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 ideal = (date: string, scope: number, cycle: ICycle) =>
Math.floor(
((findTotalDaysInRange(date, cycle.end_date) || 0) /
(findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) *
scope
const ideal = (date: string, scope: number, cycle: ICycle) =>
Math.floor(
((findTotalDaysInRange(date, cycle.end_date) || 0) /
(findTotalDaysInRange(cycle.start_date, cycle.end_date) || 1)) *
scope
);

Comment on lines +161 to +171
export const formatActiveCycle = (args: {
cycle: ICycle;
isBurnDown?: boolean | undefined;
isTypeIssue?: boolean | undefined;
}) => {
const { cycle, isBurnDown, isTypeIssue } = args;
const endDate: Date | string = new Date(cycle.end_date!);

return cycle.version === 1
? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
: formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
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

Avoid using non-null assertions with potentially undefined values.

In formatActiveCycle, isTypeIssue and isBurnDown are optional and may be undefined, but non-null assertions (!) are used when passing them to formatV1Data and formatV2Data, which could lead to runtime errors.

Provide default values to ensure safety:

 export const formatActiveCycle = (args: {
   cycle: ICycle;
   isBurnDown?: boolean;
   isTypeIssue?: boolean;
 }) => {
   const { cycle, isBurnDown = false, isTypeIssue = false } = args;
   const endDate: Date | string = new Date(cycle.end_date!);

   return cycle.version === 1
-    ? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
-    : formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
+    ? formatV1Data(isTypeIssue, cycle, isBurnDown, endDate)
+    : formatV2Data(isTypeIssue, cycle, isBurnDown, endDate);
 };

This approach removes the need for non-null assertions and sets default values if they are not provided.

📝 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 formatActiveCycle = (args: {
cycle: ICycle;
isBurnDown?: boolean | undefined;
isTypeIssue?: boolean | undefined;
}) => {
const { cycle, isBurnDown, isTypeIssue } = args;
const endDate: Date | string = new Date(cycle.end_date!);
return cycle.version === 1
? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
: formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
export const formatActiveCycle = (args: {
cycle: ICycle;
isBurnDown?: boolean;
isTypeIssue?: boolean;
}) => {
const { cycle, isBurnDown = false, isTypeIssue = false } = args;
const endDate: Date | string = new Date(cycle.end_date!);
return cycle.version === 1
? formatV1Data(isTypeIssue, cycle, isBurnDown, endDate)
: formatV2Data(isTypeIssue, cycle, isBurnDown, endDate);
};

Comment on lines +79 to +85
const scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
const ideal = (date: string, scope: number, cycle: ICycle) =>
Math.floor(
((findTotalDaysInRange(date, cycle.end_date) || 0) /
(findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) *
scope
);
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 typing the scope and ideal functions for clarity.

Explicitly typing the return types and parameters enhances code clarity and TypeScript's type checking capabilities.

Specify types for the functions:

-const scope = (p: ProgressData, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
+const scope = (p: ProgressData, isTypeIssue: boolean): number => (isTypeIssue ? p.total_issues || 0 : p.total_estimate_points || 0);

-const ideal = (date: string, scope: number, cycle: ICycle) =>
+const ideal = (date: string, scope: number, cycle: ICycle): number =>

Committable suggestion was skipped due to low confidence.

Comment on lines +131 to +153
let progress = [...orderBy(cycle?.progress, "date"), ...extendedArray].map((p) => {
const pending = isTypeIssue
? p.total_issues - p.completed_issues - p.cancelled_issues
: p.total_estimate_points - p.completed_estimate_points - p.cancelled_estimate_points;
const completed = isTypeIssue ? p.completed_issues : p.completed_estimate_points;
const dataDate = p.progress_date ? format(new Date(p.progress_date), "yyyy-MM-dd") : p.date;

return {
date: p.date,
scope: p.date! < today ? scope(p) : p.date! < cycle.end_date! ? scopeToday : null,
date: dataDate,
scope: dataDate! < today ? scope(p, isTypeIssue) : dataDate! <= cycle.end_date! ? scopeToday : null,
completed,
backlog: isTypeIssue ? p.backlog_issues : p.backlog_estimate_points,
started: isTypeIssue ? p.started_issues : p.started_estimate_points,
unstarted: isTypeIssue ? p.unstarted_issues : p.unstarted_estimate_points,
cancelled: isTypeIssue ? p.cancelled_issues : p.cancelled_estimate_points,
pending: Math.abs(pending),
// TODO: This is a temporary logic to show the ideal line in the cycle chart
ideal: p.date! < today ? ideal(p) : p.date! < cycle.end_date! ? idealToday : null,
actual: p.date! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined,
ideal:
dataDate! < today
? ideal(dataDate, scope(p, isTypeIssue), cycle)
: dataDate! < cycle.end_date!
? idealToday
: null,
actual: dataDate! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined,
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 different types of p in the progress mapping.

In formatV2Data, the progress array combines cycle.progress objects and extendedArray dates. When mapping over this array, the code assumes that each p has certain properties, which may not be true for dates from extendedArray.

Separate the handling of progress data and extended dates:

 let progressData = orderBy(cycle.progress, "date").map((p) => {
   // existing logic for progress entries
 });

+let extendedProgress = extendedArray.map((date) => ({
+  date: format(new Date(date), "yyyy-MM-dd"),
+  scope: date <= cycle.end_date! ? scopeToday : null,
+  // other properties set to default or null
+}));

-progress = [...progressData, ...extendedArray].map((p) => { /* ... */ });
+progress = [...progressData, ...extendedProgress];
 progress = uniqBy(progress, "date");

This ensures that the properties are correctly assigned based on whether p is a progress entry or an extended date.

Committable suggestion was skipped due to low confidence.

Comment on lines +120 to +159
const formatV2Data = (isTypeIssue: boolean, cycle: ICycle, isBurnDown: boolean, endDate: Date | string) => {
if (!cycle.progress) return [];
let today: Date | string = startOfToday();

const scopeToday = scope(cycle?.progress[cycle?.progress.length - 1]);
const idealToday = ideal(cycle?.progress[cycle?.progress.length - 1]);
const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : [];
if (isEmpty(cycle.progress)) return extendedArray;
today = format(startOfToday(), "yyyy-MM-dd");
const todaysData = cycle?.progress[cycle?.progress.length - 1];
const scopeToday = scope(todaysData, isTypeIssue);
const idealToday = ideal(todaysData.date, scopeToday, cycle);

const progress = [...orderBy(cycle?.progress, "date"), ...extendedArray].map((p) => {
let progress = [...orderBy(cycle?.progress, "date"), ...extendedArray].map((p) => {
const pending = isTypeIssue
? p.total_issues - p.completed_issues - p.cancelled_issues
: p.total_estimate_points - p.completed_estimate_points - p.cancelled_estimate_points;
const completed = isTypeIssue ? p.completed_issues : p.completed_estimate_points;
const dataDate = p.progress_date ? format(new Date(p.progress_date), "yyyy-MM-dd") : p.date;

return {
date: p.date,
scope: p.date! < today ? scope(p) : p.date! < cycle.end_date! ? scopeToday : null,
date: dataDate,
scope: dataDate! < today ? scope(p, isTypeIssue) : dataDate! <= cycle.end_date! ? scopeToday : null,
completed,
backlog: isTypeIssue ? p.backlog_issues : p.backlog_estimate_points,
started: isTypeIssue ? p.started_issues : p.started_estimate_points,
unstarted: isTypeIssue ? p.unstarted_issues : p.unstarted_estimate_points,
cancelled: isTypeIssue ? p.cancelled_issues : p.cancelled_estimate_points,
pending: Math.abs(pending),
// TODO: This is a temporary logic to show the ideal line in the cycle chart
ideal: p.date! < today ? ideal(p) : p.date! < cycle.end_date! ? idealToday : null,
actual: p.date! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined,
ideal:
dataDate! < today
? ideal(dataDate, scope(p, isTypeIssue), cycle)
: dataDate! < cycle.end_date!
? idealToday
: null,
actual: dataDate! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined,
};
});
return uniqBy(progress, "date");
progress = uniqBy(progress, "date");

return progress;
};
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

Possible property access on undefined in formatV2Data.

When accessing todaysData.date and other properties of todaysData, there's a risk that todaysData could be undefined if cycle.progress is empty. This would lead to a runtime error.

Apply this diff to add a safety check:

 let today: Date | string = startOfToday();
 
 const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : [];
 if (isEmpty(cycle.progress)) return extendedArray;
 today = format(startOfToday(), "yyyy-MM-dd");
-const todaysData = cycle?.progress[cycle?.progress.length - 1];
+const todaysData = cycle.progress[cycle.progress.length - 1];
+if (!todaysData) return [];
 const scopeToday = scope(todaysData, isTypeIssue);
 const idealToday = ideal(todaysData.date, scopeToday, cycle);

This ensures that the function returns early if todaysData is undefined.

Committable suggestion was skipped due to low confidence.

@SatishGandham SatishGandham changed the title Fix: community changes for cycle graphs Fix: Cycle graphs refactor Oct 3, 2024
@pushya22 pushya22 merged commit f1a0a8d into preview Oct 3, 2024
13 of 15 checks passed
@pushya22 pushya22 deleted the fix-cycle-graph-changes branch October 3, 2024 13:55
@pushya22 pushya22 added this to the v0.24.0 milestone Oct 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants