Skip to content

[WEB-3781] Analytics page enhancements#7005

Merged
sriramveeraghanta merged 89 commits intopreviewfrom
analytics-page-enhancements
May 12, 2025
Merged

[WEB-3781] Analytics page enhancements#7005
sriramveeraghanta merged 89 commits intopreviewfrom
analytics-page-enhancements

Conversation

@JayashTripathy
Copy link
Copy Markdown
Contributor

@JayashTripathy JayashTripathy commented May 1, 2025

Description

Added Analytics V2

  • overview and work items tab
  • customizable charts and tables with global and chart specific filters

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Screen.Recording.2025-05-01.at.7.17.48.PM.mov

Test Scenarios

References

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive Analytics V2 dashboard with overview and work item insights, including customizable charts, data tables, and project analytics.
    • Added interactive chart components (bar, area, radar, scatter) with dynamic color schemes, export options, and advanced filtering.
    • Implemented new analytics filter actions, duration and project selectors, and customizable axis/grouping controls.
    • Added localized empty states and expanded translations for analytics features in multiple languages.
  • Enhancements

    • Improved analytics navigation with a new tabbed interface and persistent tab selection.
    • Enabled CSV export for analytics tables and enhanced table interactivity (sorting, filtering, pagination).
    • Updated analytics icons for clearer status indication and better scalability.
  • Bug Fixes

    • Minor formatting and style adjustments for better consistency and user experience.
  • Chores

    • Added new dependencies to support advanced table and CSV export functionality.
    • Refactored and reorganized code for improved maintainability and scalability.

NarayanBavisetti and others added 27 commits April 25, 2025 01:53
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 1, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 1, 2025

Walkthrough

This update introduces a comprehensive new analytics module (Analytics V2) across the codebase. It adds new React components for analytics dashboards, charting utilities, MobX state management, service APIs, and supporting constants and types. The changes also include extensive localization updates for analytics-related UI, new chart types and utilities, new table components, and icon updates. Several files refactor or replace legacy analytics modal usage with the new Work Items modal. The update is additive and modular, with no breaking changes to existing public APIs.

Changes

