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

Feature: PR Size to Time dependency #50

Closed
wants to merge 1 commit into from

Conversation

AlexSim93
Copy link
Owner

@AlexSim93 AlexSim93 commented Nov 8, 2024

Pull Request

Description

Added XY graph to show Time/PR size dependency

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Other (please specify)

Checklist:

  • I have read the CONTRIBUTING document.
  • My code follows the code style of this project.
  • I have added tests to cover my changes.
  • My changes generate no new warnings.
  • I have updated the documentation accordingly.

Summary by CodeRabbit

  • New Features

    • Introduced a new chart feature to visualize pull request size dependencies, providing insights into review, approval, and merge times.
    • Enhanced statistical analysis capabilities for pull requests with new metrics for time to review, approve, and merge.
  • Bug Fixes

    • Improved data structure for pull request timelines, allowing for a more detailed breakdown of metrics by size.
  • Documentation

    • Updated version number to 4.2.0 in the package.json file.
  • Chores

    • Refined existing functions to incorporate new metrics and improve overall data handling.

Copy link

coderabbitai bot commented Nov 8, 2024

Walkthrough

The changes in this pull request enhance the functionality of pull request statistics and visualizations within the project. Key modifications include the introduction of a new sizes property in the Collection type, which aggregates time metrics for pull requests. The preparePullRequestStats and preparePullRequestTimeline functions are updated to compute and return these metrics. A new function, createSizeDependencyXYChart, is added to generate visual charts based on these metrics, and the createTimelineMonthComparisonChart function is modified to integrate this new charting capability. The version number in package.json is also updated.

Changes

File Change Summary
build/index.js - Added method: createSizeDependencyXYChart(data, type, user)
- Updated method: createTimelineMonthComparisonChart to include createSizeDependencyXYChart
package.json - Version updated from "4.1.0" to "4.2.0"
src/converters/types.ts - Added property: sizes to Collection type, detailing timing metrics for pull requests
src/converters/utils/preparePullRequestStats.ts - Updated preparePullRequestStats to calculate metrics for sizes property
src/converters/utils/preparePullRequestTimeline.ts - Updated preparePullRequestTimeline to include sizes based on pull request size calculations
src/view/utils/createSizeDependencyXYChart.ts - Introduced function: createSizeDependencyXYChart for generating size dependency charts
src/view/utils/createTimelineMonthComparisonChart.ts - Updated to import and include createSizeDependencyXYChart in chart generation logic

Poem

🐇 In the garden of code, new metrics bloom,
With sizes and stats, they brighten the room.
A chart for each size, colors dance and play,
Enhancing our insights, come join the display!
From versions to functions, all changes align,
In the world of pull requests, our stats now shine! 🌼

Warning

Tool Failures:

Tool Failure Count:


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

coderabbitai bot commented Nov 8, 2024

✅ Actions performed

Full review triggered.

Copy link

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

🧹 Outside diff range and nitpick comments (10)
src/view/utils/createTimelineMonthComparisonChart.ts (1)

28-36: Consider refactoring for better maintainability and error handling.

The current implementation could be improved in several ways:

  1. Extract the chart creation logic into a separate function for better readability
  2. Add error handling for chart creation failures

Consider this refactoring:

- [
-   createTimelineMonthsXYChart(
-     data,
-     type as StatsType,
-     dates.filter((date) => date !== "total"),
-     user
-   ),
-   createSizeDependencyXYChart(data, type as StatsType, user),
- ].join("\n")
+ const createCharts = (type: StatsType, user: string) => {
+   try {
+     const timelineChart = createTimelineMonthsXYChart(
+       data,
+       type,
+       dates.filter((date) => date !== "total"),
+       user
+     );
+     const sizeChart = createSizeDependencyXYChart(data, type, user);
+     return [timelineChart, sizeChart].join("\n");
+   } catch (error) {
+     console.error(`Failed to create charts for user ${user}:`, error);
+     return "";
+   }
+ };
+ createCharts(type as StatsType, user)
src/view/utils/createSizeDependencyXYChart.ts (2)

1-5: Consider moving size categories to a configuration file.

The hardcoded sizes array could be moved to a central configuration file to improve maintainability and ensure consistency across the codebase.

-const sizes = ["xs", "s", "m", "l", "xl"];
+import { PR_SIZE_CATEGORIES } from "../../config/constants";

7-11: Improve type safety with dedicated interfaces.

The nested Record type makes the code harder to understand and maintain. Consider creating dedicated interfaces for the data structure.

interface PullRequestStats {
  total?: {
    sizes?: {
      [size: string]: {
        [type in StatsType]: {
          timeToReview: number;
          timeToApprove: number;
          timeToMerge: number;
        };
      };
    };
  };
}

interface ChartData {
  [user: string]: PullRequestStats;
}

