From 488dfcd849a6f21456920e485b7dcc31a78e6715 Mon Sep 17 00:00:00 2001 From: Sudheer Salavadi Date: Tue, 24 Dec 2024 04:44:24 -0500 Subject: [PATCH] Various improvements in graphs, and analytics pages. (#2908) * Various improvements Cards, OmniSearch and Cards Listing * Improved cards listing page * Various improvements in product analytics * Charts UI improvements * ui crash * Chart improvements and layout toggling * Various improvements * Tooltips --------- Co-authored-by: nick-delirium --- .../CustomMetricsWidgets/AreaChart.tsx | 86 ++++++++--- .../Widgets/CustomMetricsWidgets/BarChart.tsx | 37 ++++- .../CustomMetricsWidgets/BigNumChart.tsx | 4 +- .../CustomChartTooltip.tsx | 85 ++++++----- .../CustomMetricLineChart.tsx | 133 +++++++++--------- .../CustomMetricPieChart.tsx | 71 +++++----- .../CustomMetricsWidgets/ProgressBarChart.tsx | 116 +++++++++------ .../components/FilterSeries/FilterSeries.tsx | 22 +-- .../FilterSeries/SeriesName/SeriesName.tsx | 7 +- .../MetricListItem/MetricListItem.tsx | 2 +- .../components/MetricsList/ListView.tsx | 8 +- .../components/WidgetChart/WidgetChart.tsx | 19 +-- .../WidgetDatatable/WidgetDatatable.tsx | 34 ++--- .../components/WidgetName/WidgetName.tsx | 4 +- .../Dashboard/components/WidgetOptions.tsx | 42 +++--- .../components/WidgetView/WidgetView.tsx | 69 +++++++-- .../WidgetView/WidgetViewHeader.tsx | 30 ++-- .../FilterAutoComplete/AutocompleteModal.tsx | 108 ++++++++------ .../shared/Filters/FilterItem/FilterItem.tsx | 6 +- .../shared/Filters/FilterList/EventsOrder.tsx | 9 +- .../shared/Filters/FilterList/FilterList.tsx | 16 +-- .../FilterSelection/FilterSelection.tsx | 2 +- .../Filters/FilterValue/FilterValue.tsx | 4 +- .../Filters/SubFilterItem/SubFilterItem.tsx | 2 +- .../app/components/shared/Select/Select.tsx | 2 +- 25 files changed, 554 insertions(+), 364 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx index 04caa0c46..e858f4f2e 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/AreaChart.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import CustomTooltip from "./CustomChartTooltip"; import { Styles } from '../common'; import { @@ -33,15 +33,43 @@ function CustomAreaChart(props: Props) { inGrid, } = props; + const [hoveredSeries, setHoveredSeries] = useState(null); + + const handleMouseOver = (key: string) => () => { + setHoveredSeries(key); + }; + + const handleMouseLeave = () => { + setHoveredSeries(null); + }; + + // Dynamically reorder namesMap to render hovered series last + const reorderedNamesMap = hoveredSeries + ? [...data.namesMap.filter((key) => key !== hoveredSeries), hoveredSeries] + : data.namesMap; + return ( {!hideLegend && ( - + ({ + value: key, + type: 'line', + color: colors[index], + id: key, + })) + } + /> )} - - {Array.isArray(data.namesMap) && - data.namesMap.map((key, index) => ( - - ))} + } // Pass hoveredSeries to tooltip + /> + {Array.isArray(reorderedNamesMap) && + reorderedNamesMap.map((key, index) => ( + + ))} ); } -export default CustomAreaChart; +export default CustomAreaChart; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BarChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BarChart.tsx index 2ac8e14a4..498e0593d 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BarChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BarChart.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import CustomTooltip from "./CustomChartTooltip"; import { Styles } from '../common'; import { @@ -71,8 +71,26 @@ function CustomBarChart(props: Props) { inGrid, } = props; + const [hoveredSeries, setHoveredSeries] = useState(null); + + const handleMouseOver = (key) => () => { + setHoveredSeries(key); + }; + + const handleMouseLeave = () => { + setHoveredSeries(null); + }; + const resultChart = data.chart.map((item, i) => { - if (compData && compData.chart[i]) return { ...compData.chart[i], ...item }; + if (compData && compData.chart[i]) { + const comparisonItem: Record = {}; + Object.keys(compData.chart[i]).forEach(key => { + if (key !== 'time') { + comparisonItem[`${key}_comparison`] = (compData.chart[i] as any)[key]; + } + }); + return { ...item, ...comparisonItem }; + } return item; }); @@ -84,7 +102,6 @@ function CustomBarChart(props: Props) { } } - // Filter out comparison items for legend const legendItems = mergedNameMap.filter(item => !item.isComp); return ( @@ -93,7 +110,9 @@ function CustomBarChart(props: Props) { data={resultChart} margin={Styles.chartMargins} onClick={onClick} + onMouseLeave={handleMouseLeave} barSize={10} + style={{ backgroundColor: 'transparent' }} > @@ -147,15 +166,16 @@ function CustomBarChart(props: Props) { value: label || 'Number of Sessions', }} /> - + } /> {mergedNameMap.map((item) => ( ( )} + fillOpacity={ + hoveredSeries && + hoveredSeries !== item.data && + hoveredSeries !== `${item.data} (Comparison)` ? 0.2 : 1 + } legendType="rect" activeBar={ +
{values.map((val, i) => ( +
{series}
diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx index 5d1f5fe5b..ba76974c7 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomChartTooltip.tsx @@ -16,27 +16,32 @@ interface Props { active: boolean; payload: PayloadItem[]; label: string; - hoveredSeries: string | null; + hoveredSeries?: string | null; } function CustomTooltip(props: Props) { - const { active, payload, label, hoveredSeries } = props; + const { active, payload, label, hoveredSeries = null } = props; + + // Return null if tooltip is not active or there is no valid payload if (!active || !payload?.length || !hoveredSeries) return null; // Find the current and comparison payloads - const currentPayload = payload.find(p => p.name === hoveredSeries); - const comparisonPayload = payload.find(p => - p.name === `${hoveredSeries.replace(' (Comparison)', '')} (Comparison)` || - p.name === `${hoveredSeries} (Comparison)` + const currentPayload = payload.find((p) => p.name === hoveredSeries); + const comparisonPayload = payload.find( + (p) => + p.name === `${hoveredSeries.replace(' (Comparison)', '')} (Comparison)` || + p.name === `${hoveredSeries} (Comparison)` ); if (!currentPayload) return null; // Create transformed array with comparison data - const transformedArray = [{ - ...currentPayload, - prevValue: comparisonPayload ? comparisonPayload.value : null - }]; + const transformedArray = [ + { + ...currentPayload, + prevValue: comparisonPayload ? comparisonPayload.value : null, + }, + ]; const isHigher = (item: { value: number; prevValue: number }) => item.prevValue !== null && item.prevValue < item.value; @@ -45,34 +50,36 @@ function CustomTooltip(props: Props) { (((val - prevVal) / prevVal) * 100).toFixed(2); return ( -
+
{transformedArray.map((p, index) => ( -
+
-
{index + 1}
+
{index + 1}
-
{p.name}
+
{p.name}
-
- {label}, {formatTimeOrDate(p.payload.timestamp)} +
+ {label},{' '} + {p.payload?.timestamp + ? formatTimeOrDate(p.payload.timestamp) + :
'Timestamp is not Applicable'
}
-
-
{p.value}
- {p.prevValue !== null && ( - - )} +
+
{p.value}
+ +
@@ -86,20 +93,30 @@ export function CompareTag({ absDelta, delta, }: { - isHigher: boolean; - absDelta?: number | string; - delta?: string; + isHigher: boolean | null; // Allow null for default view + absDelta?: number | string | null; + delta?: string | null; }) { return (
- {!isHigher ? : } -
{absDelta}
-
({delta}%)
+ {isHigher === null ? ( +
No Comparison
+ ) : ( + <> + {!isHigher ? : } +
{absDelta}
+
({delta}%)
+ + )}
); } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx index 6e64177de..c460009e0 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx @@ -98,71 +98,74 @@ function CustomMetricLineChart(props: Props) { } /> {Array.isArray(data.namesMap) && - data.namesMap.map((key, index) => - key ? ( - - ) : null - )} - - {compData?.namesMap?.map((key, i) => - data.namesMap[i] ? ( - - ) : null - )} + data.namesMap.map((key, index) => + key ? ( + + ) : null + )} + +{compData?.namesMap?.map((key, i) => + data.namesMap[i] ? ( + + ) : null +)} ); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index 0b9896679..b0d8029c0 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -1,10 +1,11 @@ -import React from 'react'; +import React, { useState } from 'react'; import { ResponsiveContainer, Tooltip } from 'recharts'; import { PieChart, Pie, Cell, Legend } from 'recharts'; import { Styles } from '../../common'; import { NoContent } from 'UI'; import { filtersMap } from 'Types/filter/newFilter'; import { numberWithCommas } from 'App/utils'; +import CustomTooltip from '../CustomChartTooltip'; interface Props { metric: { @@ -23,6 +24,8 @@ interface Props { function CustomMetricPieChart(props: Props) { const { metric, data, onClick = () => null, inGrid } = props; + const [hoveredSeries, setHoveredSeries] = useState(null); + const onClickHandler = (event) => { if (event && !event.payload.group) { const filters = Array(); @@ -41,19 +44,22 @@ function CustomMetricPieChart(props: Props) { } }; - const getTotalForSeries = (series: string) => { - return data.chart ? data.chart.reduce((acc, curr) => acc + curr[series], 0) : 0 - } - const values = data.namesMap.map((k, i) => { - return { - name: k, - value: getTotalForSeries(k) - } - }) - const highest = values.reduce( - (acc, curr) => - acc.value > curr.value ? acc : curr, - { name: '', value: 0 }); + const handleMouseOver = (name: string) => setHoveredSeries(name); + const handleMouseLeave = () => setHoveredSeries(null); + + const getTotalForSeries = (series: string) => + data.chart ? data.chart.reduce((acc, curr) => acc + curr[series], 0) : 0; + + const values = data.namesMap.map((k) => ({ + name: k, + value: getTotalForSeries(k), + })); + + const highest = values.reduce( + (acc, curr) => (acc.value > curr.value ? acc : curr), + { name: '', value: 0 } + ); + return ( - + + } + /> handleMouseOver(name)} + onMouseLeave={handleMouseLeave} labelLine={({ cx, cy, @@ -89,9 +100,7 @@ function CustomMetricPieChart(props: Props) { let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN); let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN); - const percentage = - (value * 100) / - highest.value; + const percentage = (value * 100) / highest.value; if (percentage < 3) { return null; @@ -121,9 +130,7 @@ function CustomMetricPieChart(props: Props) { let radius = 20 + innerRadius + (outerRadius - innerRadius); let x = cx + radius * Math.cos(-midAngle * RADIAN); let y = cy + radius * Math.sin(-midAngle * RADIAN); - const percentage = - (value / highest.value) * - 100; + const percentage = (value / highest.value) * 100; let name = values[index].name || 'Unidentified'; name = name.length > 20 ? name.substring(0, 20) + '...' : name; if (percentage < 3) { @@ -135,7 +142,6 @@ function CustomMetricPieChart(props: Props) { y={y} fontWeight="400" fontSize="12px" - // fontFamily="'Source Sans Pro', 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fill="#666" @@ -145,18 +151,17 @@ function CustomMetricPieChart(props: Props) { ); }} > - {values && values.map((entry, index) => ( - - ))} + {values.map((entry, index) => ( + + ))} - ); } -export default CustomMetricPieChart; +export default CustomMetricPieChart; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ProgressBarChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ProgressBarChart.tsx index 1732d54fb..568237b3c 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ProgressBarChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ProgressBarChart.tsx @@ -32,54 +32,90 @@ function ProgressBarChart(props: Props) { return Intl.NumberFormat().format(num); } - // we mix 1 original, then 1 comparison, etc - const mergedNameMap: { data: any, isComp: boolean, index: number }[] = []; + // Group the data into pairs (original + comparison) + const groupedData: Array<{ original: any, comparison: any }> = []; for (let i = 0; i < data.namesMap.length; i++) { - if (!data.namesMap[i]) { - continue; - } - mergedNameMap.push({ data: data.namesMap[i], isComp: false, index: i }); - if (compData && compData.namesMap[i]) { - mergedNameMap.push({ data: compData.namesMap[i], isComp: true, index: i }); - } + if (!data.namesMap[i]) continue; + + const original = { + name: data.namesMap[i], + value: getTotalForSeries(data.namesMap[i], false), + isComp: false, + index: i + }; + + const comparison = compData && compData.namesMap[i] ? { + name: compData.namesMap[i], + value: getTotalForSeries(compData.namesMap[i], true), + isComp: true, + index: i + } : null; + + groupedData.push({ original, comparison }); } - const values = mergedNameMap.map((k, i) => { - return { - name: k.data, - value: getTotalForSeries(k.data, k.isComp), - isComp: k.isComp, - index: k.index, - } - }) - const highest = values.reduce( - (acc, curr) => - acc.value > curr.value ? acc : curr, - { name: '', value: 0 }); + // Find highest value among all data points + const highest = groupedData.reduce((acc, curr) => { + const maxInGroup = Math.max( + curr.original.value, + curr.comparison ? curr.comparison.value : 0 + ); + return Math.max(acc, maxInGroup); + }, 0); + return ( -
- {values.map((val, i) => ( -
-
-
- {val.name} +
+ {groupedData.map((group, i) => ( +
+
+
+
+ {group.original.name} +
+
+
+
{formattedNumber(group.original.value)}
+
+
-
-
-
{formattedNumber(val.value)}
-
-
+ {group.comparison && ( +
+
+
+ {group.comparison.name} +
+
+
+
{formattedNumber(group.comparison.value)}
+
+
+
+ )}
))}
); } -export default ProgressBarChart; +export default ProgressBarChart; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index dfde9ad8e..b44995a87 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -16,10 +16,10 @@ const FilterCountLabels = observer( {events > 0 && ( @@ -27,10 +27,10 @@ const FilterCountLabels = observer( {filters > 0 && ( @@ -69,23 +69,25 @@ const FilterSeriesHeader = observer( onUpdate={onUpdate} onChange={props.onChange} /> - {!props.expanded && ( + + + + {!props.expanded && ( )} - - -
{renderModal()} diff --git a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx index 9b3c52d00..bdc5dace2 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx @@ -4,6 +4,7 @@ import MetricListItem from '../MetricListItem'; import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface'; import Widget from 'App/mstore/types/widget'; import { LockOutlined, TeamOutlined } from "@ant-design/icons"; +import classNames from 'classnames'; const { Text } = Typography; @@ -172,16 +173,15 @@ const ListView: React.FC = (props: Props) => {
Visibility
- toggleOwn() - } + onChange={() => toggleOwn()} checkedChildren={'Team'} unCheckedChildren={'Private'} + className={classNames( '!bg-tealx')} />
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 7e5106cca..1ac9b7a7c 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -284,6 +284,7 @@ function WidgetChart(props: Props) { : chartData.namesMap; if (viewType === 'lineChart') { return ( +
+
); } if (viewType === 'areaChart') { return ( +
+
); } if (viewType === 'barChart') { return ( +
+
); } + if (viewType === 'progressChart') { return ( +
); } if (viewType === 'progress') { @@ -507,15 +516,7 @@ function WidgetChart(props: Props) { return (
-
+
{renderChart()} {props.isPreview && _metric.metricType === TIMESERIES ? ( void; defaultOpen?: boolean; - metric: { name: string }; - isTableView?: boolean; + metric: { name: string; viewType: string }; } function WidgetDatatable(props: Props) { @@ -112,33 +111,28 @@ function WidgetDatatable(props: Props) { }), type: 'checkbox', }; + + const isTableOnlyMode = props.metric.viewType === 'table'; + return ( -
- {props.isTableView ? null : ( - <> -
-
+
+ {!isTableOnlyMode && ( +
+ -
- + +
)} - {showTable ? ( + + {(showTable || isTableOnlyMode) ? (
) : ( // @ts-ignore - +
setEditing(true)} + onClick={() => setEditing(true)} className={cn( "text-2xl h-8 flex items-center p-2 rounded-lg", canEdit && 'cursor-pointer select-none ps-2 hover:bg-teal/10' diff --git a/frontend/app/components/Dashboard/components/WidgetOptions.tsx b/frontend/app/components/Dashboard/components/WidgetOptions.tsx index 84b9607c9..48aa47db7 100644 --- a/frontend/app/components/Dashboard/components/WidgetOptions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetOptions.tsx @@ -24,7 +24,7 @@ import { Library, ChartColumnBig, ChartBarBig, -} from 'lucide-react'; +} from 'lucide-react'; function WidgetOptions() { const { metricStore } = useStore(); @@ -32,6 +32,7 @@ function WidgetOptions() { const handleChange = (value: any) => { metric.update({ metricFormat: value }); + metric.updateKey('hasChanged', true); }; // const hasSeriesTypes = [TIMESERIES, FUNNEL, TABLE].includes(metric.metricType); @@ -54,12 +55,10 @@ function WidgetOptions() { )} - {metric.metricType === TIMESERIES ? ( - - ) : null} + {metric.metricType === TIMESERIES && } {(metric.metricType === FUNNEL || metric.metricType === TABLE) && - metric.metricOf != FilterKey.USERID && - metric.metricOf != FilterKey.ERRORS && ( + metric.metricOf !== FilterKey.USERID && + metric.metricOf !== FilterKey.ERRORS && ( handleChange(info.key), }} + > )} - {hasViewTypes ? : null} - - {metric.metricType === HEATMAP ? : null} + {hasViewTypes && } + {metric.metricType === HEATMAP && }
); } @@ -114,7 +113,7 @@ const SeriesTypeOptions = observer(({ metric }: { metric: any }) => { })), onClick: ({ key }: any) => { metric.updateKey('metricOf', key); - metric.updateKey('hasChanged', true) + metric.updateKey('hasChanged', true); }, }} > @@ -137,23 +136,22 @@ const SeriesTypeOptions = observer(({ metric }: { metric: any }) => { const WidgetViewTypeOptions = observer(({ metric }: { metric: any }) => { const chartTypes = { lineChart: 'Line', - barChart: 'Column', areaChart: 'Area', + barChart: 'Column', + progressChart: 'Vertical Bar', + columnChart: 'Horizontal Bar', pieChart: 'Pie', - progressChart: 'Bar', - table: 'Table', metric: 'Metric', - chart: 'Funnel Bar', - columnChart: 'Funnel Column', + table: 'Table', }; const chartIcons = { - lineChart: , + lineChart: , barChart: , areaChart: , pieChart: , progressChart: , - table:
, metric: , + table:
, // funnel specific columnChart: , chart: , @@ -161,14 +159,14 @@ const WidgetViewTypeOptions = observer(({ metric }: { metric: any }) => { const allowedTypes = { [TIMESERIES]: [ 'lineChart', - 'barChart', 'areaChart', - 'pieChart', + 'barChart', 'progressChart', - 'table', + 'pieChart', 'metric', + 'table', ], - [FUNNEL]: ['chart', 'columnChart', 'metric', 'table'], + [FUNNEL]: ['lineChart', 'areaChart', 'barChart', 'progressChart', 'pieChart', 'metric', 'table'], }; return ( { key, label: (
- <> {chartIcons[key]}
{chartTypes[key]}
-
), })), diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 0b07145d3..916eaac84 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -21,9 +21,11 @@ import { import CardUserList from '../CardUserList/CardUserList'; import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader'; import WidgetFormNew from 'Components/Dashboard/components/WidgetForm/WidgetFormNew'; -import { Space } from 'antd'; +import { Space, Segmented, Tooltip } from 'antd'; import { renderClickmapThumbnail } from 'Components/Dashboard/components/WidgetForm/renderMap'; import Widget from 'App/mstore/types/widget'; +import { LayoutPanelTop, LayoutPanelLeft } from 'lucide-react'; +import classNames from 'classnames'; interface Props { history: any; @@ -32,6 +34,7 @@ interface Props { } function WidgetView(props: Props) { + const [layout, setLayout] = useState('horizontal'); const { match: { params: { siteId, dashboardId, metricId }, @@ -137,21 +140,57 @@ function WidgetView(props: Props) { } > - - - + + + + ) + }, + { + value: 'vertical', + icon: ( + + + + ) + } + ]} + /> + } + /> +
+
+ +
+
+ - {widget.metricOf !== FilterKey.SESSIONS && - widget.metricOf !== FilterKey.ERRORS && - (widget.metricType === TABLE || - widget.metricType === TIMESERIES || - widget.metricType === HEATMAP || - widget.metricType === INSIGHTS || - widget.metricType === FUNNEL || - widget.metricType === USER_PATH ? ( - - ) : null)} - {widget.metricType === RETENTION && } + {widget.metricOf !== FilterKey.SESSIONS && + widget.metricOf !== FilterKey.ERRORS && + (widget.metricType === TABLE || + widget.metricType === TIMESERIES || + widget.metricType === HEATMAP || + widget.metricType === INSIGHTS || + widget.metricType === FUNNEL || + widget.metricType === USER_PATH ? ( + + ) : null)} + {widget.metricType === RETENTION && } +
+
+ +
diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx index 8bca5efba..550cd3f87 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx @@ -12,11 +12,13 @@ import copy from 'copy-to-clipboard'; interface Props { onClick?: () => void; onSave: () => void; + undoChanges: () => void; + layoutControl?: React.ReactNode; } const defaultText = 'Copy link to clipboard' -function WidgetViewHeader({ onClick, onSave }: Props) { +function WidgetViewHeader({ onClick, onSave, layoutControl }: Props) { const [tooltipText, setTooltipText] = React.useState(defaultText); const { metricStore } = useStore(); const widget = metricStore.instance; @@ -48,25 +50,25 @@ function WidgetViewHeader({ onClick, onSave }: Props) { /> - - + {/* */} - + {layoutControl} diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx index 454b80868..efd2e0127 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/AutocompleteModal.tsx @@ -1,9 +1,39 @@ -import React, { useRef, useState } from 'react'; -import { Button, Checkbox, Input } from 'antd'; +import React, { useRef, useState, useEffect } from 'react'; +import { Button, Checkbox, Input, Tooltip } from 'antd'; import cn from 'classnames'; import { Loader } from 'UI'; import OutsideClickDetectingDiv from '../../OutsideClickDetectingDiv'; + +function TruncatedText({ text, maxWidth }: { text?: string; maxWidth?: string;}) { + const textRef = useRef(null); + const [isTruncated, setIsTruncated] = useState(false); + + useEffect(() => { + if (textRef.current) { + setIsTruncated(textRef.current.scrollWidth > textRef.current.offsetWidth); + } + }, [text]); + + return ( + +
+ {text} +
+
+ ); +} + + export function AutocompleteModal({ onClose, onApply, @@ -51,9 +81,9 @@ export function AutocompleteModal({ }; const applyQuery = () => { - const vals = commaQuery ? query.split(',').map(i => i.trim()) : [query]; + const vals = commaQuery ? query.split(',').map((i) => i.trim()) : [query]; onApply(vals); - } + }; const sortedOptions = React.useMemo(() => { if (values[0] && values[0].length) { @@ -67,20 +97,20 @@ export function AutocompleteModal({ const queryBlocks = commaQuery ? query.split(',') : [query]; const blocksAmount = queryBlocks.length; - // x,y and z const queryStr = React.useMemo(() => { - let str = '' + let str = ''; queryBlocks.forEach((block, index) => { if (index === blocksAmount - 1 && blocksAmount > 1) { - str += ' and ' + str += ' and '; } - str += `"${block.trim()}"` + str += `"${block.trim()}"`; if (index < blocksAmount - 2) { - str += ', ' + str += ', '; } - }) + }); return str; - }, [query]) + }, [query]); + return (
- - +
); } +// Props interface interface Props { value: string[]; params?: any; @@ -153,6 +186,7 @@ interface Props { mapValues?: (value: string) => string; } +// AutoCompleteContainer component export function AutoCompleteContainer(props: Props) { const filterValueContainer = useRef(null); const [showValueModal, setShowValueModal] = useState(false); @@ -177,40 +211,28 @@ export function AutoCompleteContainer(props: Props) { > {!isEmpty ? ( <> -
- {props.mapValues - ? props.mapValues(props.value[0]) - : props.value[0]} -
- {props.value.length > 1 ? ( + + {props.value.length > 1 && ( <> - or - {props.value.length === 2 ? ( -
- {props.mapValues - ? props.mapValues(props.value[1]) - : props.value[1]} -
- ) : ( -
- + {props.value.length - 1} More -
+ or + + {props.value.length > 2 && ( + )} - ) : null} + )} ) : ( -
+
{props.placeholder ? props.placeholder : 'Select value(s)'}
)} @@ -226,4 +248,4 @@ export function AutoCompleteContainer(props: Props) { ) : null}
); -} +} \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index f1ad74bb1..46ce146f2 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -69,10 +69,10 @@ function FilterItem(props: Props) { return (
-
+
{!isFilter && !hideIndex && filterIndex >= 0 && (
+ className="flex-shrink-0 w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-lighter mr-2"> {filterIndex + 1}
)} @@ -157,7 +157,7 @@ function FilterItem(props: Props) { type="text" onClick={props.onRemoveFilter} size="small" - className='btn-remove-step' + className='btn-remove-step mt-2' > diff --git a/frontend/app/components/shared/Filters/FilterList/EventsOrder.tsx b/frontend/app/components/shared/Filters/FilterList/EventsOrder.tsx index bf6ec8ef9..619f3384d 100644 --- a/frontend/app/components/shared/Filters/FilterList/EventsOrder.tsx +++ b/frontend/app/components/shared/Filters/FilterList/EventsOrder.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; -import { Tooltip } from 'UI'; -import { Dropdown, Button } from 'antd'; +import { Dropdown, Button, Tooltip } from 'antd'; const EventsOrder = observer( (props: { onChange: (e: any, v: any) => void; filter: any }) => { @@ -38,17 +37,17 @@ const EventsOrder = observer( title="Select the operator to be applied between events." placement="bottom" > -
Events Order
+
Events Order
- +
); diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index 3c329fee8..2fb928b9c 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -49,7 +49,7 @@ export const FilterList = observer((props: Props) => {
{ borderTopRightRadius: props.mergeUp ? 0 : undefined, }} > -
+
Filters
{ return (
{ pointerEvents: 'unset', paddingTop: hoveredItem.i === filterIndex && hoveredItem.position === 'top' - ? '1.5rem' - : '0.5rem', + ? '' + : '', paddingBottom: hoveredItem.i === filterIndex && hoveredItem.position === 'bottom' - ? '1.5rem' - : '0.5rem', + ? '' + : '', marginLeft: '-1rem', width: 'calc(100% + 2rem)', alignItems: 'start', @@ -271,7 +271,7 @@ export const EventsList = observer((props: Props) => { {!!props.onFilterMove && eventsNum > 1 ? (
diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index e7c302ee3..77bed5ccb 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -41,7 +41,7 @@ function FilterSelection(props: Props) { const label = filter?.category === 'Issue' ? 'Issue' : filter?.label; return ( -
+
{ diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 0e27a45be..22ce8433e 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -178,9 +178,9 @@ function FilterValue(props: Props) { return (
{renderValueFiled(filter.value)} diff --git a/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx b/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx index c7db4a3bd..3ffa358a2 100644 --- a/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx +++ b/frontend/app/components/shared/Filters/SubFilterItem/SubFilterItem.tsx @@ -18,7 +18,7 @@ export default function SubFilterItem(props: Props) { } return ( -
+
{filter.label}
({ return (