diff --git a/frontend/app/components/Charts/ColumnChart.tsx b/frontend/app/components/Charts/ColumnChart.tsx
index 26e5c6f58..cce550bc4 100644
--- a/frontend/app/components/Charts/ColumnChart.tsx
+++ b/frontend/app/components/Charts/ColumnChart.tsx
@@ -25,6 +25,7 @@ export interface DataProps {
interface ColumnChartProps extends DataProps {
label?: string;
+ onSeriesFocus?: (name: string) => void;
}
function ColumnChart(props: ColumnChartProps) {
@@ -80,6 +81,10 @@ function ColumnChart(props: ColumnChartProps) {
const obs = new ResizeObserver(() => chart.resize());
obs.observe(chartRef.current);
+ chart.on('click', (event) => {
+ const focusedSeriesName = event.name;
+ props.onSeriesFocus?.(focusedSeriesName);
+ })
return () => {
chart.dispose();
diff --git a/frontend/app/components/Charts/PieChart.tsx b/frontend/app/components/Charts/PieChart.tsx
index a172bee26..9675aab97 100644
--- a/frontend/app/components/Charts/PieChart.tsx
+++ b/frontend/app/components/Charts/PieChart.tsx
@@ -18,7 +18,7 @@ interface PieChartProps {
};
label?: string;
inGrid?: boolean;
- onClick?: (filters: any[]) => void;
+ onSeriesFocus?: (seriesName: string) => void;
}
function PieChart(props: PieChartProps) {
@@ -40,10 +40,10 @@ function PieChart(props: PieChartProps) {
return;
}
- const largestSlice = pieData.reduce((acc, curr) =>
- curr.value > acc.value ? curr : acc
- );
- const largestVal = largestSlice.value || 1; // avoid divide-by-zero
+ // const largestSlice = pieData.reduce((acc, curr) =>
+ // curr.value > acc.value ? curr : acc
+ // );
+ // const largestVal = largestSlice.value || 1; // avoid divide-by-zero
const option = {
...defaultOptions,
@@ -75,14 +75,14 @@ function PieChart(props: PieChartProps) {
name: d.name,
value: d.value,
label: {
- show: d.value / largestVal >= 0.03,
+ show: false, //d.value / largestVal >= 0.03,
position: 'outside',
formatter: (params: any) => {
return params.value;
},
},
labelLine: {
- show: d.value / largestVal >= 0.03,
+ show: false, // d.value / largestVal >= 0.03,
length: 10,
length2: 20,
lineStyle: { color: '#3EAAAF' },
@@ -105,7 +105,8 @@ function PieChart(props: PieChartProps) {
obs.observe(chartRef.current);
chartInstance.on('click', function (params) {
- onClick([{ name: params.name, value: params.value }]);
+ const focusedSeriesName = params.name
+ props.onSeriesFocus?.(focusedSeriesName);
});
return () => {
diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
index 1f97c0892..01d4ca2e8 100644
--- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
+++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/BigNumChart.tsx
@@ -1,19 +1,21 @@
import React from 'react'
import { CompareTag } from "./CustomChartTooltip";
+import cn from 'classnames'
interface Props {
colors: any;
- onClick?: (event, index) => void;
yaxis?: any;
label?: string;
hideLegend?: boolean;
values: { value: number, compData?: number, series: string, valueLabel?: string }[];
+ onSeriesFocus?: (name: string) => void;
}
function BigNumChart(props: Props) {
const {
colors,
label = 'Number of Sessions',
values,
+ onSeriesFocus,
} = props;
return (
@@ -26,19 +28,21 @@ function BigNumChart(props: Props) {
label={label}
compData={val.compData}
valueLabel={val.valueLabel}
+ onSeriesFocus={onSeriesFocus}
/>
))}
)
}
-function BigNum({ color, series, value, label, compData, valueLabel }: {
+function BigNum({ color, series, value, label, compData, valueLabel, onSeriesFocus }: {
color: string,
series: string,
value: number,
label: string,
compData?: number,
valueLabel?: string,
+ onSeriesFocus?: (name: string) => void
}) {
const formattedNumber = (num: number) => {
return Intl.NumberFormat().format(num);
@@ -53,7 +57,13 @@ function BigNum({ color, series, value, label, compData, valueLabel }: {
return value - compData;
}, [value, compData])
return (
-
+
onSeriesFocus?.(series)}
+ className={cn(
+ 'flex flex-col flex-auto justify-center items-center rounded-lg transition-all',
+ 'hover:transition-all ease-in-out hover:ease-in-out hover:bg-teal/5 hover:cursor-pointer'
+ )}
+ >
{series}
diff --git a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx
index 4b3002499..2158dbfac 100644
--- a/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx
+++ b/frontend/app/components/Dashboard/components/AddCardSection/AddCardSection.tsx
@@ -12,6 +12,8 @@ import {
Combine,
Users,
Sparkles,
+ Globe,
+ MonitorSmartphone,
} from 'lucide-react';
import { Icon } from 'UI';
import FilterSeries from 'App/mstore/types/filterSeries';
@@ -129,6 +131,18 @@ export const tabItems: Record
= {
type: FilterKey.USERID,
description: 'Identify the users with the most interactions.',
},
+ {
+ icon: ,
+ title: 'Top Countries',
+ type: FilterKey.LOCATION,
+ description: 'Track the geographical distribution of your audience.',
+ },
+ {
+ icon: ,
+ title: 'Top Devices',
+ type: FilterKey.USER_DEVICE,
+ description: 'Explore the devices used by your users.',
+ }
// { TODO: 1.23+ maybe
// icon: ,
// title: 'Speed Index by Country',
diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx
index 53c9b833c..bc1191654 100644
--- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx
+++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx
@@ -8,25 +8,40 @@ import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { DROPDOWN_OPTIONS } from 'App/constants/card';
+const options = [
+ {
+ key: 'all',
+ label: 'All Types',
+ },
+ ...DROPDOWN_OPTIONS.map((option) => ({
+ key: option.value,
+ label: option.label,
+ })),
+ {
+ key: 'monitors',
+ label: 'Monitors',
+ },
+ {
+ key: 'web_analytics',
+ label: 'Web Analytics',
+ },
+]
+
function MetricViewHeader() {
const { metricStore } = useStore();
const filter = metricStore.filter;
useEffect(() => {
- // Set the default sort order to 'desc'
metricStore.updateKey('sort', { by: 'desc' });
}, [metricStore]);
- // Handler for dropdown menu selection
const handleMenuClick = ({ key }) => {
metricStore.updateKey('filter', { ...filter, type: key });
};
- // Dropdown menu options
const menu = (
);
@@ -39,10 +54,7 @@ function MetricViewHeader() {
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
index 853d6ec1b..2699bf4c7 100644
--- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
@@ -214,6 +214,10 @@ function WidgetChart(props: Props) {
]);
useEffect(loadPage, [_metric.page]);
+ const onFocus = (seriesName: string)=> {
+ metricStore.setFocusedSeriesName(seriesName);
+ }
+
const renderChart = React.useCallback(() => {
const { metricType, metricOf } = _metric;
const viewType = _metric.viewType;
@@ -351,7 +355,7 @@ function WidgetChart(props: Props) {
compData={compData}
params={params}
colors={colors}
- onClick={onChartClick}
+ onSeriesFocus={onFocus}
label={
_metric.metricOf === 'sessionCount'
? 'Number of Sessions'
@@ -366,7 +370,7 @@ function WidgetChart(props: Props) {
([]);
const isMounted = useIsMounted();
const [loading, setLoading] = useState(false);
- const filteredSessions = getListSessionsBySeries(data, activeSeries);
+ // all filtering done through series now
+ const filteredSessions = getListSessionsBySeries(data, 'all');
const { dashboardStore, metricStore, sessionStore, customFieldStore } = useStore();
+ const focusedSeries = metricStore.focusedSeriesName;
const filter = dashboardStore.drillDownFilter;
const widget = metricStore.instance;
const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm');
@@ -44,15 +46,14 @@ function WidgetSessions(props: Props) {
)
}));
- const writeOption = ({ value }: any) => setActiveSeries(value.value);
useEffect(() => {
- if (!data) return;
- const seriesOptions = data.map((item: any) => ({
- label: item.seriesName,
+ if (!widget.series) return;
+ const seriesOptions = widget.series.map((item: any) => ({
+ label: item.name,
value: item.seriesId
}));
setSeriesOptions([{ label: 'All', value: 'all' }, ...seriesOptions]);
- }, [data]);
+ }, [widget.series]);
const fetchSessions = (metricId: any, filter: any) => {
if (!isMounted()) return;
@@ -99,9 +100,10 @@ function WidgetSessions(props: Props) {
};
debounceClickMapSearch(customFilter);
} else {
+ const usedSeries = focusedSeries ? widget.series.filter((s) => s.name === focusedSeries) : widget.series;
debounceRequest(widget.metricId, {
...filter,
- series: widget.series.map((s) => s.toJson()),
+ series: usedSeries.map((s) => s.toJson()),
page: metricStore.sessionsPage,
limit: metricStore.sessionsPageSize
});
@@ -116,9 +118,23 @@ function WidgetSessions(props: Props) {
filter.filters,
depsString,
metricStore.clickMapSearch,
- activeSeries
+ focusedSeries
]);
useEffect(loadData, [metricStore.sessionsPage]);
+ useEffect(() => {
+ if (activeSeries === 'all') {
+ metricStore.setFocusedSeriesName(null);
+ } else {
+ metricStore.setFocusedSeriesName(seriesOptions.find((option) => option.value === activeSeries)?.label, false);
+ }
+ }, [activeSeries])
+ useEffect(() => {
+ if (focusedSeries) {
+ setActiveSeries(seriesOptions.find((option) => option.label === focusedSeries)?.value || 'all');
+ } else {
+ setActiveSeries('all');
+ }
+ }, [focusedSeries])
const clearFilters = () => {
metricStore.updateKey('sessionsPage', 1);
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts
index 3598a0956..db2a2e2d9 100644
--- a/frontend/app/mstore/metricStore.ts
+++ b/frontend/app/mstore/metricStore.ts
@@ -18,6 +18,37 @@ import { clickmapFilter } from 'App/types/filter/newFilter';
import { getRE } from 'App/utils';
import { FilterKey } from 'Types/filter/filterType';
+const handleFilter = (card: Widget, filterType?: string) => {
+ const metricType = card.metricType;
+ if (filterType === 'all' || !filterType || !metricType) {
+ return true;
+ }
+ if ([CATEGORIES.monitors, CATEGORIES.web_analytics].includes(filterType)) {
+ if (metricType !== 'table') return false;
+ const metricOf = card.metricOf;
+ if (filterType === CATEGORIES.monitors) {
+ return [
+ FilterKey.ERRORS,
+ FilterKey.FETCH,
+ TIMESERIES + '_4xx_requests',
+ TIMESERIES + '_slow_network_requests'
+ ].includes(metricOf)
+ }
+ if (filterType === CATEGORIES.web_analytics) {
+ return [
+ FilterKey.LOCATION,
+ FilterKey.USER_BROWSER,
+ FilterKey.REFERRER,
+ FilterKey.USERID,
+ FilterKey.LOCATION,
+ FilterKey.USER_DEVICE,
+ ].includes(metricOf)
+ }
+ } else {
+ return filterType === metricType;
+ }
+}
+
const cardToCategory = (cardType: string) => {
switch (cardType) {
case TIMESERIES:
@@ -70,6 +101,8 @@ export default class MetricStore {
cardCategory: string | null = CATEGORIES.product_analytics;
+ focusedSeriesName: string | null = null;
+
constructor() {
makeAutoObservable(this);
}
@@ -89,7 +122,7 @@ export default class MetricStore {
(this.filter.showMine
? card.owner === JSON.parse(localStorage.getItem('user')!).account.email
: true) &&
- (this.filter.type === 'all' || card.metricType === this.filter.type) &&
+ handleFilter(card, this.filter.type) &&
(!dbIds.length ||
card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) &&
// @ts-ignore
@@ -105,6 +138,14 @@ export default class MetricStore {
this.instance.update(metric || new Widget());
}
+ setFocusedSeriesName(name: string | null, resetOnSame = true) {
+ if (this.focusedSeriesName === name && resetOnSame) {
+ this.focusedSeriesName = null;
+ } else {
+ this.focusedSeriesName = name;
+ }
+ }
+
setCardCategory(category: string) {
this.cardCategory = category;
}