export const createSizeDependencyXYChart = (
  data: ChartData,
  type: StatsType,
  user: string
) => {
src/converters/types.ts (2)

81-103: Consider extracting nested types for better maintainability and reuse.

The new sizes property introduces complex nested types that could be reused elsewhere. Consider extracting these into separate type definitions.

Here's a suggested refactoring:

+type TimeMetrics = {
+  timeToApprove: number;
+  timeToReview: number;
+  timeToMerge: number;
+};

+type SizeTimeStats = {
+  timeToApprove: number[];
+  timeToReview: number[];
+  timeToMerge: number[];
+  percentile: Partial<TimeMetrics>;
+  average: Partial<TimeMetrics>;
+  median: Partial<TimeMetrics>;
+};

 export type Collection = {
   // ... other properties ...
-  sizes?: Record<
-    string,
-    {
-      timeToApprove: number[];
-      timeToReview: number[];
-      timeToMerge: number[];
-      percentile: {
-        timeToApprove?: number;
-        timeToReview?: number;
-        timeToMerge?: number;
-      };
-      average: {
-        timeToApprove?: number;
-        timeToReview?: number;
-        timeToMerge?: number;
-      };
-      median: {
-        timeToApprove?: number;
-        timeToReview?: number;
-        timeToMerge?: number;
-      };
-    }
-  >;
+  sizes?: Record<string, SizeTimeStats>;

This refactoring:

  • Improves code reusability
  • Reduces duplication
  • Makes the type structure more maintainable
  • Uses Partial<T> for optional fields

81-103: Add JSDoc comments to document the new property.

The new sizes property would benefit from documentation explaining its purpose and structure.

Add JSDoc comments like this:

 export type Collection = {
   // ... other properties ...
+  /**
+   * Aggregated time metrics grouped by pull request size.
+   * Each size category (XS, S, M, L, XL) contains arrays of raw timing data
+   * and computed statistics (percentile, average, median).
+   */
   sizes?: Record<string, {
     // ... rest of the type definition
   }>;
src/converters/utils/preparePullRequestStats.ts (1)

37-60: Add helper function to reduce code duplication.

The calculation pattern for percentile, median, and average is repeated for each time metric.

Consider extracting the calculations into a helper function:

function calculateTimeMetrics(data: { 
  timeToReview?: number[], 
  timeToApprove?: number[], 
  timeToMerge?: number[] 
}) {
  return {
    percentile: {
      timeToReview: calcPercentileValue(data?.timeToReview),
      timeToApprove: calcPercentileValue(data?.timeToApprove),
      timeToMerge: calcPercentileValue(data?.timeToMerge),
    },
    median: {
      timeToReview: calcMedianValue(data?.timeToReview),
      timeToApprove: calcMedianValue(data?.timeToApprove),
      timeToMerge: calcMedianValue(data?.timeToMerge),
    },
    average: {
      timeToReview: calcAverageValue(data?.timeToReview),
      timeToApprove: calcAverageValue(data?.timeToApprove),
      timeToMerge: calcAverageValue(data?.timeToMerge),
    },
  };
}

Then use it in the main function:

sizes: Object.fromEntries(
  Object.entries(collection?.sizes || {}).map(([size, data]) => [
    size,
    {
      ...data,
      ...calculateTimeMetrics(data)
    },
  ])
),
🧰 Tools
🪛 Biome

[error] 39-39: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

build/index.js (2)

3640-3642: Ensure all imports are used or remove unused imports

The imported module _1 on line 3640 does not appear to be used in the code. Unused imports can clutter the codebase and may lead to confusion.

Consider removing the unused import:

-const _1 = __nccwpck_require__(92884);
 const utils_1 = __nccwpck_require__(41002);
 const createContributionMonthXYChart_1 = __nccwpck_require__(78462);

3649-3652: Optimize nested loops to improve performance

The current implementation uses multiple nested filter, map, and reduce operations, which may affect performance with large datasets. Refactoring the code to minimize the number of iterations can enhance efficiency.

Consider refactoring as follows:

-    .map((type) => [
-        (0, createTimelineMonthXYChart_1.createTimelineMonthsXYChart)(data, type, dates.filter((date) => date !== "total"), user),
-        (0, createSizeDependencyXYChart_1.createSizeDependencyXYChart)(data, type, user),
-    ].join("\n"))
+    .forEach((type) => {
+        charts.push(
+            (0, createTimelineMonthXYChart_1.createTimelineMonthsXYChart)(data, type, dates.filter((date) => date !== "total"), user),
+            (0, createSizeDependencyXYChart_1.createSizeDependencyXYChart)(data, type, user)
+        );
+    });

This reduces the number of intermediate arrays created and may improve performance.

src/converters/utils/preparePullRequestTimeline.ts (2)

96-115: Refactor repetitive code for updating time arrays

The code block that updates time arrays (timeToReview, timeToApprove, timeToMerge, timeToReviewRequest, timeInDraft) repeats the same pattern multiple times. This repetition can make the code harder to maintain and more prone to errors. Consider abstracting this logic into a helper function to improve readability and reduce duplication.

Apply this refactor:

// Define a helper function
function updateTimeArray(
  originalArray: number[] | undefined,
  newValue: number | null | undefined
): number[] | undefined {
  return typeof newValue === "number"
    ? [...(originalArray || []), newValue]
    : originalArray;
}

// Use the helper function in the return statement
return {
  ...collection,
  timeToReview: updateTimeArray(collection.timeToReview, timeToReview),
  timeToApprove: updateTimeArray(collection.timeToApprove, timeToApprove),
  timeToMerge: updateTimeArray(collection.timeToMerge, timeToMerge),
  timeToReviewRequest: updateTimeArray(collection.timeToReviewRequest, timeToReviewRequest),
  timeInDraft: updateTimeArray(collection.timeInDraft, timeInDraft),
  // ... rest of the code
};

118-175: Simplify nested structures to enhance readability

The nested structures within the sizes object are complex and can be difficult to read and maintain. Consider refactoring this code to reduce nesting and improve clarity. This can be achieved by extracting some logic into helper functions or restructuring the data manipulation.

Example refactor:

  1. Extract the size key calculation:
const prSize = getPullRequestSize(
  pullRequestInfo?.additions,
  pullRequestInfo?.deletions
);
  1. Extract or map the times into a separate object:
const sizeTimes = ['timeToApprove', 'timeToReview', 'timeToMerge'].reduce(
  (acc, key) => {
    const timeValue = { timeToApprove, timeToReview, timeToMerge }[key];
    if (timeValue) {
      acc[key] = [
        ...(collection?.sizes?.[prSize]?.[key] || []),
        timeValue,
      ];
    } else {
      acc[key] = collection?.sizes?.[prSize]?.[key];
    }
    return acc;
  },
  {} as Record<string, number[] | undefined>
);
  1. Update the sizes property:
sizes: {
  ...(collection.sizes || {}),
  [prSize]: {
    ...(collection.sizes?.[prSize] || {}),
    ...sizeTimes,
  },
},

This refactor reduces repetition and makes the code easier to understand.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4951c and 4d76423.

📒 Files selected for processing (7)
  • build/index.js (5 hunks)
  • package.json (1 hunks)
  • src/converters/types.ts (1 hunks)
  • src/converters/utils/preparePullRequestStats.ts (1 hunks)
  • src/converters/utils/preparePullRequestTimeline.ts (2 hunks)
  • src/view/utils/createSizeDependencyXYChart.ts (1 hunks)
  • src/view/utils/createTimelineMonthComparisonChart.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • package.json
🧰 Additional context used
📓 Path-based instructions (5)
src/converters/types.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/converters/utils/preparePullRequestStats.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/converters/utils/preparePullRequestTimeline.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/view/utils/createSizeDependencyXYChart.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/view/utils/createTimelineMonthComparisonChart.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

🪛 Biome
src/converters/utils/preparePullRequestStats.ts

[error] 39-39: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (5)
src/view/utils/createTimelineMonthComparisonChart.ts (2)

5-5: LGTM: Import statement is correctly placed and follows conventions.


28-36: Verify the performance impact of generating multiple charts.

The addition of size dependency charts doubles the chart generation workload. Let's verify the performance impact.

src/converters/utils/preparePullRequestStats.ts (1)

37-60: Verify the statistical calculations for PR sizes.

Since this is a new feature for PR size to time dependency visualization, we should verify that the statistical calculations are working as expected.

✅ Verification successful

Let me gather more information about the statistical calculation functions.


Let me check the implementation of these statistical functions to ensure they're correctly implemented.


Statistical calculations for PR sizes are correctly implemented

The statistical functions are properly implemented with appropriate safeguards:

  • calcPercentileValue: Correctly implements weighted percentile calculation using the configured percentile value
  • calcMedianValue: Properly handles both odd and even-length arrays with correct median calculation
  • calcAverageValue: Correctly implements arithmetic mean with proper null checks

The calculations are being consistently applied across all time metrics (review, approve, merge) for each PR size category.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for test files covering these statistical calculations
echo "Searching for test coverage of statistical calculations..."
rg -l "calcPercentileValue|calcMedianValue|calcAverageValue" --type ts --glob "*test*"

# Look for existing size-related calculations
echo "Checking for existing size-related calculations..."
rg "sizes?\." --type ts -A 3

Length of output: 1852


Script:

#!/bin/bash
# Search for the implementation of statistical calculation functions
echo "Searching for statistical calculation functions..."
ast-grep --pattern 'function calc$_Value($_) { $$$ }'

# Search for any test files that might have been missed
echo "Searching for test files..."
fd -e test.ts -e spec.ts

# Look for any usage of these calculations in the codebase
echo "Searching for usage of calculations..."
rg "calc(Percentile|Median|Average)Value" -A 2

Length of output: 15421


Script:

#!/bin/bash
# Get the implementation of statistical calculation functions
echo "Checking calcPercentileValue implementation..."
cat src/converters/utils/calculations/calcPercentileValue.ts

echo "Checking calcMedianValue implementation..."
cat src/converters/utils/calculations/calcMedianValue.ts

echo "Checking calcAverageValue implementation..."
cat src/converters/utils/calculations/calcAverageValue.ts

Length of output: 1618

🧰 Tools
🪛 Biome

[error] 39-39: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

build/index.js (1)

3468-3505: 🛠️ Refactor suggestion

Add input validation and error handling in createSizeDependencyXYChart

The createSizeDependencyXYChart function lacks input validation for the parameters data, type, and user. If any of these are undefined or null, it could cause runtime errors. Additionally, the function assumes that certain nested properties exist within data, which may not always be the case.

Consider adding checks at the beginning of the function:

 const createSizeDependencyXYChart = (data, type, user) => {
+    if (!data || !type || !user) {
+        throw new Error('Invalid input parameters for createSizeDependencyXYChart');
+    }
+
+    const userData = data[user]?.total?.sizes;
+    if (!userData) {
+        return ''; // Or handle accordingly if data is missing
+    }
+
     return (0, createXYChart_1.createXYChart)({
         // Existing code...
     });
 };

Likely invalid or redundant comment.

src/converters/utils/preparePullRequestTimeline.ts (1)

96-175: Ensure type safety and handle undefined values appropriately

Throughout the code, optional chaining (?.) is used extensively, which is good for handling undefined values. However, ensure that the rest of the code correctly handles potential undefined results, especially when performing operations like spreading arrays or adding to them.

Consider verifying that all properties used after optional chaining are correctly accounted for, and that any undefined values won't cause runtime errors.

Comment on lines +7 to +75
export const createSizeDependencyXYChart = (
data: Record<string, Record<string, Collection>>,
type: StatsType,
user: string
) => {
return createXYChart({
title: `Pull request's size dependency timeline(${
type === "percentile" ? parseInt(getValueAsIs("PERCENTILE")) : ""
}${type === "percentile" ? "th " : ""}${type}) ${user}`,
xAxis: sizes,
yAxis: {
min: 0,
max: Math.ceil(
Math.max(
...sizes.map((size) =>
Math.max(
...["timeToReview", "timeToApprove", "timeToMerge"].map(
(key) =>
data[user]?.total?.sizes?.[size]?.[type]?.[
key as "timeToReview" | "timeToApprove" | "timeToMerge"
] || 0
)
)
),
1
) / 60
),
title: "hours",
},
lines: [
{
color: "gold",
name: "Time\\ To\\ Review",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToReview || 0) /
60) *
100
) / 100
),
},
{
color: "chartreuse",
name: "Time\\ To\\ Approve",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToApprove || 0) /
60) *
100
) / 100
),
},
{
color: "blueviolet",
name: "Time\\ To\\ Merge",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToMerge || 0) /
60) *
100
) / 100
),
},
],
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add unit tests for the chart generation logic.

