diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx index 3f6e742ec..dce70e78d 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx @@ -33,8 +33,6 @@ function CustomMetricLineChart(props: Props) { hideLegend = false, } = props; - - console.log(data.namesMap, data.chart) return ( - + void; @@ -22,6 +22,7 @@ interface Props { label?: string; hideLegend?: boolean; } + const getPath = (x, y, width, height) => { const radius = Math.min(width / 2, height / 2); return ` @@ -39,15 +40,35 @@ const getPath = (x, y, width, height) => { }; const PillBar = (props) => { - const { fill, x, y, width, height } = props; + const { fill, x, y, width, height, striped } = props; - return ; + return ( + + + {striped && ( + + )} + + ); }; + function CustomMetricLineChart(props: Props) { const { data = { chart: [], namesMap: [] }, + compData, params, colors, onClick = () => null, @@ -56,13 +77,32 @@ function CustomMetricLineChart(props: Props) { hideLegend = false, } = props; + const resultChart = data.chart.map((item, i) => { + if (compData && compData.chart[i]) return { ...compData.chart[i], ...item }; + return item; + }); + return ( + + + + + + + + {!hideLegend && ( )} @@ -71,7 +111,11 @@ function CustomMetricLineChart(props: Props) { vertical={false} stroke="#EEEEEE" /> - + } - fill={colors[index]} + shape={(barProps) => ( + + )} legendType={key === 'Total' ? 'none' : 'line'} - activeBar={} - // strokeDasharray={'4 3'} FOR COPMARISON ONLY + activeBar={ + + } /> ))} + {compData + ? compData.namesMap.map((key, i) => ( + ( + + )} + legendType={key === 'Total' ? 'none' : 'line'} + activeBar={ + + } + /> + )) + : null} ); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx index cc6ca62a6..0baa35959 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { formatTimeOrDate } from 'App/date'; import cn from 'classnames'; import { ArrowUp, ArrowDown } from 'lucide-react' + function CustomTooltip({ active, payload, label }) { if (!active) return; @@ -33,7 +34,7 @@ function CustomTooltip({ active, payload, label }) { className={'flex flex-col gap-1 bg-white shadow border rounded p-2 z-30'} > {transformedArray.map((p, index) => ( - <> +
- + ))}
); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx index b961cbb4f..884b39f49 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx @@ -45,6 +45,7 @@ function CustomMetricLineChart(props: Props) { if (compData && compData.chart[i]) return { ...compData.chart[i], ...item } return item }) + return ( - + {Array.isArray(data.namesMap) && - data.namesMap.map((key, index) => key ? ( - - ) : null)} + data.namesMap.map((key, index) => key ? ( + + ) : null)} {compData ? compData.namesMap.map((key, i) => ( (); const isMounted = useIsMounted(); const [data, setData] = useState(metric.data); @@ -153,8 +153,8 @@ function WidgetChart(props: Props) { prevMetricRef.current = metric; const timestmaps = drillDownPeriod.toTimestamps(); const payload = isSaved - ? { ...params } - : { ...metricParams, ...timestmaps, ...metric.toJson() }; + ? { ...metricParams } + : { ...params, ...timestmaps, ...metric.toJson() }; debounceRequest( metric, payload, @@ -167,7 +167,7 @@ function WidgetChart(props: Props) { if (!dashboardStore.comparisonPeriod) return setCompData(null); const timestamps = dashboardStore.comparisonPeriod.toTimestamps(); - const payload = { ...metricParams, ...timestamps, ...metric.toJson() }; + const payload = { ...params, ...timestamps, ...metric.toJson() }; fetchMetricChartData(metric, payload, isSaved, dashboardStore.comparisonPeriod, true); } useEffect(() => { @@ -180,6 +180,7 @@ function WidgetChart(props: Props) { drillDownPeriod, period, depsString, + dashboardStore.selectedDensity, metric.metricType, metric.metricOf, metric.metricValue, @@ -257,6 +258,7 @@ function WidgetChart(props: Props) { return ( void +}) { + const granularityOptions = React.useMemo(() => { + return calculateGranularities(period.getDuration()); + }, [period]); + + const menuProps = { + items: granularityOptions, + onClick: (item: any) => onDensityChange(item.key), + } + const selected = React.useMemo(() => { + let selected = 'Custom'; + for (const option of granularityOptions) { + if (option.key <= density) { + selected = option.label; + break; + } + } + return selected; + }, []) + return ( + } + menuProps={menuProps} + /> + ) +} + +function calculateGranularities(periodDurationMs: number) { + const granularities = [ + { label: 'Minute', durationMs: 60 * 1000 }, + { label: 'Hourly', durationMs: 60 * 60 * 1000 }, + { label: 'Daily', durationMs: 24 * 60 * 60 * 1000 }, + { label: 'Weekly', durationMs: 7 * 24 * 60 * 60 * 1000 }, + { label: 'Monthly', durationMs: 30 * 24 * 60 * 60 * 1000 }, + { label: 'Quarterly', durationMs: 3 * 30 * 24 * 60 * 60 * 1000 }, + ]; + + const result = []; + + for (const granularity of granularities) { + if (periodDurationMs >= granularity.durationMs) { + const density = Math.floor(periodDurationMs / granularity.durationMs); + result.push({ label: granularity.label, key: density }); + } + } + + return result; +} + +export default RangeGranularity; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx index ff36db1ca..51a46df30 100644 --- a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx +++ b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx @@ -3,9 +3,14 @@ import SelectDateRange from 'Shared/SelectDateRange'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import { Space } from 'antd'; +import RangeGranularity from "./RangeGranularity"; -function WidgetDateRange({ label = 'Time Range', isTimeseries = false }: any) { +function WidgetDateRange({ viewType = undefined, label = 'Time Range', isTimeseries = false }: any) { const { dashboardStore } = useStore(); + const density = dashboardStore.selectedDensity + const onDensityChange = (density: number) => { + dashboardStore.setDensity(density); + } const period = dashboardStore.drillDownPeriod; const compPeriod = dashboardStore.comparisonPeriod; const drillDownFilter = dashboardStore.drillDownFilter; @@ -29,6 +34,8 @@ function WidgetDateRange({ label = 'Time Range', isTimeseries = false }: any) { }); } + const hasGranularity = ['lineChart', 'barChart', 'areaChart'].includes(viewType); + const hasCompare = ['lineChart', 'barChart', 'table', 'progressChart'].includes(viewType); return ( {label && {label}} @@ -40,16 +47,27 @@ function WidgetDateRange({ label = 'Time Range', isTimeseries = false }: any) { useButtonStyle={true} /> {isTimeseries ? ( - + <> + {hasGranularity ? ( + + ) : null} + {hasCompare ? + + : null} + ) : null} ); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx index 60b93ee0b..e2b2a60da 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx @@ -82,7 +82,6 @@ const FilterSection = observer(({ metric, excludeFilterKeys }: any) => { metric.updateKey('hasChanged', true) } - console.log(metric.series, isTable, isClickMap, isInsights, isPathAnalysis, isFunnel) return ( <> {metric.series.length > 0 && diff --git a/frontend/app/components/Dashboard/components/WidgetOptions.tsx b/frontend/app/components/Dashboard/components/WidgetOptions.tsx index 16fd82927..8a5de8708 100644 --- a/frontend/app/components/Dashboard/components/WidgetOptions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetOptions.tsx @@ -9,14 +9,14 @@ import { observer } from 'mobx-react-lite'; import { ChartLine, ChartArea, ChartColumn, ChartBar, ChartPie, Table } from 'lucide-react' function WidgetOptions() { - const { metricStore, dashboardStore } = useStore(); + const { metricStore } = useStore(); const metric: any = metricStore.instance; const handleChange = (value: any) => { metric.update({ metricFormat: value }); }; - const chartTypes = { + const chartTypes = { lineChart: 'Chart', barChart: 'Column', areaChart: 'Area', diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index aae1f278a..f559667ef 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -26,7 +26,7 @@ function WidgetPreview(props: Props) { className={cn(className, 'bg-white rounded-xl border shadow-sm mt-0')} >
- +
{/*{metric.metricType === USER_PATH && (*/} diff --git a/frontend/app/components/shared/Dropdown/index.tsx b/frontend/app/components/shared/Dropdown/index.tsx new file mode 100644 index 000000000..901f62625 --- /dev/null +++ b/frontend/app/components/shared/Dropdown/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Dropdown } from 'antd'; + +function AntlikeDropdown(props: { + label: string; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + menuProps: any; + useButtonStyle?: boolean; + className?: string; +}) { + const { label, leftIcon, rightIcon, menuProps, useButtonStyle, className } = props; + return ( + + {useButtonStyle ? ( +
+ {leftIcon} + {label} + {rightIcon} +
+ ) : ( +
+ {leftIcon} + {label} + {rightIcon} +
+ )} +
+ ); +} + +export default AntlikeDropdown; \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx index 6548e296d..3a193cd68 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx @@ -146,13 +146,28 @@ export function AutoCompleteContainer(props: Props) { : props.value[0]}
{props.value.length > 1 ? ( -
- + {props.value.length - 1} More -
+ props.value.length === 2 ? ( + <> + or +
+ {props.mapValues + ? props.mapValues(props.value[1]) + : props.value[1]} +
+ + ) : ( +
+ + {props.value.length - 1} More +
+ ) ) : null} ) : ( diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index 01c3d5434..6e9431dc0 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -120,7 +120,6 @@ const FilterAutoComplete = observer( setOptions(topValues.map((i) => ({ value: i.value, label: i.value }))); }; - console.log(options) return void; + compPeriod?: any | null; + onChangeComparison?: (data: any) => void; comparison?: boolean; [x: string]: any; } @@ -217,24 +218,13 @@ function AndDateRange({
) : ( - - {useButtonStyle ? ( -
- - {isCustomRange ? customRange : selectedValue?.label} - -
- ) : ( -
- {isCustomRange ? customRange : selectedValue?.label} - -
- )} -
+ : null} + rightIcon={} + /> )} {isCustom && ( = Period({ rangeName: LAST_7_DAYS }); + selectedDensity: number = 7 // depends on default drilldown, 7 points here!!!; comparisonPeriod: Record | null = null startTimestamp: number = 0; endTimestamp: number = 0; @@ -57,6 +58,10 @@ export default class DashboardStore { this.resetDrillDownFilter(); } + setDensity = (density: any) => { + this.selectedDensity = parseInt(density, 10); + } + get sortedDashboards() { const sortOrder = this.sort.by; return [...this.dashboards].sort((a, b) => diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index ce0831404..bd4338698 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -130,6 +130,9 @@ export default Record( endTimestamp: this.end, }; }, + getDuration() { + return this.range.end.diff(this.range.start).as("milliseconds"); + }, rangeFormatted(format = "MMM dd yyyy, HH:mm", tz) { const start = this.range.start.setZone(tz); const end = this.range.end.setZone(tz);