2026-01-27 08:24:22 +00:00
|
|
|
/**
|
|
|
|
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
* See the LICENSE file for details.
|
|
|
|
|
*/
|
|
|
|
|
|
[WEB-3781] Analytics page enhancements (#7005)
* 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>
2025-05-12 15:20:33 +00:00
|
|
|
import { getWeekOfMonth, isValid } from "date-fns";
|
2025-07-01 13:49:44 +00:00
|
|
|
import { CHART_X_AXIS_DATE_PROPERTIES, ChartXAxisDateGrouping, TO_CAPITALIZE_PROPERTIES } from "@plane/constants";
|
2025-10-14 11:15:07 +00:00
|
|
|
import type { ChartXAxisProperty, TChart, TChartDatum } from "@plane/types";
|
2025-07-08 15:11:11 +00:00
|
|
|
import {
|
|
|
|
|
capitalizeFirstLetter,
|
|
|
|
|
hexToHsl,
|
|
|
|
|
hslToHex,
|
|
|
|
|
renderFormattedDate,
|
|
|
|
|
renderFormattedDateWithoutYear,
|
|
|
|
|
} from "@plane/utils";
|
2025-06-16 11:48:41 +00:00
|
|
|
//
|
[WEB-3781] Analytics page enhancements (#7005)
* 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>
2025-05-12 15:20:33 +00:00
|
|
|
|
|
|
|
|
const getDateGroupingName = (date: string, dateGrouping: ChartXAxisDateGrouping): string => {
|
|
|
|
|
if (!date || ["none", "null"].includes(date.toLowerCase())) return "None";
|
|
|
|
|
|
|
|
|
|
const formattedData = new Date(date);
|
|
|
|
|
const isValidDate = isValid(formattedData);
|
|
|
|
|
|
|
|
|
|
if (!isValidDate) return date;
|
|
|
|
|
|
|
|
|
|
const year = formattedData.getFullYear();
|
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
|
|
|
|
|
|
const isCurrentYear = year === currentYear;
|
|
|
|
|
|
|
|
|
|
let parsedName: string | undefined;
|
|
|
|
|
|
|
|
|
|
switch (dateGrouping) {
|
|
|
|
|
case ChartXAxisDateGrouping.DAY:
|
|
|
|
|
if (isCurrentYear) parsedName = renderFormattedDateWithoutYear(formattedData);
|
|
|
|
|
else parsedName = renderFormattedDate(formattedData);
|
|
|
|
|
break;
|
|
|
|
|
case ChartXAxisDateGrouping.WEEK: {
|
|
|
|
|
const month = renderFormattedDate(formattedData, "MMM");
|
|
|
|
|
parsedName = `${month}, Week ${getWeekOfMonth(formattedData)}`;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ChartXAxisDateGrouping.MONTH:
|
|
|
|
|
if (isCurrentYear) parsedName = renderFormattedDate(formattedData, "MMM");
|
|
|
|
|
else parsedName = renderFormattedDate(formattedData, "MMM, yyyy");
|
|
|
|
|
break;
|
|
|
|
|
case ChartXAxisDateGrouping.YEAR:
|
|
|
|
|
parsedName = `${year}`;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
parsedName = date;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parsedName ?? date;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const parseChartData = (
|
|
|
|
|
data: TChart | null | undefined,
|
|
|
|
|
xAxisProperty: ChartXAxisProperty | null | undefined,
|
|
|
|
|
groupByProperty: ChartXAxisProperty | null | undefined,
|
|
|
|
|
xAxisDateGrouping: ChartXAxisDateGrouping | null | undefined
|
|
|
|
|
): TChart => {
|
|
|
|
|
if (!data) {
|
|
|
|
|
return {
|
|
|
|
|
data: [],
|
|
|
|
|
schema: {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const widgetData = structuredClone(data.data);
|
|
|
|
|
const schema = structuredClone(data.schema);
|
|
|
|
|
const allKeys = Object.keys(schema);
|
|
|
|
|
const updatedWidgetData: TChartDatum[] = widgetData.map((datum) => {
|
|
|
|
|
const keys = Object.keys(datum);
|
|
|
|
|
const missingKeys = allKeys.filter((key) => !keys.includes(key));
|
2025-07-08 15:11:11 +00:00
|
|
|
const missingValues: Record<string, number> = Object.fromEntries(missingKeys.map((key) => [key, 0]));
|
[WEB-3781] Analytics page enhancements (#7005)
* 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>
2025-05-12 15:20:33 +00:00
|
|
|
|
|
|
|
|
if (xAxisProperty) {
|
|
|
|
|
// capitalize first letter if xAxisProperty is in TO_CAPITALIZE_PROPERTIES and no groupByProperty is set
|
|
|
|
|
if (TO_CAPITALIZE_PROPERTIES.includes(xAxisProperty)) {
|
|
|
|
|
datum.name = capitalizeFirstLetter(datum.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parse timestamp to visual date if xAxisProperty is in WIDGET_X_AXIS_DATE_PROPERTIES
|
|
|
|
|
if (CHART_X_AXIS_DATE_PROPERTIES.includes(xAxisProperty)) {
|
|
|
|
|
datum.name = getDateGroupingName(datum.name, xAxisDateGrouping ?? ChartXAxisDateGrouping.DAY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...datum,
|
|
|
|
|
...missingValues,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// capitalize first letter if groupByProperty is in TO_CAPITALIZE_PROPERTIES
|
|
|
|
|
const updatedSchema = schema;
|
|
|
|
|
if (groupByProperty) {
|
|
|
|
|
if (TO_CAPITALIZE_PROPERTIES.includes(groupByProperty)) {
|
|
|
|
|
Object.keys(updatedSchema).forEach((key) => {
|
|
|
|
|
updatedSchema[key] = capitalizeFirstLetter(updatedSchema[key]);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CHART_X_AXIS_DATE_PROPERTIES.includes(groupByProperty)) {
|
|
|
|
|
Object.keys(updatedSchema).forEach((key) => {
|
|
|
|
|
updatedSchema[key] = getDateGroupingName(updatedSchema[key], xAxisDateGrouping ?? ChartXAxisDateGrouping.DAY);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
data: updatedWidgetData,
|
|
|
|
|
schema: updatedSchema,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const generateExtendedColors = (baseColorSet: string[], targetCount: number) => {
|
|
|
|
|
const colors = [...baseColorSet];
|
|
|
|
|
const baseCount = baseColorSet.length;
|
|
|
|
|
|
|
|
|
|
if (targetCount <= baseCount) {
|
|
|
|
|
return colors.slice(0, targetCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert base colors to HSL
|
|
|
|
|
const baseHSL = baseColorSet.map(hexToHsl);
|
|
|
|
|
|
|
|
|
|
// Calculate average saturation and lightness from base colors
|
|
|
|
|
const avgSat = baseHSL.reduce((sum, hsl) => sum + hsl.s, 0) / baseHSL.length;
|
|
|
|
|
const avgLight = baseHSL.reduce((sum, hsl) => sum + hsl.l, 0) / baseHSL.length;
|
|
|
|
|
|
|
|
|
|
// Sort base colors by hue for better distribution
|
2026-09-19 15:41:08 +00:00
|
|
|
const sortedBaseHSL = [...baseHSL].toSorted((a, b) => a.h - b.h);
|
[WEB-3781] Analytics page enhancements (#7005)
* 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>
2025-05-12 15:20:33 +00:00
|
|
|
|
|
|
|
|
// Generate additional colors for each base color
|
|
|
|
|
const colorsNeeded = targetCount - baseCount;
|
|
|
|
|
const colorsPerBase = Math.ceil(colorsNeeded / baseCount);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < baseCount; i++) {
|
|
|
|
|
const baseColor = sortedBaseHSL[i];
|
|
|
|
|
const nextBaseColor = sortedBaseHSL[(i + 1) % baseCount];
|
|
|
|
|
|
|
|
|
|
// Calculate hue distance to next base color
|
|
|
|
|
const hueDistance = (nextBaseColor.h - baseColor.h + 360) % 360;
|
|
|
|
|
const hueParts = colorsPerBase + 1;
|
|
|
|
|
|
|
|
|
|
// Narrower ranges for more consistency
|
|
|
|
|
const satRange = [Math.max(40, avgSat - 5), Math.min(60, avgSat + 5)];
|
|
|
|
|
const lightRange = [Math.max(40, avgLight - 5), Math.min(60, avgLight + 5)];
|
|
|
|
|
|
|
|
|
|
for (let j = 1; j <= colorsPerBase; j++) {
|
|
|
|
|
if (colors.length >= targetCount) break;
|
|
|
|
|
|
|
|
|
|
// Create evenly spaced hue variations between base colors
|
|
|
|
|
const hueStep = (hueDistance / hueParts) * j;
|
|
|
|
|
const newHue = (baseColor.h + hueStep) % 360;
|
|
|
|
|
|
|
|
|
|
// Keep saturation and lightness closer to base color
|
|
|
|
|
const newSat = baseColor.s * 0.8 + avgSat * 0.2;
|
|
|
|
|
const newLight = baseColor.l * 0.8 + avgLight * 0.2;
|
|
|
|
|
|
|
|
|
|
// Ensure values stay within desired ranges
|
|
|
|
|
const finalSat = Math.max(satRange[0], Math.min(satRange[1], newSat));
|
|
|
|
|
const finalLight = Math.max(lightRange[0], Math.min(lightRange[1], newLight));
|
|
|
|
|
|
|
|
|
|
colors.push(
|
|
|
|
|
hslToHex({
|
|
|
|
|
h: newHue,
|
|
|
|
|
s: finalSat,
|
|
|
|
|
l: finalLight,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return colors.slice(0, targetCount);
|
2025-07-08 15:11:11 +00:00
|
|
|
};
|