This new functionality lacks test coverage. Consider adding tests for:

  1. Chart configuration generation
  2. Time conversion calculations
  3. Edge cases with missing or invalid data
  4. Different combinations of stats types and user data

Would you like me to help create a test suite for this functionality?

Comment on lines +19 to +33
max: Math.ceil(
Math.max(
...sizes.map((size) =>
Math.max(
...["timeToReview", "timeToApprove", "timeToMerge"].map(
(key) =>
data[user]?.total?.sizes?.[size]?.[type]?.[
key as "timeToReview" | "timeToApprove" | "timeToMerge"
] || 0
)
)
),
1
) / 60
),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify max value calculation and document time conversions.

The max value calculation is complex and uses magic numbers. Consider:

  1. Extracting the time conversion logic to a utility function
  2. Using named constants for magic numbers
  3. Adding comments to explain the calculation
+const SECONDS_TO_HOURS = 60;
+const MIN_CHART_HOURS = 1;
+
+const convertSecondsToHours = (seconds: number): number => seconds / SECONDS_TO_HOURS;
+
+const getMaxTimeValue = (
+  data: ChartData,
+  user: string,
+  type: StatsType
+): number => {
+  const timeKeys = ["timeToReview", "timeToApprove", "timeToMerge"] as const;
+  const maxSeconds = Math.max(
+    ...sizes.map((size) =>
+      Math.max(...timeKeys.map((key) => data[user]?.total?.sizes?.[size]?.[type]?.[key] || 0))
+    ),
+    MIN_CHART_HOURS * SECONDS_TO_HOURS
+  );
+  return Math.ceil(convertSecondsToHours(maxSeconds));
+};

 yAxis: {
   min: 0,
-  max: Math.ceil(
-    Math.max(
-      ...sizes.map((size) =>
-        Math.max(
-          ...["timeToReview", "timeToApprove", "timeToMerge"].map(
-            (key) =>
-              data[user]?.total?.sizes?.[size]?.[type]?.[
-                key as "timeToReview" | "timeToApprove" | "timeToMerge"
-              ] || 0
-          )
-        )
-      ),
-      1
-    ) / 60
-  ),
+  max: getMaxTimeValue(data, user, type),
   title: "hours",
 },

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +36 to +73
lines: [
{
color: "gold",
name: "Time\\ To\\ Review",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToReview || 0) /
60) *
100
) / 100
),
},
{
color: "chartreuse",
name: "Time\\ To\\ Approve",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToApprove || 0) /
60) *
100
) / 100
),
},
{
color: "blueviolet",
name: "Time\\ To\\ Merge",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToMerge || 0) /
60) *
100
) / 100
),
},
],
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication and improve maintainability.

