[WEB-3781] Analytics page enhancements#7005
Conversation
…alytics-page-enhancements
…alytics-page-enhancements
…alytics-page-enhancements
…lytics-page-enhancements
…alytics-page-enhancements
WalkthroughThis 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
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
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Pull Request Linked with Plane Work Items Comment Automatically Generated by Plane |
…ane into analytics-page-enhancements
…plane/plane into analytics-page-enhancements
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (15)
web/core/components/analytics-v2/empty-state.tsx (1)
27-37: Replace deprecated Next.js Image propsThe 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 issueIssues with filtering data and parameter validation
The
getmethod acceptsfiltersas a string fromrequest.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 forx_axisandgroup_byparameters.@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 issueFilter parsing and parameter validation issues in chart endpoint
There are similar issues in this endpoint:
filtersis treated as a dict but comes from query params as a stringx_axisandgroup_byneed validation against allowed values- The variable
typeshadows 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 pageThe 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 formattingThe 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 attributesThe
CustomSelectcomponent 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: ReplaceisFinitewithNumber.isFinitefor safer type handlingThe global
isFinitefunction attempts type coercion and could lead to unexpected behavior. UseNumber.isFiniteinstead.- 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 issueAdd 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 issueType mismatch between implementation and interface
There's a type mismatch between the
selectedDurationproperty implementation and its interface definition. The property is typed as(typeof ANALYTICS_V2_DURATION_FILTER_OPTIONS)[number]["value"]but the interface defines it asDurationType.//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
DurationTypevalue fromPROJECT_CREATED_AT_FILTER_OPTIONS.
28-40: 🛠️ Refactor suggestionMissing 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 suggestionImprove search filter implementation and null safety.
The current implementation has two issues:
- It only filters based on the first column
- 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 suggestionImplement 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 suggestionImprove 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 nameThere'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 textThe 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 buttonThe 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 buttonThe 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 experienceThe 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 screensWhen 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 undefinedThe 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 fragmentThe 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 classNameThere 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 dependenciesThe dependency array includes
updateSelectedProjectswhich 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 definitionThe
placeholderprop 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 logicThe 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 detailsThe mapping of project IDs to options doesn't have sufficient error handling for cases where
getProjectByIdmight 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 logicThe 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 handlerThe
onChangehandler 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 errorThe
updateSelectedProjectsmethod 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 updatesSimilar to the
updateSelectedProjectsmethod, theupdateSelectedDurationmethod 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 importThe
AnalyticsV2Serviceis 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 propsThe component accepts
setValueandworkspaceSlugprops 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 changesUpdate 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 selectorAdd 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" functionalityThe "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 withas any.Using
as anybypasses 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
anyas 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
⛔ Files ignored due to path filters (1)
yarn.lockis 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 componentThe change from the old analytics modal to the new
WorkItemsModalcomponent 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 componentThe change from the old analytics modal to the new
WorkItemsModalcomponent 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 goodThe 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 goodThe component is properly wrapped with
observerfor 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 operatorGood 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.PanelsYou're using
Tab.Groupbut there are no visibleTab.ListorTab.Panelscomponents 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 goodGood 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 implementedThe implementation of option filtering based on
hiddenOptionsis clean and efficient. The component also properly handles the "No value" case whenallowNoValueis true.web/core/components/analytics-v2/insight-card.tsx (1)
30-39: Good conditional rendering of trend informationThe 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 V2The 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
TChartColorSchemefrom@plane/typesensures 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
ChartYAxisMetricenum 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
DataTablePropsinterface 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
APIServiceand 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
getAdvanceAnalyticsStatsusestypeas query parameter name whilegetAdvanceAnalyticsusestab. 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 -B5Length 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 -B2Length 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
getAdvanceAnalyticsStatscall sends atypeparameter whilegetAdvanceAnalyticsusestab. I wasn’t able to find where the stats endpoint reads atypequery param in the backend:
- Frontend:
web/core/services/analytics-v2.service.ts(lines 24–35) usestype: tab- Backend:
apiserver/plane/app/views/analytic/advance.py→class AdvanceAnalyticsStatsEndpointinherits fromAdvanceAnalyticsBaseViewPlease verify that the backend expects
typehere (or update the frontend to usetab) so both endpoints remain consistent.
7fe2314 to
4de46e8
Compare
There was a problem hiding this comment.
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
getAdvanceAnalyticsmethod constrains its generic type withT extends IAnalyticsResponseV2, but this constraint is missing fromgetAdvanceAnalyticsStats. 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
getAdvanceAnalyticsmethod constrains its generic type withT extends IAnalyticsResponseV2, but this constraint is missing fromgetAdvanceAnalyticsCharts. 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
📒 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
getAdvanceAnalyticsmethod 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.
There was a problem hiding this comment.
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
parsedExtremescalculation or simplifying the algorithm.You could extract and memoize the
parsedExtremescalculation: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
SkeletonChartLoadercomponent 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
...propsin 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
📒 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
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
web/core/components/analytics-v2/work-items/utils.ts (1)
1-47: 🛠️ Refactor suggestionMissing 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 undefinedcountin UIWhen
datais still loading,countcan beundefined, leading React to render an empty string.
If that’s unintended, consider a placeholder (e.g., “—”) or formatting viatoLocaleStringfor 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 opportunitiesOverall, 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:
- Implement the TODO on line 43 to refactor the loader implementation
- Consider adding error handling for potential API failures
- Add propTypes or TypeScript interfaces for better type safety and documentation
- 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 codeThere'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 removedThe 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 modalThe 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 performanceThe derived
pageTitlevalue 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
📒 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
WorkspaceAnalyticsHeadercomponent 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.
* 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>
* 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>
Description
Added Analytics V2
Type of Change
Screenshots and Media (if applicable)
Screen.Recording.2025-05-01.at.7.17.48.PM.mov
Test Scenarios
References
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Chores