File(s) / Path(s) Change Summary
apiserver/plane/app/views/analytic/advance.py Added redundant import statements and blank lines for formatting; no logic changes.
packages/constants/src/analytics-v2/common.ts, index.ts Added new analytics constants and re-exports for configuration, fields, filter options, and axes/metrics.
packages/constants/src/chart.ts Added enums and constants for chart configuration, properties, models, and color palettes.
packages/constants/src/index.ts Added export for new analytics-v2 module.
packages/constants/src/state.ts Added "use client" directive at top; no logic changes.
packages/i18n/src/locales/*/translations.json Added new analytics and project metrics translation keys and empty state messages in all supported languages.
packages/propel/package.json Added export for table components and new dependency @tanstack/react-table.
packages/propel/src/charts/area-chart/root.tsx, bar-chart/root.tsx, line-chart/root.tsx Refactored computation of keys, labels, and colors for chart series into single memoized hooks; improved color fallback logic.
packages/propel/src/charts/components/legend.tsx Merged additional wrapper styles into legend component; minor type reformatting.
packages/propel/src/charts/components/tick.tsx Extended XAxis tick with optional label transform; added new RadarAxisTick component for radar charts.
packages/propel/src/charts/radar-chart/index.ts, scatter-chart/index.ts, table/index.ts Added index files to re-export root modules for radar chart, scatter chart, and table.
packages/propel/src/charts/radar-chart/root.tsx, scatter-chart/root.tsx Added new RadarChart and ScatterChart React components with interactive features, legends, and tooltips.
packages/propel/src/table/core.tsx Added styled, ref-forwarding table components for use in UI.
packages/types/src/analytics-v2.d.ts, charts/common.d.ts, charts/index.d.ts, enums.ts Added new types, interfaces, and enums for analytics, chart data, and update status.
packages/types/src/index.d.ts Added export for analytics-v2 types.
packages/ui/src/icons/at-risk-icon.tsx, off-track-icon.tsx, on-track-icon.tsx Refactored SVG icons for dynamic sizing, smaller viewBox, and simplified paths; color and shape adjustments.
web/ce/components/analytics-v2/tabs.ts Added new definition for analytics tabs with keys, labels, and components.
web/core/components/analytics-v2/analytics-filter-actions.tsx, analytics-section-wrapper.tsx, ... Added new React components for analytics UI: filter actions, section wrappers, wrappers, empty state, insight cards, tables, loaders, select inputs, and trend indicators.
web/core/components/analytics-v2/insight-table/data-table.tsx, loader.tsx, root.tsx Added generic DataTable component with sorting, filtering, export; TableLoader for skeleton state; InsightTable for analytics data.
web/core/components/analytics-v2/overview/active-project-item.tsx, active-projects.tsx, ... Added components for analytics overview: active projects, project insights, and overview root.
web/core/components/analytics-v2/select/analytics-params.tsx, duration.tsx, project.tsx, ... Added select input components for analytics parameters, duration, project, X/Y axes with conditional logic and i18n.
web/core/components/analytics-v2/total-insights.tsx, trend-piece.tsx Added TotalInsights grid and trend indicator components.
web/core/components/analytics-v2/work-items/created-vs-resolved.tsx, customized-insights.tsx, ... Added work items analytics components: created vs resolved chart, customized insights, priority chart, insight table, and modal dialog with header/content.
web/core/components/chart/utils.ts Added utilities for chart data normalization, color palette generation, and date formatting.
web/core/hooks/store/index.ts, use-analytics-v2.ts Added export and implementation of MobX analytics-v2 hook.
web/core/services/analytics-v2.service.ts Added new AnalyticsV2Service class for API requests to backend analytics endpoints.
web/core/store/analytics-v2.store.ts, root.store.ts Added MobX analytics-v2 store and integrated it into the root store.
web/package.json Added dependencies: @tanstack/react-table, export-to-csv.
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx, ... Replaced usage of legacy ProjectAnalyticsModal with new WorkItemsModal in relevant components.
web/app/[workspaceSlug]/(projects)/analytics-old/header.tsx, layout.tsx, page.tsx Added new workspace analytics header, layout, and page components for legacy analytics section.
web/app/[workspaceSlug]/(projects)/analytics/layout.tsx Added comment labels to import statements.
web/app/[workspaceSlug]/(projects)/analytics/page.tsx Refactored analytics page to use new Tabs component and URL sync; removed legacy tab logic.
web/core/components/empty-state/detailed-empty-state-root.tsx Removed layout="responsive" from asset image rendering.
web/core/components/issues/filters.tsx Switched modal to WorkItemsModal in header filters.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AnalyticsV2UI
    participant MobXStore
    participant AnalyticsV2Service
    participant BackendAPI

    User->>AnalyticsV2UI: Selects filters / navigates tabs
    AnalyticsV2UI->>MobXStore: Updates selected filters/state
    AnalyticsV2UI->>AnalyticsV2Service: Fetch analytics data (with filters)
    AnalyticsV2Service->>BackendAPI: GET /advance-analytics(-stats/-charts)
    BackendAPI-->>AnalyticsV2Service: Returns analytics data
    AnalyticsV2Service-->>AnalyticsV2UI: Resolves data
    AnalyticsV2UI->>AnalyticsV2UI: Renders charts, tables, insights
    AnalyticsV2UI-->>User: Displays analytics dashboard
Loading

Poem

In fields of charts where numbers grow,
The rabbits dig and insights flow.
With colors bright and tables neat,
Analytics V2 is quite a feat!
From filters, trends, and radar views,
Fresh translations bring global news.
🐇✨ Data hops ahead—let’s chew!

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

@makeplane
Copy link
Copy Markdown

makeplane bot commented May 1, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

@JayashTripathy JayashTripathy marked this pull request as ready for review May 12, 2025 09:35
Copy link
Copy Markdown
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: 8

♻️ Duplicate comments (15)
web/core/components/analytics-v2/empty-state.tsx (1)

27-37: Replace deprecated Next.js Image props

The component uses the deprecated layout="fixed" prop on both Image components. Next.js has removed this prop in favor of using style props for more flexible image handling.

Apply this diff to update both Image components:

- <Image src={assetPath} alt={title} width={100} height={100} layout="fixed" className="z-10 h-2/3 w-2/3" />
+ <Image 
+   src={assetPath} 
+   alt={title} 
+   width={100} 
+   height={100} 
+   style={{ width: '66.67%', height: '66.67%' }}
+   className="z-10" 
+ />

- <Image
-   src={backgroundReolvedPath}
-   alt={title}
-   width={100}
-   height={100}
-   layout="fixed"
-   className="h-full w-full"
- />
+ <Image
+   src={backgroundReolvedPath}
+   alt={title}
+   width={100}
+   height={100}
+   style={{ width: '100%', height: '100%' }}
+ />
apiserver/plane/app/views/analytic/advance.py (2)

190-201: ⚠️ Potential issue

Issues with filtering data and parameter validation

The get method accepts filters as a string from request.GET.get("filters", {}) but uses it as a dict for queries. This can cause runtime errors if the string isn't properly parsed. Additionally, there's no validation for x_axis and group_by parameters.

@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def get(self, request: HttpRequest, slug: str) -> Response:
    self.initialize_workspace(slug, type="chart")
-    type = request.GET.get("type", "work-items")
+    tab_type = request.GET.get("type", "work-items")
+    
+    # Parse filters from JSON string to dict
+    raw_filters = request.GET.get("filters", "{}")
+    try:
+        filters = json.loads(raw_filters)
+    except json.JSONDecodeError:
+        return Response({"error": "Invalid filters JSON"}, status=status.HTTP_400_BAD_REQUEST)

-    if type == "work-items":
+    if tab_type == "work-items":
        return Response(
            self.get_project_issues_stats(),
            status=status.HTTP_200_OK,
        )

    return Response({"message": "Invalid type"}, status=status.HTTP_400_BAD_REQUEST)

Also add the import at the top:

+import json

317-353: ⚠️ Potential issue

Filter parsing and parameter validation issues in chart endpoint

There are similar issues in this endpoint:

  1. filters is treated as a dict but comes from query params as a string
  2. x_axis and group_by need validation against allowed values
  3. The variable type shadows a built-in function
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def get(self, request: HttpRequest, slug: str) -> Response:
    self.initialize_workspace(slug, type="chart")
-    type = request.GET.get("type", "projects")
+    chart_type = request.GET.get("type", "projects")
    group_by = request.GET.get("group_by", None)
    x_axis = request.GET.get("x_axis", "PRIORITY")
    
+    # Parse filters from JSON string to dict
+    raw_filters = request.GET.get("filters", "{}")
+    try:
+        filters = json.loads(raw_filters)
+    except json.JSONDecodeError:
+        return Response({"error": "Invalid filters JSON"}, status=status.HTTP_400_BAD_REQUEST)

-    if type == "projects":
+    if chart_type == "projects":
        return Response(self.project_chart(), status=status.HTTP_200_OK)

-    elif type == "custom-work-items":
+    elif chart_type == "custom-work-items":
        # Get the base queryset
        queryset = (
            Issue.issue_objects.filter(**self.filters["base_filters"])
            .select_related("workspace", "state", "parent")
            .prefetch_related(
                "assignees", "labels", "issue_module__module", "issue_cycle__cycle"
            )
        )
web/app/[workspaceSlug]/(projects)/analytics-v2/page.tsx (1)

32-35: Add fallback title for analytics page

The page title is conditionally set based on workspace name, but there's no fallback if workspace name is not available.

-  const pageTitle = currentWorkspace?.name
-    ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
-    : undefined;
+  const pageTitle = currentWorkspace?.name
+    ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
+    : t(`workspace_analytics.page_label_default`);

Make sure to add the "page_label_default" translation key to your translations file.

web/core/components/analytics-v2/analytics-filter-actions.tsx (1)

1-3: Ensure consistent comment formatting

The comment formatting on line 3 needs to be checked for consistency with project standards.

web/core/components/analytics-v2/select/select-x-axis.tsx (1)

19-19: Add accessibility attributes

The CustomSelect component lacks accessibility attributes that would help screen reader users understand its purpose.

-    <CustomSelect value={value} label={label} onChange={onChange} maxHeight="lg">
+    <CustomSelect value={value} label={label} onChange={onChange} maxHeight="lg" aria-label="Select X-axis property">
web/core/components/analytics-v2/insight-card.tsx (1)

20-22: Replace isFinite with Number.isFinite for safer type handling

The global isFinite function attempts type coercion and could lead to unexpected behavior. Use Number.isFinite instead.

-      const isFiniteAndNotNaNOrZero = isFinite(result) && !isNaN(result) && result !== 0;
+      const isFiniteAndNotNaNOrZero = Number.isFinite(result) && !isNaN(result) && result !== 0;
🧰 Tools
🪛 Biome (1.9.4)

[error] 21-21: isFinite is unsafe. It attempts a type coercion. Use Number.isFinite instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isFinite instead.

(lint/suspicious/noGlobalIsFinite)

web/core/components/analytics-v2/work-items/created-vs-resolved.tsx (1)

27-34: Add error handling for data fetching.

The component doesn't handle potential error states from the useSWR hook. Consider adding error handling to gracefully manage API failures.

-  const { data: createdVsResolvedData, isLoading: isCreatedVsResolvedLoading } = useSWR(
+  const { data: createdVsResolvedData, isLoading: isCreatedVsResolvedLoading, error } = useSWR(
     `created-vs-resolved-${workspaceSlug}-${selectedDuration}`,
     () =>
       analyticsV2Service.getAdvanceAnalyticsCharts<IChartResponseV2>(workspaceSlug, "work-items", {
         date_filter: selectedDuration,
         ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
       })
   );

Then add conditional rendering for error state:

{error && (
  <div className="flex h-[350px] w-full items-center justify-center">
    <p className="text-custom-text-400">{t("common.error_loading_data")}</p>
  </div>
)}
web/core/components/analytics-v2/total-insights.tsx (2)

25-32: Add error handling for data fetching.

The component is missing error handling for the SWR data fetch. Consider adding error handling to provide a better user experience.

-    const { data: totalInsightsData, isLoading } = useSWR(
+    const { data: totalInsightsData, isLoading, error } = useSWR(
       `total-insights-${analyticsType}-${selectedDuration}-${selectedProjects}`,
       () =>
         analyticsV2Service.getAdvanceAnalytics<IAnalyticsResponseV2>(workspaceSlug, analyticsType, {
           date_filter: selectedDuration,
           ...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}),
         })
     );

And then handle errors in the rendering part:

if (error) {
  return (
    <div className="flex items-center justify-center p-4 text-custom-text-400">
      {t('common.error_loading_data')}
    </div>
  );
}

44-44: ⚠️ Potential issue

Add safety checks for missing analytics fields.

The component doesn't validate that the analyticsType exists in insightsFields before mapping over it, which could cause errors.

-        {insightsFields[analyticsType].map((item: string) => (
+        {insightsFields[analyticsType]?.map((item: string) => (
           <InsightCard
             key={`${analyticsType}-${item}`}
             isLoading={isLoading}
             data={totalInsightsData?.[item]}
             label={t(`workspace_analytics.${item}`)}
             versus={selectedDurationLabel}
           />
         ))}
web/core/store/analytics-v2.store.ts (2)

22-26: ⚠️ Potential issue

Type mismatch between implementation and interface

There's a type mismatch between the selectedDuration property implementation and its interface definition. The property is typed as (typeof ANALYTICS_V2_DURATION_FILTER_OPTIONS)[number]["value"] but the interface defines it as DurationType.

  //observables
  currentTab: TAnalyticsTabsV2Base = "overview";
  selectedProjects: string[] = [];
- selectedDuration: (typeof ANALYTICS_V2_DURATION_FILTER_OPTIONS)[number]["value"] = "last_30_days";
+ selectedDuration: DurationType = "last_30_days;custom;custom";

Note: Make sure the default value is a valid DurationType value from PROJECT_CREATED_AT_FILTER_OPTIONS.


28-40: 🛠️ Refactor suggestion

Missing rootStore reference

The constructor doesn't store a reference to the root store, which is needed for accessing other stores. This was previously flagged in a review comment but not addressed.

+ private rootStore: CoreRootStore;

  constructor(_rootStore: CoreRootStore) {
+   this.rootStore = _rootStore;
    makeObservable(this, {
      // observables
      currentTab: observable.ref,
      selectedDuration: observable.ref,
      selectedProjects: observable.ref,
      // computed
      selectedDurationLabel: computed,
      // actions
      updateSelectedProjects: action,
      updateSelectedDuration: action,
    });
  }
web/core/components/analytics-v2/insight-table/data-table.tsx (2)

100-104: 🛠️ Refactor suggestion

Improve search filter implementation and null safety.

The current implementation has two issues:

  1. It only filters based on the first column
  2. It uses optional chaining for array access but then assumes non-null values
-              value={table.getColumn(table.getHeaderGroups()?.[0].headers[0].id)?.getFilterValue() as string}
-              onChange={(e) =>
-                table.getColumn(table.getHeaderGroups()?.[0].headers[0].id)?.setFilterValue(e.target.value)
-              }
+              value={(table.getColumn(table.getHeaderGroups()?.[0]?.headers?.[0]?.id || "")?.getFilterValue() as string) ?? ""}
+              onChange={(e) => {
+                const columnId = table.getHeaderGroups()?.[0]?.headers?.[0]?.id;
+                if (columnId) table.getColumn(columnId)?.setFilterValue(e.target.value);
+              }}

61-65: 🛠️ Refactor suggestion

Implement pagination UI controls.

The table is configured with pagination functionality (getPaginationRowModel), but there are no UI controls for users to navigate between pages.

Add pagination controls at the bottom of the table:

      </div>
+      <div className="flex items-center justify-between space-x-2 py-4">
+        <div className="flex-1 text-sm text-custom-text-400">
+          {table.getFilteredRowModel().rows.length} row(s)
+        </div>
+        <div className="flex items-center space-x-2">
+          <button
+            className="rounded border border-custom-border-200 p-1 disabled:opacity-50"
+            onClick={() => table.previousPage()}
+            disabled={!table.getCanPreviousPage()}
+          >
+            Previous
+          </button>
+          <button
+            className="rounded border border-custom-border-200 p-1 disabled:opacity-50"
+            onClick={() => table.nextPage()}
+            disabled={!table.getCanNextPage()}
+          >
+            Next
+          </button>
+        </div>
+      </div>
    </div>
web/core/components/analytics-v2/insight-table/root.tsx (1)

69-71: 🛠️ Refactor suggestion

Improve UI feedback for empty state.

The empty state message is too plain and lacks proper styling. Consider enhancing the user experience with a more informative and styled empty state.

-      ) : (
-        <div>No data</div>
-      )}
+      ) : (
+        <div className="flex flex-col items-center justify-center p-8 text-custom-text-300">
+          <p>No data available for the selected filters</p>
+        </div>
+      )}
🧹 Nitpick comments (33)
web/core/components/analytics-v2/empty-state.tsx (1)

15-15: Fix typo in variable name

There's a typo in the variable name backgroundReolvedPath.

- const backgroundReolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-grid-background" });
+ const backgroundResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-grid-background" });
web/core/components/analytics-v2/work-items/modal/header.tsx (3)

16-19: Add translations for the header title text

The title currently displays "Analytics for {title}" as a hardcoded string. This text should use the translation framework to support internationalization.

-      <h3 className="break-words">Analytics for {title}</h3>
+      <h3 className="break-words">{t("workspace_analytics.modal.title_for", { title })}</h3>

Make sure to add the corresponding translation key to your translation files.


20-26: Add accessibility attributes to the fullscreen toggle button

The button lacks proper accessibility attributes to describe its purpose to screen reader users.

 <button
   type="button"
   className="hidden place-items-center p-1 text-custom-text-200 hover:text-custom-text-100 md:grid"
   onClick={() => setFullScreen((prevData) => !prevData)}
+  aria-label={fullScreen ? "Exit full screen" : "Enter full screen"}
 >
   {fullScreen ? <Shrink size={14} strokeWidth={2} /> : <Expand size={14} strokeWidth={2} />}
 </button>

27-33: Add accessibility attributes to the close button

The close button should have an aria-label to clearly communicate its purpose to screen reader users.

 <button
   type="button"
   className="grid place-items-center p-1 text-custom-text-200 hover:text-custom-text-100"
   onClick={handleClose}
+  aria-label="Close analytics modal"
 >
   <X size={14} strokeWidth={2} />
 </button>
web/core/components/analytics-v2/work-items/modal/index.tsx (3)

27-36: Add backdrop handling for better user experience

The modal implementation is missing a backdrop or overlay that darkens the background and clearly indicates modal context. This would improve user experience and follow common modal UI patterns.

 <Dialog as="div" className="relative z-20" onClose={handleClose}>
+  <Transition.Child
+    as={React.Fragment}
+    enter="ease-out duration-300"
+    enterFrom="opacity-0"
+    enterTo="opacity-100"
+    leave="ease-in duration-200"
+    leaveFrom="opacity-100"
+    leaveTo="opacity-0"
+  >
+    <div className="fixed inset-0 bg-custom-backdrop/50" aria-hidden="true" />
+  </Transition.Child>
   <Transition.Child
     as={React.Fragment}
     enter="transition-transform duration-300"
     enterFrom="translate-x-full"
     enterTo="translate-x-0"
     leave="transition-transform duration-200"
     leaveFrom="translate-x-0"
     leaveTo="translate-x-full"
   >

39-42: Set max-width for modal in fullscreen mode on very large screens

When in fullscreen mode on very large screens, the modal might become too wide, affecting readability and user experience.

 <div
   className={`fixed right-0 top-0 z-20 h-full bg-custom-background-100 shadow-custom-shadow-md ${
-    fullScreen ? "w-full p-2" : "w-full sm:w-full md:w-1/2"
+    fullScreen ? "w-full p-2 max-w-[1920px] mx-auto" : "w-full sm:w-full md:w-1/2"
   }`}
 >

49-55: Handle case when project name is undefined

The title is passed as an empty string when projectDetails is undefined, but an explicit handling could be more semantic.

 <WorkItemsModalHeader
   fullScreen={fullScreen}
   handleClose={handleClose}
   setFullScreen={setFullScreen}
-  title={projectDetails?.name ?? ""}
+  title={projectDetails?.name ?? "All Projects"}
 />
web/app/[workspaceSlug]/(projects)/analytics-v2/page.tsx (2)

61-95: Remove unnecessary fragment

The fragment is unnecessary since it contains only one child element.

-        <>
           {workspaceProjectIds.length > 0 || loader === "init-loader" ? (
             <div className="flex h-full overflow-hidden bg-custom-background-100 justify-between items-center  ">
               <Tabs
                 tabs={tabs}
                 storageKey={`analytics-page-${currentWorkspace?.id}`}
                 defaultTab={defaultTab}
                 size="md"
                 tabListContainerClassName="px-6 py-2 border-b border-custom-border-200 flex items-center justify-between"
                 tabListClassName="my-2 max-w-36"
                 tabPanelClassName="h-full w-full overflow-hidden overflow-y-auto"
                 storeInLocalStorage={false}
                 actions={<AnalyticsFilterActions />}
               />
             </div>
           ) : (
             <DetailedEmptyState
               title={t("workspace_analytics.empty_state.general.title")}
               description={t("workspace_analytics.empty_state.general.description")}
               assetPath={resolvedPath}
               customPrimaryButton={
                 <ComicBoxButton
                   label={t("workspace_analytics.empty_state.general.primary_button.text")}
                   title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
                   description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
                   onClick={() => {
                     setTrackElement("Analytics empty state");
                     toggleCreateProjectModal(true);
                   }}
                   disabled={!canPerformEmptyStateActions}
                 />
               }
             />
           )}
-        </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 61-95: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


63-64: Fix inconsistent spacing in className

There are double spaces in the className string which could cause confusion.

-          <div className="flex h-full overflow-hidden bg-custom-background-100 justify-between items-center  ">
+          <div className="flex h-full overflow-hidden bg-custom-background-100 justify-between items-center">
web/core/components/analytics-v2/work-items/modal/content.tsx (1)

20-30: Consider optimizing useEffect dependencies

The dependency array includes updateSelectedProjects which could potentially cause unnecessary re-renders if it changes frequently. Since this is a function from a store, consider wrapping it with useCallback in the store or using a ref if stability is an issue.

web/core/components/analytics-v2/select/select-x-axis.tsx (1)

6-14: Unused prop in type definition

The placeholder prop is defined in the Props type but doesn't appear to be used in the component implementation.

web/core/components/analytics-v2/insight-card.tsx (1)

18-25: Consider simplifying the percentage calculation logic

The percentage calculation logic includes multiple conditions that could be simplified. Also, consider handling edge cases like division by zero more explicitly.

  const percentage = useMemo(() => {
-    if (count != null && filter_count != null) {
-      const result = ((count - filter_count) / count) * 100;
-      const isFiniteAndNotNaNOrZero = isFinite(result) && !isNaN(result) && result !== 0;
-      return isFiniteAndNotNaNOrZero ? result : null;
+    if (count != null && filter_count != null && count !== 0) {
+      const result = ((count - filter_count) / count) * 100;
+      return Number.isFinite(result) && !isNaN(result) && result !== 0 ? result : null;
    }
    return null;
  }, [count, filter_count]);
🧰 Tools
🪛 Biome (1.9.4)

[error] 21-21: isFinite is unsafe. It attempts a type coercion. Use Number.isFinite instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isFinite instead.

(lint/suspicious/noGlobalIsFinite)

web/core/components/analytics-v2/work-items/created-vs-resolved.tsx (2)

35-42: Add fallback for malformed data.

The data parsing logic could be more robust by checking for valid date formats and handling potential edge cases.

 const parsedData: TChartData<string, string>[] = useMemo(() => {
   if (!createdVsResolvedData?.data) return [];
-  return createdVsResolvedData.data.map((datum) => ({
-    ...datum,
-    [datum.key]: datum.count,
-    name: renderFormattedDate(datum.key) ?? datum.key,
-  }));
+  return createdVsResolvedData.data.map((datum) => {
+    // Ensure data points have required properties
+    if (!datum || typeof datum.key === 'undefined' || typeof datum.count === 'undefined') {
+      return null;
+    }
+    return {
+      ...datum,
+      [datum.key]: datum.count,
+      name: renderFormattedDate(datum.key) ?? datum.key,
+    };
+  }).filter(Boolean);
 }, [createdVsResolvedData]);

72-115: Improve accessibility of the chart component.

The chart lacks accessibility attributes for screen readers. Consider adding ARIA attributes to make it more accessible.

 <AreaChart
   className="h-[350px] w-full"
   data={parsedData}
   areas={areas}
+  aria-label={t("workspace_analytics.created_vs_resolved")}
+  role="img"
   xAxis={{
     key: "name",
     label: "Date",
   }}
   yAxis={{
     key: "count",
     label: "Number of Issues",
     offset: -30,
     dx: -22,
   }}
   legend={{
     align: "left",
     verticalAlign: "bottom",
     layout: "horizontal",
     wrapperStyles: {
       justifyContent: "start",
       alignContent: "start",
       paddingLeft: "40px",
       paddingTop: "10px",
     },
   }}
 />
web/core/components/analytics-v2/total-insights.tsx (1)

34-43: Extract grid class calculation to a separate function.

The grid class logic is complex and could be more readable if extracted to a separate function.

+  const getGridClass = () => {
+    if (peekView) return "grid-cols-2";
+    
+    const baseClass = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:gap-10";
+    const lgClass = insightsFields[analyticsType]?.length % 5 === 0
+      ? "gap-10 lg:grid-cols-5"
+      : "gap-8 lg:grid-cols-4";
+    
+    return cn(baseClass, lgClass);
+  };
+
   return (
     <div
-      className={cn(
-        "grid grid-cols-1 gap-8 sm:grid-cols-2 md:gap-10",
-        !peekView
-          ? insightsFields[analyticsType].length % 5 === 0
-            ? "gap-10 lg:grid-cols-5"
-            : "gap-8 lg:grid-cols-4"
-          : "grid-cols-2"
-      )}
+      className={getGridClass()}
     >
web/core/components/analytics-v2/select/duration.tsx (1)

23-25: Add documentation for the component.

This component lacks documentation to explain its purpose and usage. Consider adding JSDoc comments.

+/**
+ * DurationDropdown - A dropdown component for selecting duration filters in analytics
+ * 
+ * @param {Object} props - Component props
+ * @param {string} [props.placeholder='Duration'] - Placeholder text when no value is selected
+ * @param {Function} props.onChange - Callback when selection changes
+ * @param {string|null} props.value - Currently selected duration value
+ * @param {ReactNode} [props.button] - Optional custom button
+ * @param {boolean} [props.dropdownArrow] - Whether to show dropdown arrow
+ * @param {string} [props.dropdownArrowClassName] - Custom styling for dropdown arrow
+ * @param {Function} [props.onClose] - Callback when dropdown closes
+ * @param {boolean} [props.renderByDefault] - Whether to render dropdown by default
+ * @param {number} [props.tabIndex] - Tab index for accessibility
+ */
 function DurationDropdown({ placeholder = "Duration", onChange, value }: Props) {
   useTranslation();
web/core/components/analytics-v2/work-items/utils.ts (1)

19-30: Extract color constants to avoid hardcoding.

The priority colors are hardcoded, which makes theming or dark mode support difficult.

Consider extracting these colors to a constants file:

+ // In a constants file (e.g., packages/constants/src/colors.ts)
+ export const PRIORITY_COLORS = {
+   URGENT: "#ef4444",
+   HIGH: "#f97316",
+   MEDIUM: "#eab308",
+   LOW: "#22c55e",
+   DEFAULT: "#ced4da"
+ };
+
+ // Then in this file:
+ import { PRIORITY_COLORS } from "@plane/constants";
+
  // Priority
  if (params.x_axis === ChartXAxisProperty.PRIORITY) {
    color =
      value === "urgent"
-        ? "#ef4444"
+        ? PRIORITY_COLORS.URGENT
        : value === "high"
-        ? "#f97316"
+        ? PRIORITY_COLORS.HIGH
        : value === "medium"
-        ? "#eab308"
+        ? PRIORITY_COLORS.MEDIUM
        : value === "low"
-        ? "#22c55e"
+        ? PRIORITY_COLORS.LOW
-        : "#ced4da";
+        : PRIORITY_COLORS.DEFAULT;
  }
web/core/components/analytics-v2/select/project.tsx (3)

20-37: Consider adding null checking for project details

The mapping of project IDs to options doesn't have sufficient error handling for cases where getProjectById might return undefined.

  const options = projectIds?.map((projectId) => {
    const projectDetails = getProjectById(projectId);
+   
+   // Skip if project details aren't available
+   if (!projectDetails) return null;

    return {
      value: projectDetails?.id,
      query: `${projectDetails?.name} ${projectDetails?.identifier}`,
      content: (
        <div className="flex max-w-[300px] items-center gap-2">
          {projectDetails?.logo_props ? (
            <Logo logo={projectDetails?.logo_props} size={16} />
          ) : (
            <Briefcase className="h-4 w-4" />
          )}
          <span className="flex-grow truncate">{projectDetails?.name}</span>
        </div>
      ),
    };
-  });
+  }).filter(Boolean);

