From 34a51adfc2e524c41733f6bfed668208caef3f0d Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 13 Dec 2024 12:04:00 +0100 Subject: [PATCH] ui: fix crashes related to metric type changes --- .../ClickMapCard/ClickMapCard.tsx | 2 +- .../AddCardSection/AddCardSection.tsx | 7 +- .../DashboardWidgetGrid.tsx | 1 + .../components/MetricTypeSelector.tsx | 59 +++++++++++--- .../components/WidgetChart/WidgetChart.tsx | 78 +++++++++---------- frontend/app/mstore/metricStore.ts | 4 +- frontend/app/mstore/types/widget.ts | 19 +++-- 7 files changed, 112 insertions(+), 58 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx index 72eb6a791..e857b2520 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -15,7 +15,7 @@ function ClickMapCard() { const sessionId = metricStore.instance.data.sessionId; const url = metricStore.instance.data.path; - const operator = metricStore.instance.series[0].filter.filters[0].operator + const operator = metricStore.instance.series[0]?.filter.filters[0]?.operator ? metricStore.instance.series[0].filter.filters[0].operator : 'startsWith' React.useEffect(() => { diff --git a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx index aac378cf1..5e4feb6ce 100644 --- a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx @@ -150,6 +150,7 @@ function CategoryTab({ tab, inCards }: { tab: string; inCards?: boolean }) { metricType: selectedCard.cardType, name: selectedCard.title, metricOf: selectedCard.metricOf, + category: card, }; if (selectedCard.filters) { @@ -166,12 +167,14 @@ function CategoryTab({ tab, inCards }: { tab: string; inCards?: boolean }) { // TODO This code here makes 0 sense if (selectedCard.cardType === FUNNEL) { cardData.series = []; - cardData.series.filter = []; + cardData.series.push(new FilterSeries()); + cardData.series[0].filter.addFunnelDefaultFilters(); + cardData.series[0].filter.eventsOrder = 'then'; + cardData.series[0].filter.eventsOrderSupport = ['then']; } metricStore.setCardCategory(tab); metricStore.merge(cardData); - metricStore.instance.resetDefaults(); if (projectsStore.activeSiteId) { if (inCards) { diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index ed241e8b5..d2961db0d 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -6,6 +6,7 @@ import AddCardSection from '../AddCardSection/AddCardSection'; import cn from 'classnames'; import { Button, Popover } from 'antd' import { PlusOutlined } from '@ant-design/icons' +import { Loader } from 'UI'; interface Props { siteId: string; diff --git a/frontend/app/components/Dashboard/components/MetricTypeSelector.tsx b/frontend/app/components/Dashboard/components/MetricTypeSelector.tsx index f8b58195e..e2f853c45 100644 --- a/frontend/app/components/Dashboard/components/MetricTypeSelector.tsx +++ b/frontend/app/components/Dashboard/components/MetricTypeSelector.tsx @@ -6,9 +6,12 @@ import { tabItems } from 'Components/Dashboard/components/AddCardSection/AddCard import { CARD_LIST, } from 'Components/Dashboard/components/DashboardList/NewDashModal/ExampleCards'; +import FilterSeries from "App/mstore/types/filterSeries"; +import { FUNNEL, USER_PATH } from "App/constants/card"; function MetricTypeSelector() { const { metricStore } = useStore(); + const [selectedCategory, setSelectedCategory] = React.useState(null); const metric = metricStore.instance; const cardCategory = metricStore.cardCategory; if (!cardCategory) { @@ -18,22 +21,60 @@ function MetricTypeSelector() { value: opt.type, icon: opt.icon, })) + React.useEffect(() => { + const selected = metric.category ? { value: metric.category } : options.find( + (i) => { + if (metric.metricType === 'table') { + return i.value === metric.metricOf; + } + return i.value === metric.metricType + }) + if (selected) { + setSelectedCategory(selected.value); + } + }, []) + const onChange = (type: string) => { const selectedCard = CARD_LIST.find((i) => i.key === type); + if (selectedCard) { - metricStore.changeType(selectedCard.cardType, selectedCard.metricOf); + setSelectedCategory(type); + metricStore.init(); + const cardData: Record = { + metricType: selectedCard.cardType, + name: selectedCard.title, + metricOf: selectedCard.metricOf, + series: [new FilterSeries()], + category: type, + } + if (selectedCard.filters) { + cardData.series = [ + new FilterSeries().fromJson({ + name: "Series 1", + filter: { + filters: selectedCard.filters, + } + }) + ]; + } + if (selectedCard.cardType === USER_PATH) { + cardData.series = []; + cardData.series.push(new FilterSeries()); + } + if (selectedCard.cardType === FUNNEL) { + cardData.series = []; + cardData.series.push(new FilterSeries()); + cardData.series[0].filter.addFunnelDefaultFilters(); + cardData.series[0].filter.eventsOrder = 'then'; + cardData.series[0].filter.eventsOrderSupport = ['then']; + } + + metricStore.merge(cardData); } }; - const selected = options.find( - (i) => { - if (metric.metricType === 'table') { - return i.value === metric.metricOf; - } - return i.value === metric.metricType - }) || options[0]; return ( - + ); } diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index a52c98e75..a9124efdb 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -51,7 +51,7 @@ function WidgetChart(props: Props) { }); const { isSaved = false, metric, isTemplate } = props; const { dashboardStore, metricStore } = useStore(); - const _metric: any = metricStore.instance; + const _metric: any = props.isPreview ? metricStore.instance : props.metric; const data = _metric.data; const period = dashboardStore.period; const drillDownPeriod = dashboardStore.drillDownPeriod; @@ -65,9 +65,9 @@ function WidgetChart(props: Props) { const [compData, setCompData] = useState(null); const [enabledRows, setEnabledRows] = useState([]); const isTableWidget = - metric.metricType === 'table' && metric.viewType === 'table'; + _metric.metricType === 'table' && _metric.viewType === 'table'; const isPieChart = - metric.metricType === 'table' && metric.viewType === 'pieChart'; + _metric.metricType === 'table' && _metric.viewType === 'pieChart'; useEffect(() => { return () => { @@ -147,17 +147,17 @@ function WidgetChart(props: Props) { ); const loadPage = () => { if (!inView) return; - if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) { - prevMetricRef.current = metric; + if (prevMetricRef.current && prevMetricRef.current.name !== _metric.name) { + prevMetricRef.current = _metric; return; } - prevMetricRef.current = metric; + prevMetricRef.current = _metric; const timestmaps = drillDownPeriod.toTimestamps(); const payload = isSaved ? { ...metricParams } - : { ...params, ...timestmaps, ...metric.toJson() }; + : { ...params, ...timestmaps, ..._metric.toJson() }; debounceRequest( - metric, + _metric, payload, isSaved, !isSaved ? drillDownPeriod : period @@ -172,11 +172,11 @@ function WidgetChart(props: Props) { const payload = { ...params, ...timestamps, - ...metric.toJson(), + ..._metric.toJson(), viewType: 'lineChart', }; fetchMetricChartData( - metric, + _metric, payload, isSaved, dashboardStore.comparisonPeriod, @@ -198,19 +198,19 @@ function WidgetChart(props: Props) { period, depsString, dashboardStore.selectedDensity, - metric.metricType, - metric.metricOf, - metric.metricValue, - metric.startType, - metric.metricFormat, + _metric.metricType, + _metric.metricOf, + _metric.metricValue, + _metric.startType, + _metric.metricFormat, inView, ]); useEffect(loadPage, [_metric.page]); const renderChart = React.useCallback(() => { - const { metricType, metricOf } = metric; - const viewType = metric.viewType; - const metricWithData = { ...metric, data }; + const { metricType, metricOf } = _metric; + const viewType = _metric.viewType; + const metricWithData = { ..._metric, data }; if (metricType === FUNNEL) { if (viewType === 'table') { @@ -250,7 +250,7 @@ function WidgetChart(props: Props) { return ( ); } @@ -288,7 +288,7 @@ function WidgetChart(props: Props) { params={params} onClick={onChartClick} label={ - metric.metricOf === 'sessionCount' + _metric.metricOf === 'sessionCount' ? 'Number of Sessions' : 'Number of Users' } @@ -304,7 +304,7 @@ function WidgetChart(props: Props) { colors={colors} onClick={onChartClick} label={ - metric.metricOf === 'sessionCount' + _metric.metricOf === 'sessionCount' ? 'Number of Sessions' : 'Number of Users' } @@ -321,7 +321,7 @@ function WidgetChart(props: Props) { colors={colors} onClick={onChartClick} label={ - metric.metricOf === 'sessionCount' + _metric.metricOf === 'sessionCount' ? 'Number of Sessions' : 'Number of Users' } @@ -338,7 +338,7 @@ function WidgetChart(props: Props) { colors={colors} onClick={onChartClick} label={ - metric.metricOf === 'sessionCount' + _metric.metricOf === 'sessionCount' ? 'Number of Sessions' : 'Number of Users' } @@ -349,12 +349,12 @@ function WidgetChart(props: Props) { return ( - clickmap thumbnail + clickmap thumbnail ) : (
Unknown metric type
; - }, [data, compData, enabledRows, metric]); + }, [data, compData, enabledRows, _metric]); return (
@@ -506,19 +506,19 @@ function WidgetChart(props: Props) { style={{ minHeight: props.isPreview ? undefined : 240, paddingTop: - props.isPreview && metric.metricType === TIMESERIES + props.isPreview && _metric.metricType === TIMESERIES ? '1.5rem' : 0, }} > {renderChart()} - {props.isPreview && metric.metricType === TIMESERIES ? ( + {props.isPreview && _metric.metricType === TIMESERIES ? ( ) : null}
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index b72eb6c11..3598a0956 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -130,11 +130,13 @@ export default class MetricStore { merge(obj: any, updateChangeFlag: boolean = true) { const type = obj.metricType; - // handle metricType change if (obj.hasOwnProperty('metricType') && type !== this.instance.metricType) { this.instance.series.forEach((s: any, i: number) => { this.instance.series[i].filter.eventsOrderSupport = ['then', 'or', 'and'] }) + if (type === HEATMAP && 'series' in obj) { + delete obj['series'] + } this.changeType(type); } diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index b4084ceea..140db5455 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -100,6 +100,7 @@ export default class Widget { sessions: [], issues: [], total: 0, + values: [], chart: [], namesMap: {}, avg: 0, @@ -261,7 +262,6 @@ export default class Widget { } update(data: any) { - console.log(this.data, data.data) runInAction(() => { Object.assign(this, data); }); @@ -295,6 +295,7 @@ export default class Widget { } setData(data: { timestamp: number, [seriesName: string]: number}[], period: any, isComparison?: boolean) { + if (!data) return; const _data: any = {}; if (isComparison && this.metricType === TIMESERIES) { data.forEach((point, i) => { @@ -306,6 +307,10 @@ export default class Widget { }) } + if (this.metricType === HEATMAP) { + return; + } + if (this.metricType === USER_PATH) { const _data = processData(data); Object.assign(this.data, _data); @@ -342,10 +347,9 @@ export default class Widget { return unique; }, []); } else { - const updatedData: any = data; // we don't use total anymore this.calculateTotalSeries(data); - _data['chart'] = getChartFormatter(period)(updatedData); - _data['namesMap'] = Array.isArray(updatedData) - ? updatedData + _data['chart'] = getChartFormatter(period)(data); + _data['namesMap'] = Array.isArray(data) + ? data .map((i) => Object.keys(i)) .flat() .filter((i) => i !== 'time' && i !== 'timestamp') @@ -359,8 +363,11 @@ export default class Widget { } } + if (!isComparison) { - Object.assign(this.data, _data); + runInAction(() => { + Object.assign(this.data, _data); + }) } return _data; }