Files
plane/web/ee/components/analytics/modules/modules-progress.tsx
JayashTripathy 578ab887cd [WEB-4254] feat: Enterprise analytics (#3330)
* chore: added code split for the analytics store

* chore: done some refactor

* refactor: update entity keys in analytics and translations

* chore: updated the translations

* refactor: simplify AnalyticsStoreV2 class by removing unnecessary constructor

* feat: add AnalyticsStoreV2 class and interface for enhanced analytics functionality

* feat: enhance WorkItemsModal and analytics store with isEpic functionality

* feat: integrate isEpic state into TotalInsights and WorkItemsModal components

* refactor: remove isEpic state from WorkItemsModalMainContent component

* refactor: removed old  analytics components and related services

* refactor: new analytics

* refactor: removed all nivo chart dependencies

* chore: resolved coderabbit comments

* fix: remove duplicate isEpic prop in WorkItemsModal component

* fix: update type for tab parameter in processUrl method of AnalyticsV2Service

* fix: update processUrl to handle custom-work-items in peek view

* feat: implement CSV export functionality in InsightTable component

* feat: add user analytics components and extend analytics functionality

* feat: enhance analytics service with filter parameters and improve data handling in InsightTable

* feat: add projects analytics components

* feat: add cycles, modules, and intake analytics components with corresponding data handling and visualization

* feat: add new translation keys for various statuses across multiple languages

* chore: update analytics components and translations

* [WEB-4246] fix: enhance analytics components to include 'isEpic' parameter for improved data fetching

* refactor: clean up imports and update translation keys in analytics components

* chore: added project logo props in charts

* feat: integrate project logo display in analytics tables

* chore: added feature flag

* chore: disabled users

* chore: added feature flag

* chore: added payment failure vaidation

* feat: enhance analytics page with subscription upgrade tooltip and update translations

* updated translations

* chore: added additional parameters in payload

* added state icons in module and cycle table

* fix: handle empty date values in cycles and modules insight tables

* chore: added the keys for module

* chore: added custom tooltip for cycles and modules

* chore: added color to the scatter points

* refactor: centralize module status colors and update chart point colors and yaxis domain

* fix: update comment to clarify display name usage in WorkItemInsightColumns

* feat: add completed work items count to analytics and update chart components

* refactor: update trend-piece component to use new status variants for analytics

* feat: enhance ee analytics tables with export functionality and added project logo in intake

* refactor: clean up fetch keys and simplify analytics component structure

* refactor: rename analytics fields constants for consistency and clarity

* refactor: update analytics feature flag for improved clarity and consistency

* chore: changed the feature flag of analytics

* refactor: streamline analytics tab structure and introduce locked tab labels for feature flag check while rendering tab label

* chore: remove unused translation key for entity count in English locale

* fix: import of intake

* fix: append percentage sign to completion percentage label in cycles insight table

* refactor: simplify completion percentage calculation in ModulesCyclesTooltip and enhance Avatar component styling in UserAvatarName

* refactor: update AnalyticsWrapper titles to use i18n keys and clean up imports in analytics components

* fix: update isEpic handling in WorkItemsModal to consider isPeekView state

* fix: update i18n title in AnalyticsWrapper for Intake component

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2025-06-18 15:53:32 +05:30

162 lines
6.0 KiB
TypeScript

import React, { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
import { MODULE_STATUS, MODULE_STATUS_COLORS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { ScatterChart } from "@plane/propel/charts/scatter-chart";
import { IChartResponse, TChartData, TModuleStatus, TScatterPointItem } from "@plane/types";
import { IModuleProgressData } from "@plane/types/src/analytics-extended";
import { renderFormattedDate } from "@plane/utils";
import AnalyticsSectionWrapper from "@/components/analytics/analytics-section-wrapper";
import AnalyticsEmptyState from "@/components/analytics/empty-state";
import { ChartLoader } from "@/components/analytics/loaders";
import { useAnalytics } from "@/hooks/store/use-analytics";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { AnalyticsService } from "@/services/analytics.service";
import ModulesCyclesTooltip, { ICycleModuleTooltipProps } from "../modules-cycles-tooltip";
const analyticsService = new AnalyticsService();
const ModuleProgress = observer(() => {
const params = useParams();
const { t } = useTranslation();
const workspaceSlug = params.workspaceSlug.toString();
const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } =
useAnalytics();
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-radar" });
const { data: moduleInsightsData, isLoading: isLoadingModuleInsight } = useSWR(
`radar-chart-module-progress-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
() =>
analyticsService.getAdvanceAnalyticsCharts<IChartResponse>(
workspaceSlug,
"modules",
{
// date_filter: selectedDuration,
...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
...(selectedModule ? { module_id: selectedModule } : {}),
},
isPeekView
)
);
const parsedData: TChartData<string, string>[] = useMemo(() => {
if (!moduleInsightsData?.data) return [];
return moduleInsightsData.data.map((datum) => ({
...datum,
[datum.key]: datum.count,
name: renderFormattedDate(datum.key) ?? datum.key,
}));
}, [moduleInsightsData]);
const scatterPoints: TScatterPointItem<string>[] = useMemo(() => {
if (!parsedData) return [];
return parsedData.map((datum) => ({
key: datum.key,
label: datum.name,
x: datum.key,
y: datum.count,
fill: MODULE_STATUS_COLORS[datum.status.toLowerCase() as TModuleStatus],
stroke: MODULE_STATUS_COLORS[datum.status.toLowerCase() as TModuleStatus],
}));
}, [parsedData]);
const tooltipRows = useCallback(
(data: IModuleProgressData) => [
{
label: t("workspace_analytics.total", {
entity: t("common.work_items"),
}),
value: data.total_issues,
},
{
label: t("workspace_projects.state.completed"),
value: data.completed_issues,
},
{
label: t("workspace_projects.state.started"),
value: data.started_issues,
},
{
label: t("workspace_projects.state.unstarted"),
value: data.unstarted_issues,
},
{
label: t("workspace_projects.state.cancelled"),
value: data.cancelled_issues,
},
],
[t]
);
return (
<AnalyticsSectionWrapper title="Module Progress" subtitle={selectedDurationLabel} className="col-span-1 ">
{isLoadingModuleInsight ? (
<ChartLoader />
) : moduleInsightsData && moduleInsightsData?.data?.length > 0 ? (
<div className="h-[350px] flex flex-col gap-4">
<ScatterChart
data={parsedData}
scatterPoints={scatterPoints}
className="h-full"
xAxis={{
key: "name",
label: t("common.modules"),
}}
yAxis={{
key: "count",
label: t("common.completion") + " " + "%",
offset: -30,
dx: -25,
domain: [0, 100],
}}
tickCount={{
y: 100,
}}
margin={{
top: 20,
right: 20,
bottom: 35,
left: 20,
}}
showTooltip
customTooltipContent={({ active, label, payload }) => {
if (!active) return null;
const data: IModuleProgressData = payload[0]?.payload as IModuleProgressData;
const tooltipProps: ICycleModuleTooltipProps = {
title: data.name,
startDate: data.start_date,
endDate: data.target_date,
rows: tooltipRows(data),
totalCount: data.total_issues,
completedCount: data.completed_issues,
};
return <ModulesCyclesTooltip {...tooltipProps} />;
}}
/>
<div className="flex gap-4 pl-12">
{Object.entries(MODULE_STATUS_COLORS).map(([key, value]) => (
<div key={key} className="flex items-center gap-2 ">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: value }} />
<span className="text-sm opacity-50">
{t(MODULE_STATUS.find((status) => status.value === key)?.i18n_label || "")}
</span>
</div>
))}
</div>
</div>
) : (
<AnalyticsEmptyState
title={t("workspace_analytics.empty_state.module_progress.title")}
description={t("workspace_analytics.empty_state.module_progress.description")}
className="h-[350px]"
assetPath={resolvedPath}
/>
)}
</AnalyticsSectionWrapper>
);
});
export default ModuleProgress;