46-54: Improve readability of the project label logic

The nested ternary operator for displaying the project label is a bit hard to read. Consider breaking it down for better readability.

-          {value && value.length > 3
-            ? `3+ projects`
-            : value && value.length > 0
-            ? projectIds
-                ?.filter((p) => value.includes(p))
-                .map((p) => getProjectById(p)?.name)
-                .join(", ")
-            : "All projects"}
+          {(() => {
+            if (value && value.length > 3) return "3+ projects";
+            if (value && value.length > 0) {
+              return projectIds
+                ?.filter((p) => value.includes(p))
+                .map((p) => getProjectById(p)?.name)
+                .join(", ");
+            }
+            return "All projects";
+          })()}

41-42: Add type safety to the onChange handler

The onChange handler is accepting values but doesn't properly handle the type when passing to the parent.

-      value={value ?? []}
-      onChange={(val: string[]) => onChange(val)}
+      value={value ?? []}
+      onChange={(val: string[]) => {
+        onChange(val.length > 0 ? val : null);
+      }}
web/core/store/analytics-v2.store.ts (2)

46-56: Add state restoration on error

The updateSelectedProjects method should restore the previous state in case of an error.

updateSelectedProjects = (projects: string[]) => {
  const initialState = this.selectedProjects;
  try {
    runInAction(() => {
      this.selectedProjects = projects;
    });
  } catch (error) {
    console.error("Failed to update selected project");
+   runInAction(() => {
+     this.selectedProjects = initialState;
+   });
    throw error;
  }
};

