diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
index b0e84e483..8c62c50b3 100644
--- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
+++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
@@ -2,35 +2,19 @@ import React from 'react'
import { CompareTag } from "./CustomChartTooltip";
interface Props {
- data: { chart: any[], namesMap: string[] };
- compData: { chart: any[], namesMap: string[] } | null;
colors: any;
onClick?: (event, index) => void;
yaxis?: any;
label?: string;
hideLegend?: boolean;
+ values: { value: number, compData?: number, series: string, valueLabel?: string }[];
}
function BigNumChart(props: Props) {
const {
- data = { chart: [], namesMap: [] },
- compData = { chart: [], namesMap: [] },
colors,
- onClick = () => null,
label = 'Number of Sessions',
+ values,
} = props;
-
- const values: { value: number, compData?: number, series: string }[] = [];
- for (let i = 0; i < data.namesMap.length; i++) {
- if (!data.namesMap[i]) {
- continue;
- }
-
- values.push({
- value: data.chart.reduce((acc, curr) => acc + curr[data.namesMap[i]], 0),
- compData: compData ? compData.chart.reduce((acc, curr) => acc + curr[compData.namesMap[i]], 0) : undefined,
- series: data.namesMap[i],
- });
- }
return (
{values.map((val, i) => (
@@ -41,18 +25,20 @@ function BigNumChart(props: Props) {
value={val.value}
label={label}
compData={val.compData}
+ valueLabel={val.valueLabel}
/>
))}
)
}
-function BigNum({ color, series, value, label, compData }: {
+function BigNum({ color, series, value, label, compData, valueLabel }: {
color: string,
series: string,
value: number,
label: string,
compData?: number,
+ valueLabel?: string,
}) {
const formattedNumber = (num: number) => {
return Intl.NumberFormat().format(num);
@@ -69,7 +55,7 @@ function BigNum({ color, series, value, label, compData }: {
{series}
- {formattedNumber(value)}
+ {formattedNumber(value)}{valueLabel ? `${valueLabel}` : null}
{label}
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
index 4bc3c345d..a5b962bec 100644
--- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
@@ -6,6 +6,7 @@ import { Styles } from 'App/components/Dashboard/Widgets/common';
import { observer } from 'mobx-react-lite';
import { Icon, Loader } from 'UI';
import { useStore } from 'App/mstore';
+import FunnelTable from "../../../Funnels/FunnelWidget/FunnelTable";
import AreaChart from '../../Widgets/CustomMetricsWidgets/AreaChart';
import BarChart from '../../Widgets/CustomMetricsWidgets/BarChart';
import ProgressBarChart from '../../Widgets/CustomMetricsWidgets/ProgressBarChart';
@@ -215,6 +216,42 @@ function WidgetChart(props: Props) {
const metricWithData = { ...metric, data };
if (metricType === FUNNEL) {
+ console.log(data, compData);
+ if (viewType === 'table') {
+ return (
+
+ )
+ }
+ if (viewType === 'metric') {
+ const values: {
+ value: number;
+ compData?: number;
+ series: string;
+ valueLabel?: string;
+ }[] = [
+ {
+ value: data.funnel.totalConversionsPercentage,
+ compData: compData
+ ? compData.funnel.totalConversionsPercentage
+ : undefined,
+ series: 'Dynamic',
+ valueLabel: '%'
+ },
+ ];
+
+ return (
+
+ );
+ }
+
return (
acc + curr[data.namesMap[i]], 0),
+ compData: compData ? compData.chart.reduce((acc, curr) => acc + curr[compData.namesMap[i]], 0) : undefined,
+ series: data.namesMap[i],
+ });
+ }
return (
) : null}
diff --git a/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx b/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx
index e4e1371fb..c86709c4b 100644
--- a/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx
@@ -4,6 +4,7 @@ import type { TableProps } from 'antd';
import { Eye, EyeOff } from 'lucide-react';
import cn from 'classnames';
import React, { useState } from 'react';
+import { TableExporter } from "../../../Funnels/FunnelWidget/FunnelTable";
const initTableProps = [
{
@@ -27,6 +28,7 @@ interface Props {
enabledRows: string[];
setEnabledRows: (rows: string[]) => void;
defaultOpen?: boolean;
+ metric: { name: string };
}
function WidgetDatatable(props: Props) {
@@ -137,20 +139,11 @@ function WidgetDatatable(props: Props) {
size={'small'}
scroll={{ x: 'max-content' }}
/>
- {/* 1.23+ export menu floater */}
- {/**/}
- {/*
null },*/}
- {/* ]}*/}
- {/* bold*/}
- {/* customTrigger={*/}
- {/* */}
- {/* */}
- {/*
*/}
- {/* }*/}
- {/* />*/}
- {/* */}
+
) : null}
diff --git a/frontend/app/components/Dashboard/components/WidgetOptions.tsx b/frontend/app/components/Dashboard/components/WidgetOptions.tsx
index bf34b95a4..20463cad3 100644
--- a/frontend/app/components/Dashboard/components/WidgetOptions.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetOptions.tsx
@@ -54,7 +54,7 @@ function WidgetOptions() {
)}
{metric.metricType === TIMESERIES ? (
-
+
) : null}
{(metric.metricType === FUNNEL || metric.metricType === TABLE) &&
metric.metricOf != FilterKey.USERID &&
@@ -80,7 +80,7 @@ const SeriesTypeOptions = observer(({ metric }: { metric: any }) => {
const items = {
sessionCount: 'Total Sessions',
userCount: 'Unique Users',
- }
+ };
const chartIcons = {
sessionCount: ,
userCount: ,
@@ -112,7 +112,7 @@ const SeriesTypeOptions = observer(({ metric }: { metric: any }) => {
);
-})
+});
const WidgetViewTypeOptions = observer(({ metric }: { metric: any }) => {
const chartTypes = {
@@ -139,9 +139,17 @@ const WidgetViewTypeOptions = observer(({ metric }: { metric: any }) => {
chart: ,
};
const allowedTypes = {
- [TIMESERIES]: ['lineChart', 'barChart', 'areaChart', 'pieChart', 'progressChart', 'table', 'metric',],
- [FUNNEL]: ['chart', 'columnChart', ] // + table + metric
- }
+ [TIMESERIES]: [
+ 'lineChart',
+ 'barChart',
+ 'areaChart',
+ 'pieChart',
+ 'progressChart',
+ 'table',
+ 'metric',
+ ],
+ [FUNNEL]: ['chart', 'columnChart', 'metric', 'table'],
+ };
return (
{
);
-})
+});
export default observer(WidgetOptions);
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
index 813591b45..1b04d76f9 100644
--- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
@@ -23,7 +23,7 @@ function FunnelBar(props: Props) {
focusedFilter && index ? focusedFilter === index - 1 : false;
return (
-
+
+
{filter.label}
{filter.operator}
{filter.value.map((value: any, index: number) => (
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx
new file mode 100644
index 000000000..c5c5bb9a2
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelTable.tsx
@@ -0,0 +1,131 @@
+import React from 'react';
+import { Table } from 'antd';
+import type { TableProps } from 'antd';
+import Widget from 'App/mstore/types/widget';
+import Funnel from 'App/mstore/types/funnel';
+import { ItemMenu } from 'UI';
+import { EllipsisVertical } from 'lucide-react';
+import { exportAntCsv } from '../../../utils';
+
+interface Props {
+ metric?: Widget;
+ data: { funnel: Funnel };
+ compData: { funnel: Funnel };
+}
+
+function FunnelTable(props: Props) {
+ const tableData = [
+ {
+ conversion: props.data.funnel.totalConversionsPercentage,
+ },
+ ];
+ const tableProps: TableProps['columns'] = [
+ {
+ title: 'Conversion %',
+ dataIndex: 'conversion',
+ key: 'conversion',
+ fixed: 'left',
+ width: 140,
+ render: (text: string, _, index) => (
+
+
Overall {index > 0 ? '(previous)' : ''}
+
{text}%
+
+ ),
+ },
+ ];
+
+ React.useEffect(() => {
+ const funnel = props.data.funnel;
+ funnel.stages.forEach((st, ind) => {
+ const title = `${st.label} ${st.operator} ${st.value.join(' or ')}`;
+ const wrappedTitle =
+ title.length > 40 ? title.slice(0, 40) + '...' : title;
+ tableProps.push({
+ title: wrappedTitle,
+ dataIndex: 'st_' + ind,
+ key: 'st_' + ind,
+ ellipsis: true,
+ width: 120,
+ });
+ tableData[0]['st_' + ind] = st.count;
+ });
+ if (props.compData) {
+ tableData.push({
+ conversion: props.compData.funnel.totalConversionsPercentage,
+ })
+ const compFunnel = props.compData.funnel;
+ compFunnel.stages.forEach((st, ind) => {
+ tableData[1]['st_' + ind] = st.count;
+ });
+ }
+ }, [props.data]);
+
+ return (
+
+
+
(
+ index > 0 ? 'opacity-70' : ''
+ )}
+ />
+
+
+
+ );
+}
+
+export function TableExporter({
+ tableData,
+ tableColumns,
+ filename,
+ top,
+ right,
+}: {
+ tableData: any;
+ tableColumns: any;
+ filename: string;
+ top?: string;
+ right?: string;
+}) {
+ const onClick = () => exportAntCsv(tableColumns, tableData, filename);
+ return (
+
+
+
+
+ }
+ />
+
+ );
+}
+
+export default FunnelTable;
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx
index a44335318..3ca5d1d78 100644
--- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx
@@ -82,7 +82,7 @@ function FunnelWidget(props: Props) {
}
show={!stages || stages.length === 0}
>
-
+
{!isWidget &&
shownStages.map((stage: any, index: any) => (
{
}
const base64 = base64Url.replace("-", "+").replace("_", "/");
return JSON.parse(atob(base64));
-};
\ No newline at end of file
+};
+
+function saveAsFile(blob: Blob, filename: string) {
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+
+ a.href = url;
+ a.download = filename;
+ a.click();
+ window.URL.revokeObjectURL(url);
+}
+
+export function exportAntCsv(tableColumns, tableData, filename = 'table.csv') {
+ console.log(tableColumns, tableData)
+ const headers = tableColumns.map(col => col.title).join(',');
+ const rows = tableData.map(row => {
+ return tableColumns
+ .map(col => {
+ const value = col.dataIndex ? row[col.dataIndex] : '';
+ return typeof value === 'string' ? `"${value.replace(/"/g, '""')}"` : value;
+ })
+ .join(',');
+ });
+
+ const csvContent = [headers, ...rows].join('\n');
+ console.log(csvContent)
+ // const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+
+ // saveAsFile(blob, filename);
+}
\ No newline at end of file