The lines configuration has several issues:

  1. Duplicate time conversion logic
  2. Hardcoded colors
  3. Potentially unnecessary space escaping in names
+const CHART_COLORS = {
+  review: "gold",
+  approve: "chartreuse",
+  merge: "blueviolet"
+} as const;
+
+interface TimeMetric {
+  key: "timeToReview" | "timeToApprove" | "timeToMerge";
+  name: string;
+  color: string;
+}
+
+const TIME_METRICS: TimeMetric[] = [
+  { key: "timeToReview", name: "Time To Review", color: CHART_COLORS.review },
+  { key: "timeToApprove", name: "Time To Approve", color: CHART_COLORS.approve },
+  { key: "timeToMerge", name: "Time To Merge", color: CHART_COLORS.merge }
+];
+
+const calculateTimeValues = (
+  data: ChartData,
+  user: string,
+  type: StatsType,
+  metricKey: TimeMetric["key"]
+): number[] => {
+  return sizes.map(
+    (size) =>
+      Math.round(
+        convertSecondsToHours(
+          data[user]?.total?.sizes?.[size]?.[type]?.[metricKey] || 0
+        ) * 100
+      ) / 100
+  );
+};

 lines: [
-  {
-    color: "gold",
-    name: "Time\\ To\\ Review",
-    values: sizes.map(
-      (size) =>
-        Math.round(
-          ((data[user]?.total?.sizes?.[size]?.[type]?.timeToReview || 0) /
-            60) *
-            100
-        ) / 100
-    ),
-  },
-  // ... similar blocks for approve and merge
+  ...TIME_METRICS.map(({ key, name, color }) => ({
+    color,
+    name: name.replace(/ /g, "\\ "),
+    values: calculateTimeValues(data, user, type, key)
+  }))
 ],
📝 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
lines: [
{
color: "gold",
name: "Time\\ To\\ Review",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToReview || 0) /
60) *
100
) / 100
),
},
{
color: "chartreuse",
name: "Time\\ To\\ Approve",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToApprove || 0) /
60) *
100
) / 100
),
},
{
color: "blueviolet",
name: "Time\\ To\\ Merge",
values: sizes.map(
(size) =>
Math.round(
((data[user]?.total?.sizes?.[size]?.[type]?.timeToMerge || 0) /
60) *
100
) / 100
),
},
],
const CHART_COLORS = {
review: "gold",
approve: "chartreuse",
merge: "blueviolet"
} as const;
interface TimeMetric {
key: "timeToReview" | "timeToApprove" | "timeToMerge";
name: string;
color: string;
}
const TIME_METRICS: TimeMetric[] = [
{ key: "timeToReview", name: "Time To Review", color: CHART_COLORS.review },
{ key: "timeToApprove", name: "Time To Approve", color: CHART_COLORS.approve },
{ key: "timeToMerge", name: "Time To Merge", color: CHART_COLORS.merge }
];
const calculateTimeValues = (
data: ChartData,
user: string,
type: StatsType,
metricKey: TimeMetric["key"]
): number[] => {
return sizes.map(
(size) =>
Math.round(
convertSecondsToHours(
data[user]?.total?.sizes?.[size]?.[type]?.[metricKey] || 0
) * 100
) / 100
);
};
lines: [
...TIME_METRICS.map(({ key, name, color }) => ({
color,
name: name.replace(/ /g, "\\ "),
values: calculateTimeValues(data, user, type, key)
}))
],

Comment on lines +81 to +103
sizes?: Record<
string,
{
timeToApprove: number[];
timeToReview: number[];
timeToMerge: number[];
percentile: {
timeToApprove?: number;
timeToReview?: number;
timeToMerge?: number;
};
average: {
timeToApprove?: number;
timeToReview?: number;
timeToMerge?: number;
};
median: {
timeToApprove?: number;
timeToReview?: number;
timeToMerge?: number;
};
}
>;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding type safety for size keys.

The Record<string, ...> type allows any string as a key. Since this property stores PR size-related data, consider restricting the keys to valid PR size values.

Here's a suggested improvement:

+type PRSizeKey = 'XS' | 'S' | 'M' | 'L' | 'XL';

 export type Collection = {
   // ... other properties ...
-  sizes?: Record<string, SizeTimeStats>;
+  sizes?: Record<PRSizeKey, SizeTimeStats>;

This change:

  • Ensures type safety by restricting keys to valid PR sizes
  • Provides better IDE autocompletion
  • Prevents accidental use of invalid size keys

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1784 to +1822
timeToReview: typeof timeToReview === "number"
? [...(collection?.timeToReview || []), timeToReview]
: collection.timeToReview,
timeToApprove: typeof timeToApprove === 'number'
timeToApprove: typeof timeToApprove === "number"
? [...(collection?.timeToApprove || []), timeToApprove]
: collection.timeToApprove,
timeToMerge: typeof timeToMerge === 'number'
timeToMerge: typeof timeToMerge === "number"
? [...(collection?.timeToMerge || []), timeToMerge]
: collection.timeToMerge,
timeToReviewRequest: typeof timeToReviewRequest === 'number'
timeToReviewRequest: typeof timeToReviewRequest === "number"
? [...(collection?.timeToReviewRequest || []), timeToReviewRequest]
: collection.timeToReviewRequest,
timeInDraft: typeof timeInDraft === 'number'
timeInDraft: typeof timeInDraft === "number"
? [...(collection?.timeInDraft || []), timeInDraft]
: collection.timeInDraft,
sizes: {
...(collection.sizes || {}),
[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]: {
...(collection.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)] || {}),
timeToApprove: timeToApprove
? [
...(collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToApprove || []),
timeToApprove,
]
: collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToApprove,
timeToReview: timeToReview
? [
...(collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToReview || []),
timeToReview,
]
: collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToReview,
timeToMerge: timeToMerge
? [
...(collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToMerge || []),
timeToMerge,
]
: collection?.sizes?.[(0, calculations_1.getPullRequestSize)(pullRequestInfo?.additions, pullRequestInfo?.deletions)]?.timeToMerge,
},
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor to reduce code duplication when updating sizes

The code that updates the sizes property for each time metric (timeToApprove, timeToReview, timeToMerge) contains repetitive patterns. This can be refactored to improve maintainability and readability by iterating over the time metrics dynamically.

Here's a refactored version:

 return {
     ...collection,
     timeToReview: typeof timeToReview === "number"
         ? [...(collection?.timeToReview || []), timeToReview]
         : collection.timeToReview,
     timeToApprove: typeof timeToApprove === "number"
         ? [...(collection?.timeToApprove || []), timeToApprove]
         : collection.timeToApprove,
     timeToMerge: typeof timeToMerge === "number"
         ? [...(collection?.timeToMerge || []), timeToMerge]
         : collection.timeToMerge,
     // Other properties...

     sizes: {
         ...(collection.sizes || {}),
         [sizeKey]: {
             ...(collection.sizes?.[sizeKey] || {}),
-            timeToApprove: timeToApprove
-                ? [
-                    ...(collection?.sizes?.[sizeKey]?.timeToApprove || []),
-                    timeToApprove,
-                ]
-                : collection?.sizes?.[sizeKey]?.timeToApprove,
-            timeToReview: timeToReview
-                ? [
-                    ...(collection?.sizes?.[sizeKey]?.timeToReview || []),
-                    timeToReview,
-                ]
-                : collection?.sizes?.[sizeKey]?.timeToReview,
-            timeToMerge: timeToMerge
-                ? [
-                    ...(collection?.sizes?.[sizeKey]?.timeToMerge || []),
-                    timeToMerge,
-                ]
-                : collection?.sizes?.[sizeKey]?.timeToMerge,
+            ...['timeToApprove', 'timeToReview', 'timeToMerge'].reduce((acc, key) => {
+                const value = eval(key);
+                return {
+                    ...acc,
+                    [key]: typeof value === "number"
+                        ? [
+                            ...(collection?.sizes?.[sizeKey]?.[key] || []),
+                            value,
+                        ]
+                        : collection?.sizes?.[sizeKey]?.[key],
+                };
+            }, {}),
         },
     },
     // Remaining properties...
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1686 to +1706
sizes: Object.entries(collection?.sizes || {}).reduce((acc, el) => ({
...acc,
[el[0]]: {
...acc[el[0]],
percentile: {
timeToReview: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToReview),
timeToApprove: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToApprove),
timeToMerge: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToMerge),
},
median: {
timeToReview: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToReview),
timeToApprove: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToApprove),
timeToMerge: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToMerge),
},
average: {
timeToReview: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToReview),
timeToApprove: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToApprove),
timeToMerge: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToMerge),
},
},
}), collection?.sizes || {}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential logical error in accumulator usage within the reducer

In the sizes reducer function, you are using acc[el[0]] to access the time metrics for calculations. However, acc represents the accumulating object being built, and at each iteration, acc[el[0]] may not have the expected data since it's being constructed. Instead, you should use el[1], which contains the actual data for the current size category from collection.sizes.

Here's a suggested fix:

 sizes: Object.entries(collection?.sizes || {}).reduce((acc, el) => ({
     ...acc,
     [el[0]]: {
         ...acc[el[0]],
         percentile: {
-            timeToReview: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToReview),
-            timeToApprove: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToApprove),
-            timeToMerge: (0, calculations_1.calcPercentileValue)(acc[el[0]]?.timeToMerge),
+            timeToReview: (0, calculations_1.calcPercentileValue)(el[1]?.timeToReview),
+            timeToApprove: (0, calculations_1.calcPercentileValue)(el[1]?.timeToApprove),
+            timeToMerge: (0, calculations_1.calcPercentileValue)(el[1]?.timeToMerge),
         },
         median: {
-            timeToReview: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToReview),
-            timeToApprove: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToApprove),
-            timeToMerge: (0, calculations_1.calcMedianValue)(acc[el[0]]?.timeToMerge),
+            timeToReview: (0, calculations_1.calcMedianValue)(el[1]?.timeToReview),
+            timeToApprove: (0, calculations_1.calcMedianValue)(el[1]?.timeToApprove),
+            timeToMerge: (0, calculations_1.calcMedianValue)(el[1]?.timeToMerge),
         },
         average: {
-            timeToReview: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToReview),
-            timeToApprove: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToApprove),
-            timeToMerge: (0, calculations_1.calcAverageValue)(acc[el[0]]?.timeToMerge),
+            timeToReview: (0, calculations_1.calcAverageValue)(el[1]?.timeToReview),
+            timeToApprove: (0, calculations_1.calcAverageValue)(el[1]?.timeToApprove),
+            timeToMerge: (0, calculations_1.calcAverageValue)(el[1]?.timeToMerge),
         },
     },
 }), collection?.sizes || {}),

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +118 to +175
[getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)]: {
...(collection.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
] || {}),
timeToApprove: timeToApprove
? [
...(collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToApprove || []),
timeToApprove,
]
: collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToApprove,
timeToReview: timeToReview
? [
...(collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToReview || []),
timeToReview,
]
: collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToReview,
timeToMerge: timeToMerge
? [
...(collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToMerge || []),
timeToMerge,
]
: collection?.sizes?.[
getPullRequestSize(
pullRequestInfo?.additions,
pullRequestInfo?.deletions
)
]?.timeToMerge,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Cache getPullRequestSize result to avoid redundant function calls

The getPullRequestSize function is called multiple times with the same arguments within this block. Caching its result in a variable will improve performance and enhance readability by eliminating redundant calls.

Apply this diff to cache the pull request size:

+      const prSize = getPullRequestSize(
+        pullRequestInfo?.additions,
+        pullRequestInfo?.deletions
+      );

       sizes: {
         ...(collection.sizes || {}),
-        [getPullRequestSize(
-          pullRequestInfo?.additions,
-          pullRequestInfo?.deletions
-        )]: {
+        [prSize]: {
           ...(collection.sizes?.[
-            getPullRequestSize(
-              pullRequestInfo?.additions,
-              pullRequestInfo?.deletions
-            )
+            prSize
           ] || {}),
           timeToApprove: timeToApprove
             ? [
               ...(collection?.sizes?.[
-                getPullRequestSize(
-                  pullRequestInfo?.additions,
-                  pullRequestInfo?.deletions
-                )
+                prSize
               ]?.timeToApprove || []),
               timeToApprove,
             ]
             : collection?.sizes?.[
-              getPullRequestSize(
-                pullRequestInfo?.additions,
-                pullRequestInfo?.deletions
-              )
+              prSize
             ]?.timeToApprove,
           timeToReview: timeToReview
             ? [
               ...(collection?.sizes?.[
-                getPullRequestSize(
-                  pullRequestInfo?.additions,
-                  pullRequestInfo?.deletions
-                )
+                prSize
               ]?.timeToReview || []),
               timeToReview,
             ]
             : collection?.sizes?.[
-              getPullRequestSize(
-                pullRequestInfo?.additions,
-                pullRequestInfo?.deletions
-              )
+              prSize
             ]?.timeToReview,
           timeToMerge: timeToMerge
             ? [
               ...(collection?.sizes?.[
-                getPullRequestSize(
-                  pullRequestInfo?.additions,
-                  pullRequestInfo?.deletions
-                )
+                prSize
               ]?.timeToMerge || []),
               timeToMerge,
             ]
             : collection?.sizes?.[
-              getPullRequestSize(
-                pullRequestInfo?.additions,
-                pullRequestInfo?.deletions
-              )
+              prSize
             ]?.timeToMerge,
         },
       },

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (7)
package.json (1)

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

Critical: Missing tests for PR size analytics confirmed

The verification reveals that there are no tests for the PR size analytics feature. Specifically:

  • No tests exist for getPullRequestSize.ts which handles size classification logic
  • No tests for createSizeDependencyXYChart.ts which manages visualization
  • Other calculation utilities like calcDifferenceInMinutes and calcNonWorkingHours have tests, showing an established testing pattern that should be followed

Required test coverage:

  • getPullRequestSize.ts: Test size classifications (xs, s, m, l, xl) based on additions and deletions
  • createSizeDependencyXYChart.ts: Test chart data preparation and edge cases
  • preparePullRequestStats.ts: Test size-related statistics aggregation
🔗 Analysis chain

Line range hint 1-47: Critical: Tests are missing for the new feature.

While the project has Jest testing infrastructure set up, the PR objectives indicate that tests haven't been added for the new PR size analytics feature. This is concerning because:

  1. The feature involves data processing and visualization logic that should be verified
  2. Missing tests could lead to undetected bugs in size calculations or chart generation
  3. Future maintenance and refactoring will be riskier without test coverage

Please add tests for:

  • Size metric calculations
  • Chart data preparation
  • Edge cases in PR size analysis

Let's verify the current test coverage for related functionality:


Let me gather more specific information about the test coverage.


Let me verify if there are any tests in the test directory that might cover these functionalities, even if not named exactly the same.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing tests related to PR analytics and identify gaps

# Look for test files related to PR size or charts
echo "Searching for relevant test files..."
fd -e test.ts -e spec.ts | grep -i "size\|chart\|stats"

# Check for size-related test cases in all test files
echo -e "\nSearching for size-related test cases..."
rg -l "describe|it|test" -t ts | xargs rg -l "size|chart" --type ts

Length of output: 817


Script:

#!/bin/bash
# Check for test files specifically for the files that handle size calculations and charts
echo "Checking for corresponding test files..."
for file in src/converters/utils/calculations/getPullRequestSize.ts \
            src/converters/utils/preparePullRequestStats.ts \
            src/view/utils/createSizeDependencyXYChart.ts; do
    base_name=$(basename "$file" .ts)
    echo "Looking for tests for $base_name:"
    fd "${base_name}.test.ts|${base_name}.spec.ts"
done

echo -e "\nChecking content of size-related implementation files:"
for file in src/converters/utils/calculations/getPullRequestSize.ts \
            src/view/utils/createSizeDependencyXYChart.ts; do
    echo -e "\nContent of $file:"
    cat "$file"
done

Length of output: 4622


Script:

#!/bin/bash
# Search for any test files that might test size or chart functionality
echo "Searching in test files for size-related tests..."
fd -e test.ts -e spec.ts | xargs rg -l "getPullRequestSize|createSizeDependencyXYChart"

echo -e "\nSearching for size calculations in test files..."
fd -e test.ts -e spec.ts | xargs rg "test\(.*size.*\)|it\(.*size.*\)|describe\(.*size.*\)" -l

echo -e "\nChecking test directory structure..."
fd . src/__tests__ -t d

echo -e "\nListing all test files..."
fd -e test.ts -e spec.ts

Length of output: 949

src/view/utils/createTimelineMonthComparisonChart.ts (1)

28-36: Consider splitting chart generation into separate functions.

The function is handling multiple responsibilities: filtering dates, validating data, and generating different types of charts. This makes it harder to maintain and test.

Consider:

  1. Creating a separate function for data validation
  2. Moving chart-specific logic into dedicated functions
  3. Adding error handling for chart generation failures

Example structure:

const validateTimelineData = (data: Collection, type: StatsType): boolean => {
  // Validation logic
};

const generateUserCharts = (
  data: Record<string, Collection>,
  type: StatsType,
  dates: string[],
  user: string
): string => {
  try {
    if (!validateTimelineData(data[user], type)) {
      return '';
    }
    const filteredDates = dates.filter(date => date !== "total");
    return `${createTimelineMonthsXYChart(data, type, filteredDates, user)}
${createSizeDependencyXYChart(data, type, user)}`;
  } catch (error) {
    console.error(`Failed to generate charts for user ${user}:`, error);
    return '';
  }
};
src/view/utils/createSizeDependencyXYChart.ts (2)

1-5: Consider making PR size categories configurable.

The sizes array is hardcoded, which might limit flexibility. Consider making it configurable through environment variables or configuration files to allow customization of size categories.

-const sizes = ["xs", "s", "m", "l", "xl"];
+import { PR_SIZE_CATEGORIES } from "../../config";
+const sizes = PR_SIZE_CATEGORIES;

13-15: Simplify chart title construction.

The title construction logic is complex and hard to read. Consider extracting it to a separate function.

+const getChartTitle = (type: StatsType, user: string): string => {
+  const percentileText = type === "percentile"
+    ? `${parseInt(getValueAsIs("PERCENTILE"))}th `
+    : "";
+  return `Pull request's size dependency timeline(${percentileText}${type}) ${user}`;
+};

 return createXYChart({
-    title: `Pull request's size dependency timeline(${
-      type === "percentile" ? parseInt(getValueAsIs("PERCENTILE")) : ""
-    }${type === "percentile" ? "th " : ""}${type}) ${user}`,
+    title: getChartTitle(type, user),
src/converters/types.ts (1)

81-103: Consider extracting nested types for better maintainability.

The nested structure for time metrics and statistics is repeated multiple times. Consider extracting these into reusable types:

+type TimeMetrics = {
+  timeToApprove?: number;
+  timeToReview?: number;
+  timeToMerge?: number;
+};

+type SizeStats = {
+  timeToApprove: number[];
+  timeToReview: number[];
+  timeToMerge: number[];
+  percentile: TimeMetrics;
+  average: TimeMetrics;
+  median: TimeMetrics;
+};

 export type Collection = {
   // ... other properties ...
-  sizes?: Record<string, {
-    timeToApprove: number[];
-    timeToReview: number[];
-    timeToMerge: number[];
-    percentile: {
-      timeToApprove?: number;
-      timeToReview?: number;
-      timeToMerge?: number;
-    };
-    average: {
-      timeToApprove?: number;
-      timeToReview?: number;
-      timeToMerge?: number;
-    };
-    median: {
-      timeToApprove?: number;
-      timeToReview?: number;
-      timeToMerge?: number;
-    };
-  }>;
+  sizes?: Record<string, SizeStats>;

This refactoring:

  • Improves code maintainability
  • Reduces duplication
  • Makes the type structure more readable
  • Makes it easier to modify the metrics structure in the future
src/converters/utils/preparePullRequestTimeline.ts (1)

116-177: Consider improving type safety and reducing code duplication.

While the logic is correct, there are two potential improvements:

  1. Add explicit TypeScript interfaces for the sizes structure to improve type safety and documentation.
  2. Extract the repeated pattern for timeToApprove, timeToReview, and timeToMerge into a helper function.

Here's a suggested approach:

// Add type definitions
interface TimeMetrics {
  timeToApprove?: number[];
  timeToReview?: number[];
  timeToMerge?: number[];
}

interface SizeMetrics {
  [size: string]: TimeMetrics;
}

// Add helper function
const updateTimeMetric = (
  collection: Collection,
  size: string,
  metricName: keyof TimeMetrics,
  newValue?: number
): TimeMetrics[keyof TimeMetrics] => {
  if (!newValue) {
    return collection?.sizes?.[size]?.[metricName];
  }
  return [
    ...(collection?.sizes?.[size]?.[metricName] || []),
    newValue,
  ];
};

Then simplify the sizes object:

sizes: {
  ...(collection.sizes || {}),
  [prSize]: {
    ...(collection.sizes?.[prSize] || {}),
    timeToApprove: updateTimeMetric(collection, prSize, 'timeToApprove', timeToApprove),
    timeToReview: updateTimeMetric(collection, prSize, 'timeToReview', timeToReview),
    timeToMerge: updateTimeMetric(collection, prSize, 'timeToMerge', timeToMerge),
  },
},
build/index.js (1)

3460-3508: Refactor to reduce code duplication in createSizeDependencyXYChart function

The lines array in the createSizeDependencyXYChart function contains repetitive code for each time metric. Refactoring this to iterate over the time metrics can improve maintainability and reduce redundancy.

Here’s a suggested refactor:

 const sizes = ["xs", "s", "m", "l", "xl"];
 const createSizeDependencyXYChart = (data, type, user) => {
     return (0, createXYChart_1.createXYChart)({
         title: `Pull request's size dependency timeline(${type === "percentile" ? parseInt((0, utils_1.getValueAsIs)("PERCENTILE")) : ""}${type === "percentile" ? "th " : ""}${type}) ${user}`,
         xAxis: sizes,
         yAxis: {
             min: 0,
             max: Math.ceil(Math.max(...sizes.map((size) => Math.max(...["timeToReview", "timeToApprove", "timeToMerge"].map((key) => data[user]?.total?.sizes?.[size]?.[type]?.[key] || 0))), 1) / 60),
             title: "hours",
         },
-        lines: [
-            {
-                color: "gold",
-                name: "Time\\ To\\ Review",
-                values: sizes.map((size) => Math.round(((data[user]?.total?.sizes?.[size]?.[type]?.timeToReview || 0) /
-                    60) *
-                    100) / 100),
-            },
-            {
-                color: "chartreuse",
-                name: "Time\\ To\\ Approve",
-                values: sizes.map((size) => Math.round(((data[user]?.total?.sizes?.[size]?.[type]?.timeToApprove || 0) /
-                    60) *
-                    100) / 100),
-            },
-            {
-                color: "blueviolet",
-                name: "Time\\ To\\ Merge",
-                values: sizes.map((size) => Math.round(((data[user]?.total?.sizes?.[size]?.[type]?.timeToMerge || 0) /
-                    60) *
-                    100) / 100),
-            },
-        ],
+        lines: ["timeToReview", "timeToApprove", "timeToMerge"].map((metric, index) => ({
+            color: ["gold", "chartreuse", "blueviolet"][index],
+            name: ["Time\\ To\\ Review", "Time\\ To\\ Approve", "Time\\ To\\ Merge"][index],
+            values: sizes.map((size) =>
+                Math.round(
+                    ((data[user]?.total?.sizes?.[size]?.[type]?.[metric] || 0) / 60) * 100
+                ) / 100
+            ),
+        })),
     });
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4951c and 4d76423.

📒 Files selected for processing (7)
  • build/index.js (5 hunks)
  • package.json (1 hunks)
  • src/converters/types.ts (1 hunks)
  • src/converters/utils/preparePullRequestStats.ts (1 hunks)
  • src/converters/utils/preparePullRequestTimeline.ts (2 hunks)
  • src/view/utils/createSizeDependencyXYChart.ts (1 hunks)
  • src/view/utils/createTimelineMonthComparisonChart.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/converters/types.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/converters/utils/preparePullRequestStats.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/converters/utils/preparePullRequestTimeline.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/view/utils/createSizeDependencyXYChart.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

src/view/utils/createTimelineMonthComparisonChart.ts (1)

Pattern **/*.ts: Check algorithms and propose the best options

🪛 Biome
src/converters/utils/preparePullRequestStats.ts

[error] 39-39: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (6)
package.json (1)

3-3: LGTM: Version bump follows semantic versioning.

The minor version increment (4.1.0 → 4.2.0) is appropriate for adding new functionality (PR size analytics feature) while maintaining backward compatibility.

src/view/utils/createTimelineMonthComparisonChart.ts (1)

5-5: LGTM!

The import statement follows the established pattern and is correctly placed with other chart-related imports.

src/converters/utils/preparePullRequestTimeline.ts (2)

4-8: LGTM! Clean import organization.

The imports are well-structured and properly grouped.


96-115: LGTM! Well-structured time metrics handling.

The time metrics are handled consistently with proper null checks and optional chaining.

build/index.js (2)

1686-1706: Potential logical error in accumulator usage within the reducer


1784-1822: Refactor to reduce code duplication when updating sizes

Comment on lines +28 to +36
[
createTimelineMonthsXYChart(
data,
type as StatsType,
dates.filter((date) => date !== "total"),
user
),
createSizeDependencyXYChart(data, type as StatsType, user),
].join("\n")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve performance and readability of chart generation.

The current implementation using array creation and joins for string concatenation can be improved.

Consider this more efficient and readable approach:

-        [
-          createTimelineMonthsXYChart(
-            data,
-            type as StatsType,
-            dates.filter((date) => date !== "total"),
-            user
-          ),
-          createSizeDependencyXYChart(data, type as StatsType, user),
-        ].join("\n")
+        `${createTimelineMonthsXYChart(
+          data,
+          type as StatsType,
+          dates.filter((date) => date !== "total"),
+          user
+        )}
+${createSizeDependencyXYChart(data, type as StatsType, user)}`
📝 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
[
createTimelineMonthsXYChart(
data,
type as StatsType,
dates.filter((date) => date !== "total"),
user
),
createSizeDependencyXYChart(data, type as StatsType, user),
].join("\n")
`${createTimelineMonthsXYChart(
data,
type as StatsType,
dates.filter((date) => date !== "total"),
user
)}
${createSizeDependencyXYChart(data, type as StatsType, user)}`

Comment on lines +74 to +75
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for chart creation.

The chart creation could fail if the data is malformed. Consider wrapping it in a try-catch block.

-  });
+  }).catch(error => {
+    console.error('Failed to create size dependency chart:', error);
+    throw new Error(`Chart creation failed: ${error.message}`);
+  });
📝 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
});
};
}).catch(error => {
console.error('Failed to create size dependency chart:', error);
throw new Error(`Chart creation failed: ${error.message}`);
});
};

Comment on lines +7 to +11
export const createSizeDependencyXYChart = (
data: Record<string, Record<string, Collection>>,
type: StatsType,
user: string
) => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input parameter validation.

The function should validate its input parameters to handle edge cases gracefully.

 export const createSizeDependencyXYChart = (
   data: Record<string, Record<string, Collection>>,
   type: StatsType,
   user: string
-) => {
+): ReturnType<typeof createXYChart> => {
+  if (!data || !user) {
+    throw new Error("Data and user parameters are required");
+  }
+  if (!data[user]?.total?.sizes) {
+    throw new Error(`No size data available for user: ${user}`);
+  }
📝 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 createSizeDependencyXYChart = (
data: Record<string, Record<string, Collection>>,
type: StatsType,
user: string
) => {
export const createSizeDependencyXYChart = (
data: Record<string, Record<string, Collection>>,
type: StatsType,
user: string
): ReturnType<typeof createXYChart> => {
if (!data || !user) {
throw new Error("Data and user parameters are required");
}
if (!data[user]?.total?.sizes) {
throw new Error(`No size data available for user: ${user}`);
}

build/index.js Show resolved Hide resolved
@AlexSim93 AlexSim93 closed this Nov 11, 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.

1 participant