58-67: Add state restoration and initial state tracking for duration updates

Similar to the updateSelectedProjects method, the updateSelectedDuration method should track and restore the initial state on error.

updateSelectedDuration = (duration: DurationType) => {
+  const initialState = this.selectedDuration;
  try {
    runInAction(() => {
      this.selectedDuration = duration;
    });
  } catch (error) {
    console.error("Failed to update selected duration");
+   runInAction(() => {
+     this.selectedDuration = initialState;
+   });
    throw error;
  }
};
web/core/components/analytics-v2/select/analytics-params.tsx (5)

11-11: Remove unused import

The AnalyticsV2Service is imported but not used in this component.

// plane web components
- import { AnalyticsV2Service } from "@/services/analytics-v2.service";
import { SelectXAxis } from "./select-x-axis";
import { SelectYAxis } from "./select-y-axis";

15-21: Remove unused props

The component accepts setValue and workspaceSlug props but doesn't use them.

type Props = {
  control: Control<IAnalyticsV2Params, unknown>;
- setValue: UseFormSetValue<IAnalyticsV2Params>;
  params: IAnalyticsV2Params;
- workspaceSlug: string;
  classNames?: string;
};

24-24: Update prop destructuring to match recommended changes

Update the prop destructuring to match the suggested prop changes.

- const { control, params, classNames } = props;
+ const { control, params, classNames } = props;

67-69: Add label to clarify purpose of X-axis selector

Add a descriptive label to the X-axis selector to make its purpose clearer to users.

Add a label above the selector to indicate its purpose:

        <Controller
          name="x_axis"
          control={control}
          render={({ field: { value, onChange } }) => (
+           <div className="flex flex-col gap-1">
+             <span className="text-xs text-custom-text-300">X-Axis</span>
              <SelectXAxis
                value={value}
                onChange={(val) => {
                  onChange(val);
                }}
                label={
                  <div className="flex items-center gap-2">
                    <Calendar className="h-3 w-3" />
                    <span className={cn("text-custom-text-200", value && "text-custom-text-100")}>
                      {xAxisOptions.find((v) => v.value === value)?.label || "Add Property"}
                    </span>
                  </div>
                }
                options={xAxisOptions}
              />
+           </div>
          )}
        />

89-92: Consider adding clarification for "Group By" functionality

The "Group By" selector could benefit from a label or tooltip explaining its relationship with the X-axis selection.

Add a tooltip or help text explaining the relationship:

              options={groupByOptions}
              placeholder="Group By"
              allowNoValue
+             tooltip="Select a property to group your data by, different from X-axis"
            />

This assumes the SelectXAxis component supports a tooltip prop. If not, you might need to wrap it in a container with a tooltip.

packages/constants/src/chart.ts (1)

67-157: Consider adding color documentation or palettes generation logic.

The color palettes look well-designed with intentional colors for light and dark modes, but it would be helpful to have some documentation about the color selection rationale or how these palettes were generated.

 export const CHART_COLOR_PALETTES: {
   key: TChartColorScheme;
   i18n_label: string;
   light: string[];
   dark: string[];
-}[] = [
+}[] = [
+  // Modern palette: Vibrant, contemporary colors with good contrast
   {
     key: "modern",
     i18n_label: "dashboards.widget.color_palettes.modern",
     light: [
web/core/components/analytics-v2/insight-table/data-table.tsx (1)

137-137: Avoid type assertions with as any.

Using as any bypasses TypeScript's type checking and can hide potential issues. Try to use proper types or more specific assertions.

-                      : (flexRender(header.column.columnDef.header, header.getContext()) as any)}
+                      : flexRender(header.column.columnDef.header, header.getContext())}

And similarly for the cell renderer:

-                      {flexRender(cell.column.columnDef.cell, cell.getContext()) as any}
+                      {flexRender(cell.column.columnDef.cell, cell.getContext())}

Also applies to: 149-149

web/core/components/analytics-v2/insight-table/root.tsx (2)

53-53: Remove empty class name.

The empty className attribute doesn't serve any purpose and should be removed or replaced with a meaningful class.

-    <div className="">
+    <div className="w-full">

58-58: Make search placeholder more generic.

The current placeholder always shows "X Projects" regardless of the data type being displayed. This should be more context-aware.

-          searchPlaceholder={`${data.length} Projects`}
+          searchPlaceholder={`${data.length} ${props.analyticsType === "work-items" ? "Work Items" : "Items"}`}
web/core/services/analytics-v2.service.ts (2)

18-21: Reduce duplication in error handling.

All four methods use the same error handling pattern. Consider extracting this to a utility method to reduce code duplication.

+    private handleResponse<T>(promise: Promise<any>): Promise<T> {
+        return promise
+            .then((res) => res?.data)
+            .catch((err) => {
+                throw err?.response?.data;
+            });
+    }

     async getAdvanceAnalytics<T extends IAnalyticsResponseV2>(workspaceSlug: string, tab: TAnalyticsTabsV2Base, params?: Record<string, any>): Promise<T> {
-        return this.get(`/api/workspaces/${workspaceSlug}/advance-analytics/`, {
+        return this.handleResponse<T>(this.get(`/api/workspaces/${workspaceSlug}/advance-analytics/`, {
             params: {
                 tab,
                 ...params
             }
-        })
-            .then((res) => res?.data)
-            .catch((err) => {
-                throw err?.response?.data;
-            })
+        }));
     }

Apply the same pattern to the other methods.

Also applies to: 31-34, 44-47, 54-57


50-58: Add return type specificity to exportAnalytics.

The exportAnalytics method uses any as the return type. Consider using a more specific type for better type safety.

-    async exportAnalytics(workspaceSlug: string, params?: Record<string, any>): Promise<any> {
+    async exportAnalytics<T = unknown>(workspaceSlug: string, params?: Record<string, any>): Promise<T> {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4c8aeb and 1db2b52.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (75)
  • apiserver/plane/app/views/analytic/advance.py (4 hunks)
  • packages/constants/src/analytics-v2/common.ts (1 hunks)
  • packages/constants/src/chart.ts (1 hunks)
  • packages/constants/src/workspace.ts (1 hunks)
  • packages/i18n/src/locales/cs/translations.json (1 hunks)
  • packages/i18n/src/locales/de/translations.json (1 hunks)
  • packages/i18n/src/locales/en/translations.json (2 hunks)
  • packages/i18n/src/locales/es/translations.json (1 hunks)
  • packages/i18n/src/locales/fr/translations.json (1 hunks)
  • packages/i18n/src/locales/id/translations.json (1 hunks)
  • packages/i18n/src/locales/it/translations.json (1 hunks)
  • packages/i18n/src/locales/ja/translations.json (1 hunks)
  • packages/i18n/src/locales/ko/translations.json (1 hunks)
  • packages/i18n/src/locales/pl/translations.json (1 hunks)
  • packages/i18n/src/locales/pt-BR/translations.json (1 hunks)
  • packages/i18n/src/locales/ro/translations.json (1 hunks)
  • packages/i18n/src/locales/ru/translations.json (1 hunks)
  • packages/i18n/src/locales/sk/translations.json (1 hunks)
  • packages/i18n/src/locales/tr-TR/translations.json (1 hunks)
  • packages/i18n/src/locales/ua/translations.json (1 hunks)
  • packages/i18n/src/locales/vi-VN/translations.json (1 hunks)
  • packages/i18n/src/locales/zh-CN/translations.json (1 hunks)
  • packages/i18n/src/locales/zh-TW/translations.json (1 hunks)
  • packages/propel/package.json (2 hunks)
  • packages/propel/src/charts/area-chart/root.tsx (3 hunks)
  • packages/propel/src/charts/bar-chart/root.tsx (2 hunks)
  • packages/propel/src/charts/components/tick.tsx (2 hunks)
  • packages/propel/src/charts/line-chart/root.tsx (1 hunks)
  • packages/propel/src/charts/radar-chart/root.tsx (1 hunks)
  • packages/propel/src/charts/scatter-chart/root.tsx (1 hunks)
  • packages/types/src/analytics-v2.d.ts (1 hunks)
  • packages/types/src/charts/index.d.ts (8 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics-v2/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (2 hunks)
  • web/app/page.tsx (0 hunks)
  • web/ce/components/analytics-v2/tabs.ts (1 hunks)
  • web/core/components/analytics-v2/analytics-filter-actions.tsx (1 hunks)
  • web/core/components/analytics-v2/analytics-section-wrapper.tsx (1 hunks)
  • web/core/components/analytics-v2/analytics-wrapper.tsx (1 hunks)
  • web/core/components/analytics-v2/empty-state.tsx (1 hunks)
  • web/core/components/analytics-v2/index.ts (1 hunks)
  • web/core/components/analytics-v2/insight-card.tsx (1 hunks)
  • web/core/components/analytics-v2/insight-table/data-table.tsx (1 hunks)
  • web/core/components/analytics-v2/insight-table/index.ts (1 hunks)
  • web/core/components/analytics-v2/insight-table/loader.tsx (1 hunks)
  • web/core/components/analytics-v2/insight-table/root.tsx (1 hunks)
  • web/core/components/analytics-v2/loaders.tsx (1 hunks)
  • web/core/components/analytics-v2/overview/active-project-item.tsx (1 hunks)
  • web/core/components/analytics-v2/overview/active-projects.tsx (1 hunks)
  • web/core/components/analytics-v2/overview/index.ts (1 hunks)
  • web/core/components/analytics-v2/overview/project-insights.tsx (1 hunks)
  • web/core/components/analytics-v2/overview/root.tsx (1 hunks)
  • web/core/components/analytics-v2/select/analytics-params.tsx (1 hunks)
  • web/core/components/analytics-v2/select/duration.tsx (1 hunks)
  • web/core/components/analytics-v2/select/project.tsx (1 hunks)
  • web/core/components/analytics-v2/select/select-x-axis.tsx (1 hunks)
  • web/core/components/analytics-v2/select/select-y-axis.tsx (1 hunks)
  • web/core/components/analytics-v2/total-insights.tsx (1 hunks)
  • web/core/components/analytics-v2/trend-piece.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/created-vs-resolved.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/customized-insights.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/index.ts (1 hunks)
  • web/core/components/analytics-v2/work-items/modal/content.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/modal/header.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/modal/index.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/priority-chart.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/root.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/utils.ts (1 hunks)
  • web/core/components/analytics-v2/work-items/workitems-insight-table.tsx (1 hunks)
  • web/core/components/chart/utils.ts (1 hunks)
  • web/core/components/issues/filters.tsx (2 hunks)
  • web/core/components/workspace/sidebar/workspace-menu.tsx (1 hunks)
  • web/core/services/analytics-v2.service.ts (1 hunks)
  • web/core/store/analytics-v2.store.ts (1 hunks)
  • web/package.json (2 hunks)
💤 Files with no reviewable changes (1)
  • web/app/page.tsx
✅ Files skipped from review due to trivial changes (21)
  • web/core/components/analytics-v2/work-items/index.ts
  • packages/propel/src/charts/line-chart/root.tsx
  • packages/constants/src/workspace.ts
  • web/core/components/workspace/sidebar/workspace-menu.tsx
  • web/core/components/analytics-v2/loaders.tsx
  • packages/i18n/src/locales/vi-VN/translations.json
  • packages/i18n/src/locales/ja/translations.json
  • packages/i18n/src/locales/zh-TW/translations.json
  • packages/i18n/src/locales/pl/translations.json
  • packages/i18n/src/locales/zh-CN/translations.json
  • packages/i18n/src/locales/sk/translations.json
  • packages/i18n/src/locales/pt-BR/translations.json
  • packages/i18n/src/locales/ru/translations.json
  • packages/i18n/src/locales/id/translations.json
  • packages/i18n/src/locales/ua/translations.json
  • packages/i18n/src/locales/cs/translations.json
  • packages/i18n/src/locales/fr/translations.json
  • packages/i18n/src/locales/ko/translations.json
  • packages/i18n/src/locales/it/translations.json
  • packages/i18n/src/locales/es/translations.json
  • packages/i18n/src/locales/de/translations.json
🚧 Files skipped from review as they are similar to previous changes (29)
  • web/package.json
  • web/core/components/analytics-v2/insight-table/index.ts
  • web/core/components/analytics-v2/index.ts
  • web/core/components/analytics-v2/overview/root.tsx
  • web/core/components/analytics-v2/overview/index.ts
  • web/core/components/analytics-v2/analytics-wrapper.tsx
  • packages/propel/src/charts/bar-chart/root.tsx
  • packages/propel/package.json
  • web/ce/components/analytics-v2/tabs.ts
  • packages/propel/src/charts/area-chart/root.tsx
  • web/core/components/analytics-v2/work-items/root.tsx
  • web/core/components/analytics-v2/insight-table/loader.tsx
  • web/core/components/analytics-v2/trend-piece.tsx
  • web/core/components/analytics-v2/work-items/workitems-insight-table.tsx
  • web/core/components/analytics-v2/overview/project-insights.tsx
  • web/core/components/analytics-v2/overview/active-projects.tsx
  • packages/propel/src/charts/radar-chart/root.tsx
  • packages/constants/src/analytics-v2/common.ts
  • packages/propel/src/charts/components/tick.tsx
  • web/core/components/chart/utils.ts
  • web/core/components/analytics-v2/analytics-section-wrapper.tsx
  • web/core/components/analytics-v2/overview/active-project-item.tsx
  • web/core/components/analytics-v2/work-items/customized-insights.tsx
  • packages/types/src/analytics-v2.d.ts
  • packages/types/src/charts/index.d.ts
  • packages/propel/src/charts/scatter-chart/root.tsx
  • packages/i18n/src/locales/en/translations.json
  • web/core/components/analytics-v2/work-items/priority-chart.tsx
  • web/core/components/analytics-v2/select/select-y-axis.tsx
🧰 Additional context used
🧬 Code Graph Analysis (11)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (1)
web/core/components/analytics-v2/work-items/modal/index.tsx (1)
  • WorkItemsModal (16-64)
web/core/components/issues/filters.tsx (1)
web/core/components/analytics-v2/work-items/modal/index.tsx (1)
  • WorkItemsModal (16-64)
web/core/components/analytics-v2/analytics-filter-actions.tsx (3)
web/core/hooks/store/use-analytics-v2.ts (1)
  • useAnalyticsV2 (7-11)
web/core/components/analytics-v2/select/project.tsx (1)
  • ProjectSelect (16-60)
web/core/store/project/project.store.ts (1)
  • workspaceProjectIds (166-174)
web/core/components/analytics-v2/empty-state.tsx (1)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
web/core/components/analytics-v2/work-items/utils.ts (2)
web/core/store/state.store.ts (1)
  • workspaceStates (86-90)
packages/types/src/state.d.ts (1)
  • IState (5-16)
web/core/store/analytics-v2.store.ts (4)
packages/constants/src/filter.ts (1)
  • PROJECT_CREATED_AT_FILTER_OPTIONS (40-57)
packages/types/src/analytics-v2.d.ts (1)
  • TAnalyticsTabsV2Base (4-4)
packages/constants/src/analytics-v2/common.ts (1)
  • ANALYTICS_V2_DURATION_FILTER_OPTIONS (24-41)
web/core/store/root.store.ts (1)
  • CoreRootStore (39-134)
web/core/components/analytics-v2/total-insights.tsx (6)
web/core/services/analytics-v2.service.ts (1)
  • AnalyticsV2Service (6-59)
packages/types/src/analytics-v2.d.ts (2)
  • TAnalyticsTabsV2Base (4-4)
  • IAnalyticsResponseV2 (10-12)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/hooks/store/use-analytics-v2.ts (1)
  • useAnalyticsV2 (7-11)
packages/constants/src/analytics-v2/common.ts (1)
  • insightsFields (4-22)
web/core/store/analytics-v2.store.ts (1)
  • selectedDurationLabel (42-44)
packages/constants/src/chart.ts (1)
packages/types/src/charts/common.d.ts (1)
  • TChartColorScheme (3-3)
web/core/services/analytics-v2.service.ts (2)
packages/types/src/analytics-v2.d.ts (3)
  • IAnalyticsResponseV2 (10-12)
  • TAnalyticsTabsV2Base (4-4)
  • TAnalyticsGraphsV2Base (5-5)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
apiserver/plane/app/views/analytic/advance.py (3)
apiserver/plane/utils/build_chart.py (1)
  • build_analytics_chart (157-205)
apiserver/plane/bgtasks/analytic_plot_export.py (1)
  • export_analytics_to_csv_email (470-495)
apiserver/plane/utils/date_utils.py (1)
  • get_analytics_filters (135-197)
web/core/components/analytics-v2/insight-card.tsx (1)
packages/types/src/analytics-v2.d.ts (1)
  • IAnalyticsResponseFieldsV2 (14-17)
🪛 Ruff (0.8.2)
apiserver/plane/app/views/analytic/advance.py

21-21: Redefinition of unused Q from line 5

Remove definition: Q

(F811)


22-22: Redefinition of unused Count from line 5

Remove definition: Count

(F811)


25-25: Redefinition of unused timedelta from line 4

Remove definition: timedelta

(F811)

🪛 Biome (1.9.4)
web/app/[workspaceSlug]/(projects)/analytics-v2/page.tsx

[error] 61-95: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

web/core/components/analytics-v2/insight-card.tsx

[error] 21-21: isFinite is unsafe. It attempts a type coercion. Use Number.isFinite instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isFinite instead.

(lint/suspicious/noGlobalIsFinite)

🔇 Additional comments (24)
web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx (1)

23-23: LGTM: Updated to use the new analytics V2 component

The change from the old analytics modal to the new WorkItemsModal component is properly implemented, maintaining the same props interface while leveraging the enhanced features of the analytics-v2 system.

Also applies to: 108-112

web/core/components/issues/filters.tsx (1)

21-21: LGTM: Updated to use the new analytics V2 component

The change from the old analytics modal to the new WorkItemsModal component is properly implemented, maintaining the same props interface while leveraging the enhanced features of the analytics-v2 system.

Also applies to: 101-105

packages/i18n/src/locales/ro/translations.json (1)

1315-1345: New translation keys look good

The new analytics-related translation keys follow the existing naming conventions and are properly structured.

web/core/components/analytics-v2/analytics-filter-actions.tsx (2)

10-32: The component structure looks good

The component is properly wrapped with observer for reactive rendering based on observable state changes. It correctly retrieves state and update functions from the appropriate hooks and handles edge cases (null/undefined values).


17-19: Good null handling with the spread operator

Good pattern using the null coalescing operator to ensure you never pass undefined to the store update function.

web/core/components/analytics-v2/work-items/modal/content.tsx (2)

39-46: Verify Tab.Group usage without Tab.List/Tab.Panels

You're using Tab.Group but there are no visible Tab.List or Tab.Panels components within it. If this is intentional (just using the Tab context without visible tabs), consider adding a comment explaining why, or check if the Tab.Group wrapper is necessary at all.


31-36: Loading state implementation looks good

Good implementation of the loading state with centered spinner while project configuration is in progress.

web/core/components/analytics-v2/select/select-x-axis.tsx (1)

21-28: The option filtering logic is well implemented

The implementation of option filtering based on hiddenOptions is clean and efficient. The component also properly handles the "No value" case when allowNoValue is true.

web/core/components/analytics-v2/insight-card.tsx (1)

30-39: Good conditional rendering of trend information

The component effectively handles conditional rendering of trend information when percentage data is available, and provides appropriate loading states.

packages/i18n/src/locales/tr-TR/translations.json (1)

1318-1348: Successfully added Turkish translations for analytics V2

The additions to the Turkish translations file properly incorporate all the necessary strings for the new analytics-v2 feature, including empty states, insights, and metrics.

packages/constants/src/chart.ts (6)

1-1: Good type enforcement with imported type.

The import of TChartColorScheme from @plane/types ensures that the chart color palettes defined later in the file are properly typed.


7-24: Well-structured X-axis properties enum.

This enum provides a comprehensive set of properties for chart X-axis configuration, enabling powerful and flexible chart generation capabilities. The naming is clear and follows consistent patterns.


26-35: Comprehensive Y-axis metrics enum.

The ChartYAxisMetric enum provides a thorough set of metrics for visualizing work item data, covering counts in various states (pending, in progress, completed) and with different filters.


38-43: Clear date grouping options.

The enum provides standard date grouping intervals which will be useful for time-series analytics.


45-48: Useful property classification arrays.

These arrays categorize properties with specific behaviors (requiring capitalization or date handling), making it easier to implement consistent formatting and processing across the analytics UI.

Also applies to: 50-55


58-65: Comprehensive chart model types enum.

This enum covers a wide range of chart visualization types, providing good flexibility for different analytics scenarios.

web/core/components/analytics-v2/insight-table/data-table.tsx (2)

28-33: Well-typed component interface with generic types.

The DataTableProps interface with generic types provides strong typing for the component, making it reusable across different data structures while maintaining type safety.


45-65: Complete table configuration with advanced features.

The table configuration is comprehensive, including sorting, filtering, pagination, row selection, and faceted values, which provides a powerful foundation for interactive data tables.

web/core/components/analytics-v2/insight-table/root.tsx (3)

10-16: Well-typed component with generics for type safety.

The generic constraint T extends Exclude<TAnalyticsTabsV2Base, "overview"> ensures proper typing and prevents using the component with incompatible analytics types.


29-34: Configurable CSV export setup.

Good to see a flexible CSV export configuration that can be customized if needed in the future.


36-50: Well-structured CSV export function.

The function handles data transformation appropriately, including property exclusion and column label mapping.

web/core/services/analytics-v2.service.ts (3)

6-9: Well-structured service class extending base service.

The service follows the proper inheritance pattern, extending the base APIService and configuring it with the API base URL.


11-22: Consistent API methods with proper error handling.

The GET methods follow a consistent pattern with proper parameter handling and error management. The generic type parameters allow for flexible typing of the response data.

Also applies to: 24-35, 37-48


24-35:

Details

❓ Verification inconclusive

Verify API endpoint consistency with backend.

The API endpoint for getAdvanceAnalyticsStats uses type as query parameter name while getAdvanceAnalytics uses tab. Verify this is consistent with what the backend expects.


🏁 Script executed:

#!/bin/bash
# Search for the api routes definition in the backend to verify parameter naming
find . -type f -path "*apiserver*" -name "*.py" | xargs grep -l "advance-analytics"

Length of output: 122


🏁 Script executed:

#!/bin/bash
# Show the URL pattern for advance-analytics-stats
grep -R "advance-analytics-stats" -n apiserver/plane/app/urls/analytic.py -A5 -B5

# Locate the corresponding view for advance analytics stats
grep -R "advance_analytics_stats" -n apiserver/plane/app/views -A5 -B5

Length of output: 650


🏁 Script executed:

#!/bin/bash
# Find the view classes to inspect their parameter handling
grep -R "class AdvanceAnalyticsStatsEndpoint" -n apiserver/plane/app/views -A5 -B1
grep -R "class AdvanceAnalyticsEndpoint" -n apiserver/plane/app/views -A5 -B1

# Search how each endpoint reads query parameters
grep -R "query_params.get" -n apiserver/plane/app/views -A2 -B2

Length of output: 5581


#!/bin/bash
# Show the base view implementation to see how query params are mapped
grep -R "class AdvanceAnalyticsBaseView" -n apiserver/plane/app/views/analytic/advance.py -A50

# Check specifically which query params AdvanceAnalyticsBaseView reads
grep -R "query_params.get" -n apiserver/plane/app/views/analytic/advance.py -A3 -B3

Confirm API query parameter for advance analytics stats

The frontend getAdvanceAnalyticsStats call sends a type parameter while getAdvanceAnalytics uses tab. I wasn’t able to find where the stats endpoint reads a type query param in the backend:

  • Frontend: web/core/services/analytics-v2.service.ts (lines 24–35) uses type: tab
  • Backend: apiserver/plane/app/views/analytic/advance.pyclass AdvanceAnalyticsStatsEndpoint inherits from AdvanceAnalyticsBaseView

Please verify that the backend expects type here (or update the frontend to use tab) so both endpoints remain consistent.

@JayashTripathy JayashTripathy force-pushed the analytics-page-enhancements branch from 7fe2314 to 4de46e8 Compare May 12, 2025 09:59
Copy link
Copy Markdown
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: 2

♻️ Duplicate comments (2)
web/core/components/analytics-v2/work-items/utils.ts (2)

11-16: Improve type safety with null checks.

The function doesn't handle null or undefined values properly, which could lead to runtime errors when trying to access properties of undefined values.

 export const generateBarColor = (
-  value: string,
+  value: string | null | undefined,
   params: ParamsProps,
   baseColors: string[],
   workspaceStates?: IState[]
 ): string => {
+  if (!value) return baseColors[0]; // Return default color for null/undefined values
+  
   let color = baseColors[0];

46-51: Fix incorrect label color retrieval.

The code incorrectly tries to find label colors in the workspaceStates array, which is for states, not labels. Labels should have their own color mapping or use a different approach.

The proper solution would be to pass workspace labels as a separate parameter to the function or implement a consistent hashing approach similar to what you're doing for states.

The current implementation will not work correctly for labels since workspaceStates contains state objects, not label objects.

🧹 Nitpick comments (3)
web/core/services/analytics-v2.service.ts (2)

27-42: Add type constraint to getAdvanceAnalyticsStats method.

The getAdvanceAnalytics method constrains its generic type with T extends IAnalyticsResponseV2, but this constraint is missing from getAdvanceAnalyticsStats. Consider adding the same constraint for consistency and type safety.

-  async getAdvanceAnalyticsStats<T>(
+  async getAdvanceAnalyticsStats<T extends IAnalyticsResponseV2>(
     workspaceSlug: string,
     tab: Exclude<TAnalyticsTabsV2Base, "overview">,
     params?: Record<string, any>
   ): Promise<T> {

44-59: Add type constraint to getAdvanceAnalyticsCharts method.

The getAdvanceAnalytics method constrains its generic type with T extends IAnalyticsResponseV2, but this constraint is missing from getAdvanceAnalyticsCharts. Consider adding the same constraint for consistency and type safety.

-  async getAdvanceAnalyticsCharts<T>(
+  async getAdvanceAnalyticsCharts<T extends IAnalyticsResponseV2>(
     workspaceSlug: string,
     tab: TAnalyticsGraphsV2Base,
     params?: Record<string, any>
   ): Promise<T> {
web/core/components/analytics-v2/work-items/utils.ts (1)

19-30: Extract priority colors to constants.

Hardcoded color values make the code less maintainable and harder to update. Consider extracting these to named constants or a configuration object.

+// Priority level color mapping
+const PRIORITY_COLORS = {
+  urgent: "#ef4444",
+  high: "#f97316",
+  medium: "#eab308",
+  low: "#22c55e",
+  none: "#ced4da"
+};
+
 export const generateBarColor = (
   value: string,
   params: ParamsProps,
   baseColors: string[],
   workspaceStates?: IState[]
 ): string => {
   let color = baseColors[0];
   // Priority
   if (params.x_axis === ChartXAxisProperty.PRIORITY) {
-    color =
-      value === "urgent"
-        ? "#ef4444"
-        : value === "high"
-          ? "#f97316"
-          : value === "medium"
-            ? "#eab308"
-            : value === "low"
-              ? "#22c55e"
-              : "#ced4da";
+    color = PRIORITY_COLORS[value as keyof typeof PRIORITY_COLORS] || PRIORITY_COLORS.none;
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4de46e8 and b90792d.

📒 Files selected for processing (2)
  • web/core/components/analytics-v2/work-items/utils.ts (1 hunks)
  • web/core/services/analytics-v2.service.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
web/core/components/analytics-v2/work-items/utils.ts (2)
web/core/store/state.store.ts (1)
  • workspaceStates (86-90)
packages/types/src/state.d.ts (1)
  • IState (5-16)
web/core/services/analytics-v2.service.ts (2)
packages/types/src/analytics-v2.d.ts (3)
  • IAnalyticsResponseV2 (10-12)
  • TAnalyticsTabsV2Base (4-4)
  • TAnalyticsGraphsV2Base (5-5)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
web/core/services/analytics-v2.service.ts (1)

10-25: LGTM! This method follows good practices.

The getAdvanceAnalytics method correctly implements:

  • Generic typing with appropriate constraints
  • Clear parameter typing
  • Proper error handling
  • Consistent promise chaining
web/core/components/analytics-v2/work-items/utils.ts (1)

32-43: LGTM! The state color handling is well implemented.

The state color retrieval has good defensive coding:

  • It checks if workspaceStates exists and is not empty
  • It looks for a matching state by ID and uses its color if found
  • It falls back to a hash-based color selection if no matching state is found

This implementation addresses previous review concerns about null states.

Copy link
Copy Markdown
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: 1

♻️ Duplicate comments (1)
web/core/components/analytics-v2/work-items/priority-chart.tsx (1)

117-118: Verify safe property access for border radius calculations.

The border radius calculations might cause runtime errors if parsedExtremes[payload.key] is undefined for any data point. According to past review comments, this issue was addressed in previous commits, but the optional chaining fix doesn't appear to be implemented here.

-        showTopBorderRadius: (value, payload: TChartDatum) => parsedExtremes[payload.key].top === value,
-        showBottomBorderRadius: (value, payload: TChartDatum) => parsedExtremes[payload.key].bottom === value,
+        showTopBorderRadius: (value, payload: TChartDatum) => parsedExtremes[payload.key]?.top === value,
+        showBottomBorderRadius: (value, payload: TChartDatum) => parsedExtremes[payload.key]?.bottom === value,
🧹 Nitpick comments (6)
web/core/components/analytics-v2/work-items/priority-chart.tsx (6)

193-193: Use regex to replace all underscores in axis label.

The current replacement only handles the first underscore in the string. Using a regular expression would ensure all underscores are replaced.

-              label: xAxisLabel.replace("_", " "),
+              label: xAxisLabel.replace(/_/g, " "),

72-124: Improve performance for large datasets in bar generation logic.

The nested loops and complex data transformations in the bar calculation logic could impact performance with large datasets. Consider memoizing the parsedExtremes calculation or simplifying the algorithm.

You could extract and memoize the parsedExtremes calculation:

const parsedExtremes = useMemo(() => {
  if (!parsedData || !schemaKeys || chart_model !== EChartModels.STACKED) return {};
  
  const extremes = {};
  parsedData.data.forEach((datum) => {
    let top = null;
    let bottom = null;
    for (let i = 0; i < schemaKeys.length; i++) {
      const key = schemaKeys[i];
      if (datum[key] === 0) continue;
      if (!bottom) bottom = key;
      top = key;
    }
    extremes[datum.key] = { top, bottom };
  });
  
  return extremes;
}, [parsedData, schemaKeys, chart_model]);

61-61: Use conditional spread operator for cleaner code.

The current conditional expression for adding project IDs is verbose. You could simplify it with a conditional spread operator pattern.

-        ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
+        ...(selectedProjects?.length ? { project_ids: selectedProjects.join(",") } : {}),

180-182: Consider using a skeleton loader for better UX.

Instead of a generic loader, consider implementing a skeleton loader that mimics the chart and table structure. This provides a better user experience by giving users a preview of the content layout.

You could create a SkeletonChartLoader component that displays placeholder bars and table rows with animated gradients.


5-5: Consider using typed route params with next/navigation.

The useParams hook from Next.js doesn't provide type safety by default. Consider using a typed wrapper or type assertion for route parameters.

// In a types file:
interface WorkspaceParams {
  workspaceSlug: string;
}

// In your component:
const params = useParams() as WorkspaceParams;

62-62: Explicitly list API request parameters instead of using spread props.

Using ...props in API requests can potentially include unwanted properties. Consider explicitly listing only the required parameters.

-        ...props,
+        x_axis: props.x_axis,
+        y_axis: props.y_axis,
+        ...(props.group_by && { group_by: props.group_by }),
+        ...(props.x_axis_date_grouping && { x_axis_date_grouping: props.x_axis_date_grouping }),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b90792d and aa90854.

📒 Files selected for processing (4)
  • web/core/components/analytics-v2/overview/project-insights.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/created-vs-resolved.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/priority-chart.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/workitems-insight-table.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • web/core/components/analytics-v2/overview/project-insights.tsx
  • web/core/components/analytics-v2/work-items/workitems-insight-table.tsx
  • web/core/components/analytics-v2/work-items/created-vs-resolved.tsx

Copy link
Copy Markdown
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: 4

♻️ Duplicate comments (1)
web/core/components/analytics-v2/work-items/utils.ts (1)

1-47: 🛠️ Refactor suggestion

Missing handling for LABELS and other ChartXAxisProperty values.

The current implementation only handles PRIORITY and STATES explicitly, but misses several other ChartXAxisProperty values. Notably, LABELS handling is missing despite being mentioned in a previous review.

Consider refactoring to a switch statement that handles all enum values:

 export const generateBarColor = (
   value: string | null | undefined,
   params: ParamsProps,
   baseColors: string[],
   workspaceStates?: IState[]
 ): string => {
   if (!value) return baseColors[0];
-  let color = baseColors[0];
-  // Priority
-  if (params.x_axis === ChartXAxisProperty.PRIORITY) {
-    color =
-      value === "urgent"
-        ? "#ef4444"
-        : value === "high"
-          ? "#f97316"
-          : value === "medium"
-            ? "#eab308"
-            : value === "low"
-              ? "#22c55e"
-              : "#ced4da";
-  }
-
-  // State
-  if (params.x_axis === ChartXAxisProperty.STATES) {
-    if (workspaceStates && workspaceStates.length > 0) {
-      const state = workspaceStates.find((s) => s.id === value);
-      if (state) {
-        color = state.color;
-      } else {
-        const index = Math.abs(value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)) % baseColors.length;
-        color = baseColors[index];
-      }
-    }
-  }
-
-  return color;
+  
+  // Helper function to generate a consistent color based on string value
+  const getHashedColor = (val: string) => {
+    const index = Math.abs(val.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)) % baseColors.length;
+    return baseColors[index];
+  };
+  
+  switch (params.x_axis) {
+    case ChartXAxisProperty.PRIORITY:
+      return value === "urgent"
+        ? "#ef4444"
+        : value === "high"
+          ? "#f97316"
+          : value === "medium"
+            ? "#eab308"
+            : value === "low"
+              ? "#22c55e"
+              : "#ced4da";
+    
+    case ChartXAxisProperty.STATES:
+      if (workspaceStates && workspaceStates.length > 0) {
+        const state = workspaceStates.find((s) => s.id === value);
+        if (state) return state.color;
+      }
+      return getHashedColor(value);
+      
+    case ChartXAxisProperty.LABELS:
+      // Use hashed color for labels
+      return getHashedColor(value);
+      
+    case ChartXAxisProperty.ASSIGNEES:
+    case ChartXAxisProperty.STATE_GROUPS:
+    case ChartXAxisProperty.ESTIMATE_POINTS:
+    case ChartXAxisProperty.CYCLES:
+    case ChartXAxisProperty.MODULES:
+    case ChartXAxisProperty.START_DATE:
+    case ChartXAxisProperty.TARGET_DATE:
+    case ChartXAxisProperty.COMPLETED_AT:
+    case ChartXAxisProperty.CREATED_AT:
+      // Use hashed color for consistent coloring
+      return getHashedColor(value);
+      
+    default:
+      return baseColors[0];
+  }
 };
🧹 Nitpick comments (12)
web/core/components/analytics-v2/work-items/utils.ts (2)

20-31: Extract priority colors to constants for maintainability.

The hardcoded colors for priorities should be extracted to constants for better maintainability and potential theming support.

+// Define priority colors at the top of the file or in a separate constants file
+const PRIORITY_COLORS = {
+  URGENT: "#ef4444",
+  HIGH: "#f97316",
+  MEDIUM: "#eab308",
+  LOW: "#22c55e",
+  DEFAULT: "#ced4da"
+};
+
 // Priority
 if (params.x_axis === ChartXAxisProperty.PRIORITY) {
   color =
     value === "urgent"
-      ? "#ef4444"
+      ? PRIORITY_COLORS.URGENT
       : value === "high"
-        ? "#f97316"
+        ? PRIORITY_COLORS.HIGH
         : value === "medium"
-          ? "#eab308"
+          ? PRIORITY_COLORS.MEDIUM
           : value === "low"
-            ? "#22c55e"
-            : "#ced4da";
+            ? PRIORITY_COLORS.LOW
+            : PRIORITY_COLORS.DEFAULT;
 }

39-42: Extract hash color function for reusability.

The hash function for generating consistent colors appears only for states but could be reused for other properties. Extract it to a separate function.

+// Helper function to generate a consistent color based on string value
+const getHashedColor = (value: string, baseColors: string[]): string => {
+  const index = Math.abs(value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)) % baseColors.length;
+  return baseColors[index];
+};
+
 // State
 if (params.x_axis === ChartXAxisProperty.STATES) {
   if (workspaceStates && workspaceStates.length > 0) {
     const state = workspaceStates.find((s) => s.id === value);
     if (state) {
       color = state.color;
     } else {
-      const index = Math.abs(value.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0)) % baseColors.length;
-      color = baseColors[index];
+      color = getHashedColor(value, baseColors);
     }
   }
 }
web/app/[workspaceSlug]/(projects)/analytics-old/header.tsx (3)

23-36: Consider debouncing the resize event handler for performance optimization.

The resize event can fire multiple times in quick succession during window resizing, potentially causing unnecessary re-renders. Implementing a debounce function would improve performance by limiting how often the handler executes.

import { useEffect } from "react";
+import { debounce } from "lodash";
// other imports...

export const WorkspaceAnalyticsHeader = observer(() => {
  // other code...

  useEffect(() => {
-    const handleToggleWorkspaceAnalyticsSidebar = () => {
+    const handleToggleWorkspaceAnalyticsSidebar = debounce(() => {
      if (window && window.innerWidth < 768) {
        toggleWorkspaceAnalyticsSidebar(true);
      }
      if (window && workspaceAnalyticsSidebarCollapsed && window.innerWidth >= 768) {
        toggleWorkspaceAnalyticsSidebar(false);
      }
-    };
+    }, 250);

    window.addEventListener("resize", handleToggleWorkspaceAnalyticsSidebar);
    handleToggleWorkspaceAnalyticsSidebar();
-    return () => window.removeEventListener("resize", handleToggleWorkspaceAnalyticsSidebar);
+    return () => {
+      window.removeEventListener("resize", handleToggleWorkspaceAnalyticsSidebar);
+      handleToggleWorkspaceAnalyticsSidebar.cancel();
+    };
  }, [toggleWorkspaceAnalyticsSidebar, workspaceAnalyticsSidebarCollapsed]);

25-28: Remove redundant window checks in client-side code.

Since this is a client-side component (indicated by "use client" directive), the window object will always be available during the execution of this code.

useEffect(() => {
  const handleToggleWorkspaceAnalyticsSidebar = () => {
-    if (window && window.innerWidth < 768) {
+    if (window.innerWidth < 768) {
      toggleWorkspaceAnalyticsSidebar(true);
    }
-    if (window && workspaceAnalyticsSidebarCollapsed && window.innerWidth >= 768) {
+    if (workspaceAnalyticsSidebarCollapsed && window.innerWidth >= 768) {
      toggleWorkspaceAnalyticsSidebar(false);
    }
  };

66-68: Simplify the empty fragment in conditional rendering.

Using an empty fragment as an else condition is unnecessary and can be simplified.

{analytics_tab === "custom" ? (
  <button
    className="block md:hidden"
    onClick={() => {
      toggleWorkspaceAnalyticsSidebar();
    }}
  >
    <PanelRight
      className={cn(
        "block h-4 w-4 md:hidden",
        !workspaceAnalyticsSidebarCollapsed ? "text-custom-primary-100" : "text-custom-text-200"
      )}
    />
  </button>
-) : (
-  <></>
-)}
+) : null}
web/core/components/analytics-v2/insight-card.tsx (1)

30-33: Handling undefined count in UI

When data is still loading, count can be undefined, leading React to render an empty string.
If that’s unintended, consider a placeholder (e.g., “—”) or formatting via toLocaleString for large numbers.

web/app/[workspaceSlug]/(projects)/analytics/page.tsx (1)

49-51: Query-string overwrite risk

router.push(\?tab=${tab.key}`)` wipes all existing query params (e.g., pagination, filters).
Consider merging with current params:

const params = new URLSearchParams(searchParams);
params.set("tab", tab.key);
router.push(`?${params.toString()}`);
web/app/[workspaceSlug]/(projects)/analytics-old/page.tsx (5)

1-108: Well-structured component with a few minor improvement opportunities

Overall, this is a well-organized component with clear separation of concerns. The code effectively uses MobX for state management and handles conditional rendering based on workspace projects.

A few suggestions for improvement:

  1. Implement the TODO on line 43 to refactor the loader implementation
  2. Consider adding error handling for potential API failures
  3. Add propTypes or TypeScript interfaces for better type safety and documentation
  4. Extract the tab rendering logic into a separate component for better readability and maintainability
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-101: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


43-43: Address TODO comment in code

There's an unimplemented TODO comment about refactoring the loader implementation. Either implement the refactoring now or create a tracked issue to address it in the future.


48-101: Unnecessary Fragment can be removed

The static analysis tool flagged that this Fragment is unnecessary. Since it contains conditional rendering with multiple potential children, you could simplify by removing the Fragment and keeping the condition:

{workspaceProjectIds && (
-  <>
    {workspaceProjectIds.length > 0 || loader === "init-loader" ? (
      // Tab content
    ) : (
      // Empty state
    )}
-  </>
)}
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-101: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


92-95: Track analytics action before opening modal

The event tracking is currently being set before the modal action. Consider tracking the actual completion of the action rather than just the intent:

onClick={() => {
  setTrackElement("Analytics empty state");
  toggleCreateProjectModal(true);
+  // Consider adding analytics tracking for successful modal open
}}

19-41: Consider memoizing derived values for performance

The derived pageTitle value and permission check could benefit from memoization, especially in a potentially data-heavy analytics page:

+ const pageTitle = useMemo(() => 
+   currentWorkspace?.name
+     ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
+     : undefined,
+   [currentWorkspace?.name, t]
+ );

+ const canPerformEmptyStateActions = useMemo(() => 
+   allowPermissions(
+     [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
+     EUserPermissionsLevel.WORKSPACE
+   ),
+   [allowPermissions]
+ );

This would help optimize re-renders in a data-intensive analytics page.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa90854 and 56975b2.

📒 Files selected for processing (12)
  • packages/propel/src/charts/scatter-chart/root.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics-old/header.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics-old/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics-old/page.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics/layout.tsx (1 hunks)
  • web/app/[workspaceSlug]/(projects)/analytics/page.tsx (2 hunks)
  • web/core/components/analytics-v2/insight-card.tsx (1 hunks)
  • web/core/components/analytics-v2/insight-table/data-table.tsx (1 hunks)
  • web/core/components/analytics-v2/total-insights.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/priority-chart.tsx (1 hunks)
  • web/core/components/analytics-v2/work-items/utils.ts (1 hunks)
  • web/core/components/empty-state/detailed-empty-state-root.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • web/app/[workspaceSlug]/(projects)/analytics/layout.tsx
  • web/app/[workspaceSlug]/(projects)/analytics-old/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/propel/src/charts/scatter-chart/root.tsx
  • web/core/components/analytics-v2/total-insights.tsx
  • web/core/components/analytics-v2/insight-table/data-table.tsx
  • web/core/components/analytics-v2/work-items/priority-chart.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
web/core/components/analytics-v2/insight-card.tsx (1)
packages/types/src/analytics-v2.d.ts (1)
  • IAnalyticsResponseFieldsV2 (14-17)
web/app/[workspaceSlug]/(projects)/analytics/page.tsx (5)
packages/constants/src/analytics.ts (1)
  • ANALYTICS_TABS (4-10)
packages/i18n/src/store/index.ts (1)
  • t (233-254)
web/core/store/project/project.store.ts (1)
  • workspaceProjectIds (166-174)
packages/ui/src/tabs/tabs.tsx (1)
  • Tabs (29-88)
web/core/store/workspace/index.ts (1)
  • currentWorkspace (98-103)
🪛 Biome (1.9.4)
web/app/[workspaceSlug]/(projects)/analytics-old/page.tsx

[error] 48-101: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (1)
web/app/[workspaceSlug]/(projects)/analytics-old/header.tsx (1)

1-72: LGTM! New header component is well-structured.

The WorkspaceAnalyticsHeader component is well-implemented with proper responsive behavior, correctly integrates with MobX for state management, and appropriately handles cleanup of event listeners. The component supports the legacy analytics UI well.

@sriramveeraghanta sriramveeraghanta merged commit 75d81f9 into preview May 12, 2025
5 of 6 checks passed
@sriramveeraghanta sriramveeraghanta deleted the analytics-page-enhancements branch May 12, 2025 15:20
al-jaber-nishad pushed a commit to Xinotrix-Home/task-and-meeting-management that referenced this pull request May 27, 2025
* chore: analytics endpoint

* added anlytics v2

* updated status icons

* added area chart in workitems and en translations

* active projects

* chore: created analytics chart

* chore: validation errors

* improved radar-chart , added empty states , added projects summary

* chore: added a new graph in advance analytics

* integrated priority chart

* chore: added csv exporter

* added priority dropdown

* integrated created vs resolved chart

* custom x and y axis label in bar and area chart

* added wrapper styles to legends

* added filter components

* fixed temp data imports

* integrated filters in priority charts

* added label to priority chart and updated duration filter

* refactor

* reverted to void onchange

* fixed some contant exports

* fixed type issues

* fixed some type and build issues

* chore: updated the filtering logic for analytics

* updated default value to last_30_days

* percentage value whole number and added some rules for axis options

* fixed some translations

* added - custom tick for radar, calc of insight cards, filter labels

* chore: opitmised the analytics endpoint

* replace old analytics path with new , updated labels of insight card, done some store fixes

* chore: updated the export request

* Enhanced ProjectSelect to support multi-select, improved state management, and optimized data fetching and component structure.

* fix: round completion percentage calculation in ActiveProjectItem

* added empty states in project insights

* Added loader and empty state in created/resolved chart

* added loaders

* added icons in filters

* added custom colors in customised charts

* cleaned up some code

* added some responsiveness

* updated translations

* updated serrchbar for the table

* added work item modal in project analytics

* fixed some of the layput issues in the peek view

* chore: updated the base function for viewsets

* synced tab to url

* code cleanup

* chore: updated the export logic

* fixed project_ids filter

* added icon in projectdropdown

* updated export button position

* export csv and emptystates icons

* refactor

* code refactor

* updated loaders, moved color pallete to contants, added nullish collasece operator in neccessary places

* removed uneccessary cn

* fixed formatting issues

* fixed empty project_ids in payload

* improved null checks

* optimized charts

* modified relevant variables to observable.ref

* fixed the duration type

* optimized some code

* updated query key in project-insight

* updated query key in project-insight

* updated formatting

* chore: replaced analytics route with new one and done some optimizations

* removed the old analytics

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
lifeiscontent pushed a commit that referenced this pull request Aug 18, 2025
* chore: analytics endpoint

* added anlytics v2

* updated status icons

* added area chart in workitems and en translations

* active projects

* chore: created analytics chart

* chore: validation errors

* improved radar-chart , added empty states , added projects summary

* chore: added a new graph in advance analytics

* integrated priority chart

* chore: added csv exporter

* added priority dropdown

* integrated created vs resolved chart

* custom x and y axis label in bar and area chart

* added wrapper styles to legends

* added filter components

* fixed temp data imports

* integrated filters in priority charts

* added label to priority chart and updated duration filter

* refactor

* reverted to void onchange

* fixed some contant exports

* fixed type issues

* fixed some type and build issues

* chore: updated the filtering logic for analytics

* updated default value to last_30_days

* percentage value whole number and added some rules for axis options

* fixed some translations

* added - custom tick for radar, calc of insight cards, filter labels

* chore: opitmised the analytics endpoint

* replace old analytics path with new , updated labels of insight card, done some store fixes

* chore: updated the export request

* Enhanced ProjectSelect to support multi-select, improved state management, and optimized data fetching and component structure.

* fix: round completion percentage calculation in ActiveProjectItem

* added empty states in project insights

* Added loader and empty state in created/resolved chart

* added loaders

* added icons in filters

* added custom colors in customised charts

* cleaned up some code

* added some responsiveness

* updated translations

* updated serrchbar for the table

* added work item modal in project analytics

* fixed some of the layput issues in the peek view

* chore: updated the base function for viewsets

* synced tab to url

* code cleanup

* chore: updated the export logic

* fixed project_ids filter

* added icon in projectdropdown

* updated export button position

* export csv and emptystates icons

* refactor

* code refactor

* updated loaders, moved color pallete to contants, added nullish collasece operator in neccessary places

* removed uneccessary cn

* fixed formatting issues

* fixed empty project_ids in payload

* improved null checks

* optimized charts

* modified relevant variables to observable.ref

* fixed the duration type

* optimized some code

* updated query key in project-insight

* updated query key in project-insight

* updated formatting

* chore: replaced analytics route with new one and done some optimizations

* removed the old analytics

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants