ui: check chart/widget components for crashes
This commit is contained in:
parent
8a7570549c
commit
0743a4fd17
7 changed files with 150 additions and 134 deletions
|
|
@ -1,10 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { formatTimeOrDate } from 'App/date';
|
|
||||||
import { Button, Table } from 'antd';
|
|
||||||
import type { TableProps } from 'antd';
|
|
||||||
import CustomTooltip from "../CustomChartTooltip";
|
import CustomTooltip from "../CustomChartTooltip";
|
||||||
|
|
||||||
import { Eye, EyeOff } from 'lucide-react';
|
|
||||||
import { Styles } from '../../common';
|
import { Styles } from '../../common';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
|
|
@ -16,7 +11,7 @@ import {
|
||||||
Line,
|
Line,
|
||||||
Legend,
|
Legend,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import cn from 'classnames';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: any;
|
data: any;
|
||||||
|
|
@ -34,7 +29,6 @@ function CustomMetricLineChart(props: Props) {
|
||||||
const {
|
const {
|
||||||
data = { chart: [], namesMap: [] },
|
data = { chart: [], namesMap: [] },
|
||||||
compData = { chart: [], namesMap: [] },
|
compData = { chart: [], namesMap: [] },
|
||||||
params,
|
|
||||||
colors,
|
colors,
|
||||||
onClick = () => null,
|
onClick = () => null,
|
||||||
yaxis = { ...Styles.yaxis },
|
yaxis = { ...Styles.yaxis },
|
||||||
|
|
@ -117,4 +111,4 @@ function CustomMetricLineChart(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomMetricLineChart;
|
export default observer(CustomMetricLineChart);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useStore } from 'App/mstore';
|
import { useStore } from 'App/mstore';
|
||||||
import WidgetWrapperNew from 'Components/Dashboard/components/WidgetWrapper/WidgetWrapperNew';
|
import WidgetWrapperNew from 'Components/Dashboard/components/WidgetWrapper/WidgetWrapperNew';
|
||||||
import { Loader } from 'UI';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import AddCardSection from '../AddCardSection/AddCardSection';
|
import AddCardSection from '../AddCardSection/AddCardSection';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ function WidgetChart(props: Props) {
|
||||||
const { isSaved = false, metric, isTemplate } = props;
|
const { isSaved = false, metric, isTemplate } = props;
|
||||||
const { dashboardStore, metricStore } = useStore();
|
const { dashboardStore, metricStore } = useStore();
|
||||||
const _metric: any = metricStore.instance;
|
const _metric: any = metricStore.instance;
|
||||||
|
const data = _metric.data;
|
||||||
const period = dashboardStore.period;
|
const period = dashboardStore.period;
|
||||||
const drillDownPeriod = dashboardStore.drillDownPeriod;
|
const drillDownPeriod = dashboardStore.drillDownPeriod;
|
||||||
const drillDownFilter = dashboardStore.drillDownFilter;
|
const drillDownFilter = dashboardStore.drillDownFilter;
|
||||||
|
|
@ -61,7 +62,6 @@ function WidgetChart(props: Props) {
|
||||||
const metricParams = _metric.params;
|
const metricParams = _metric.params;
|
||||||
const prevMetricRef = useRef<any>();
|
const prevMetricRef = useRef<any>();
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const [data, setData] = useState<any>(metric.data);
|
|
||||||
const [compData, setCompData] = useState<any>(null);
|
const [compData, setCompData] = useState<any>(null);
|
||||||
const [enabledRows, setEnabledRows] = useState([]);
|
const [enabledRows, setEnabledRows] = useState([]);
|
||||||
const isTableWidget =
|
const isTableWidget =
|
||||||
|
|
@ -134,10 +134,7 @@ function WidgetChart(props: Props) {
|
||||||
dashboardStore
|
dashboardStore
|
||||||
.fetchMetricChartData(metric, payload, isSaved, period, isComparison)
|
.fetchMetricChartData(metric, payload, isSaved, period, isComparison)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
if (isMounted()) {
|
if (isComparison) setCompData(res);
|
||||||
if (isComparison) setCompData(res);
|
|
||||||
else setData(res);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -210,13 +207,12 @@ function WidgetChart(props: Props) {
|
||||||
]);
|
]);
|
||||||
useEffect(loadPage, [_metric.page]);
|
useEffect(loadPage, [_metric.page]);
|
||||||
|
|
||||||
const renderChart = () => {
|
const renderChart = React.useCallback(() => {
|
||||||
const { metricType, metricOf } = metric;
|
const { metricType, metricOf } = metric;
|
||||||
const viewType = metric.viewType;
|
const viewType = metric.viewType;
|
||||||
const metricWithData = { ...metric, data };
|
const metricWithData = { ...metric, data };
|
||||||
|
|
||||||
if (metricType === FUNNEL) {
|
if (metricType === FUNNEL) {
|
||||||
console.log(data, compData);
|
|
||||||
if (viewType === 'table') {
|
if (viewType === 'table') {
|
||||||
return (
|
return (
|
||||||
<FunnelTable data={data} compData={compData} />
|
<FunnelTable data={data} compData={compData} />
|
||||||
|
|
@ -503,7 +499,7 @@ function WidgetChart(props: Props) {
|
||||||
|
|
||||||
console.log('Unknown metric type', metricType, viewType);
|
console.log('Unknown metric type', metricType, viewType);
|
||||||
return <div>Unknown metric type</div>;
|
return <div>Unknown metric type</div>;
|
||||||
};
|
}, [data, compData, enabledRows, metric]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,103 @@
|
||||||
import {useHistory} from "react-router";
|
import { useHistory } from 'react-router';
|
||||||
import {useStore} from "App/mstore";
|
import { useStore } from 'App/mstore';
|
||||||
import {useObserver} from "mobx-react-lite";
|
import { observer } from 'mobx-react-lite';
|
||||||
import {Button, Dropdown, MenuProps, message, Modal} from "antd";
|
import { Button, Dropdown, MenuProps, message, Modal } from 'antd';
|
||||||
import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react";
|
import { BellIcon, EllipsisVertical, TrashIcon } from 'lucide-react';
|
||||||
import {toast} from "react-toastify";
|
import { toast } from 'react-toastify';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {useModal} from "Components/ModalContext";
|
import { useModal } from 'Components/ModalContext';
|
||||||
import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal";
|
import AlertFormModal from 'Components/Alerts/AlertFormModal/AlertFormModal';
|
||||||
|
|
||||||
const CardViewMenu = () => {
|
const CardViewMenu = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const {alertsStore, dashboardStore, metricStore} = useStore();
|
const { alertsStore, metricStore } = useStore();
|
||||||
const widget = useObserver(() => metricStore.instance);
|
const widget = metricStore.instance;
|
||||||
const {openModal, closeModal} = useModal();
|
const { openModal, closeModal } = useModal();
|
||||||
|
|
||||||
const showAlertModal = () => {
|
const showAlertModal = () => {
|
||||||
const seriesId = widget.series[0] && widget.series[0].seriesId || '';
|
const seriesId = (widget.series[0] && widget.series[0].seriesId) || '';
|
||||||
alertsStore.init({query: {left: seriesId}})
|
alertsStore.init({ query: { left: seriesId } });
|
||||||
openModal(<AlertFormModal
|
openModal(<AlertFormModal onClose={closeModal} />, {
|
||||||
onClose={closeModal}
|
// title: 'Set Alerts',
|
||||||
/>, {
|
placement: 'right',
|
||||||
// title: 'Set Alerts',
|
width: 620,
|
||||||
placement: 'right',
|
});
|
||||||
width: 620,
|
};
|
||||||
|
|
||||||
|
const items: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'alert',
|
||||||
|
label: 'Set Alerts',
|
||||||
|
icon: <BellIcon size={16} />,
|
||||||
|
disabled: !widget.exists() || widget.metricType === 'predefined',
|
||||||
|
onClick: showAlertModal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'remove',
|
||||||
|
label: 'Delete',
|
||||||
|
icon: <TrashIcon size={16} />,
|
||||||
|
onClick: () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: 'Confirm Card Deletion',
|
||||||
|
icon: null,
|
||||||
|
content:
|
||||||
|
'Are you sure you want to remove this card? This action is permanent and cannot be undone.',
|
||||||
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
|
<>
|
||||||
|
<CancelBtn />
|
||||||
|
<OkBtn />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk: () => {
|
||||||
|
metricStore
|
||||||
|
.delete(widget)
|
||||||
|
.then((r) => {
|
||||||
|
history.goBack();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error('Failed to remove card');
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const onClick: MenuProps['onClick'] = ({ key }) => {
|
||||||
{
|
if (key === 'alert') {
|
||||||
key: 'alert',
|
message.info('Set Alerts');
|
||||||
label: "Set Alerts",
|
} else if (key === 'remove') {
|
||||||
icon: <BellIcon size={16}/>,
|
Modal.confirm({
|
||||||
disabled: !widget.exists() || widget.metricType === 'predefined',
|
title: 'Are you sure you want to remove this card?',
|
||||||
onClick: showAlertModal,
|
icon: null,
|
||||||
},
|
// content: 'Bla bla ...',
|
||||||
{
|
footer: (_, { OkBtn, CancelBtn }) => (
|
||||||
key: 'remove',
|
<>
|
||||||
label: 'Delete',
|
<CancelBtn />
|
||||||
icon: <TrashIcon size={16}/>,
|
<OkBtn />
|
||||||
onClick: () => {
|
</>
|
||||||
Modal.confirm({
|
),
|
||||||
title: 'Confirm Card Deletion',
|
onOk: () => {
|
||||||
icon: null,
|
metricStore
|
||||||
content:'Are you sure you want to remove this card? This action is permanent and cannot be undone.',
|
.delete(widget)
|
||||||
footer: (_, {OkBtn, CancelBtn}) => (
|
.then((r) => {
|
||||||
<>
|
history.goBack();
|
||||||
<CancelBtn/>
|
|
||||||
<OkBtn/>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
onOk: () => {
|
|
||||||
metricStore.delete(widget).then(r => {
|
|
||||||
history.goBack();
|
|
||||||
}).catch(() => {
|
|
||||||
toast.error('Failed to remove card');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const onClick: MenuProps['onClick'] = ({key}) => {
|
|
||||||
if (key === 'alert') {
|
|
||||||
message.info('Set Alerts');
|
|
||||||
} else if (key === 'remove') {
|
|
||||||
Modal.confirm({
|
|
||||||
title: 'Are you sure you want to remove this card?',
|
|
||||||
icon: null,
|
|
||||||
// content: 'Bla bla ...',
|
|
||||||
footer: (_, {OkBtn, CancelBtn}) => (
|
|
||||||
<>
|
|
||||||
<CancelBtn/>
|
|
||||||
<OkBtn/>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
onOk: () => {
|
|
||||||
metricStore.delete(widget).then(r => {
|
|
||||||
history.goBack();
|
|
||||||
}).catch(() => {
|
|
||||||
toast.error('Failed to remove card');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
.catch(() => {
|
||||||
};
|
toast.error('Failed to remove card');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Dropdown menu={{items}}>
|
<Dropdown menu={{ items }}>
|
||||||
<Button icon={<EllipsisVertical size={16}/>}/>
|
<Button icon={<EllipsisVertical size={16} />} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardViewMenu;
|
export default observer(CardViewMenu);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useStore } from 'App/mstore';
|
||||||
import { Loader, NoContent } from 'UI';
|
import { Loader, NoContent } from 'UI';
|
||||||
import WidgetPreview from '../WidgetPreview';
|
import WidgetPreview from '../WidgetPreview';
|
||||||
import WidgetSessions from '../WidgetSessions';
|
import WidgetSessions from '../WidgetSessions';
|
||||||
import { useObserver } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { dashboardMetricDetails, metricDetails, withSiteId } from 'App/routes';
|
import { dashboardMetricDetails, metricDetails, withSiteId } from 'App/routes';
|
||||||
import Breadcrumb from 'Shared/Breadcrumb';
|
import Breadcrumb from 'Shared/Breadcrumb';
|
||||||
import { FilterKey } from 'Types/filter/filterType';
|
import { FilterKey } from 'Types/filter/filterType';
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
FUNNEL,
|
FUNNEL,
|
||||||
INSIGHTS,
|
INSIGHTS,
|
||||||
USER_PATH,
|
USER_PATH,
|
||||||
RETENTION
|
RETENTION,
|
||||||
} from 'App/constants/card';
|
} from 'App/constants/card';
|
||||||
import CardUserList from '../CardUserList/CardUserList';
|
import CardUserList from '../CardUserList/CardUserList';
|
||||||
import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader';
|
import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader';
|
||||||
|
|
@ -34,16 +34,16 @@ interface Props {
|
||||||
function WidgetView(props: Props) {
|
function WidgetView(props: Props) {
|
||||||
const {
|
const {
|
||||||
match: {
|
match: {
|
||||||
params: { siteId, dashboardId, metricId }
|
params: { siteId, dashboardId, metricId },
|
||||||
}
|
},
|
||||||
} = props;
|
} = props;
|
||||||
const { metricStore, dashboardStore } = useStore();
|
const { metricStore, dashboardStore } = useStore();
|
||||||
const widget = useObserver(() => metricStore.instance);
|
const widget = metricStore.instance;
|
||||||
const loading = useObserver(() => metricStore.isLoading);
|
const loading = metricStore.isLoading;
|
||||||
const [expanded, setExpanded] = useState(!metricId || metricId === 'create');
|
const [expanded, setExpanded] = useState(!metricId || metricId === 'create');
|
||||||
const hasChanged = useObserver(() => widget.hasChanged);
|
const hasChanged = widget.hasChanged;
|
||||||
const dashboards = useObserver(() => dashboardStore.dashboards);
|
const dashboards = dashboardStore.dashboards;
|
||||||
const dashboard = useObserver(() => dashboards.find((d: any) => d.dashboardId == dashboardId));
|
const dashboard = dashboards.find((d: any) => d.dashboardId == dashboardId);
|
||||||
const dashboardName = dashboard ? dashboard.name : null;
|
const dashboardName = dashboard ? dashboard.name : null;
|
||||||
const [metricNotFound, setMetricNotFound] = useState(false);
|
const [metricNotFound, setMetricNotFound] = useState(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
@ -83,24 +83,32 @@ function WidgetView(props: Props) {
|
||||||
if (wasCreating) {
|
if (wasCreating) {
|
||||||
if (parseInt(dashboardId, 10) > 0) {
|
if (parseInt(dashboardId, 10) > 0) {
|
||||||
history.replace(
|
history.replace(
|
||||||
withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)
|
withSiteId(
|
||||||
|
dashboardMetricDetails(dashboardId, savedMetric.metricId),
|
||||||
|
siteId
|
||||||
|
)
|
||||||
);
|
);
|
||||||
void dashboardStore.addWidgetToDashboard(
|
void dashboardStore.addWidgetToDashboard(
|
||||||
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
|
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
|
||||||
[savedMetric.metricId]
|
[savedMetric.metricId]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId));
|
history.replace(
|
||||||
|
withSiteId(metricDetails(savedMetric.metricId), siteId)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return useObserver(() => (
|
return (
|
||||||
<Loader loading={loading}>
|
<Loader loading={loading}>
|
||||||
<Prompt
|
<Prompt
|
||||||
when={hasChanged}
|
when={hasChanged}
|
||||||
message={(location: any) => {
|
message={(location: any) => {
|
||||||
if (location.pathname.includes('/metrics/') || location.pathname.includes('/metric/')) {
|
if (
|
||||||
|
location.pathname.includes('/metrics/') ||
|
||||||
|
location.pathname.includes('/metric/')
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return 'You have unsaved changes. Are you sure you want to leave?';
|
return 'You have unsaved changes. Are you sure you want to leave?';
|
||||||
|
|
@ -112,9 +120,11 @@ function WidgetView(props: Props) {
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: dashboardName ? dashboardName : 'Cards',
|
label: dashboardName ? dashboardName : 'Cards',
|
||||||
to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId)
|
to: dashboardId
|
||||||
|
? withSiteId('/dashboard/' + dashboardId, siteId)
|
||||||
|
: withSiteId('/metrics', siteId),
|
||||||
},
|
},
|
||||||
{ label: widget.name }
|
{ label: widget.name },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<NoContent
|
<NoContent
|
||||||
|
|
@ -131,21 +141,22 @@ function WidgetView(props: Props) {
|
||||||
<WidgetFormNew />
|
<WidgetFormNew />
|
||||||
<WidgetPreview name={widget.name} isEditing={expanded} />
|
<WidgetPreview name={widget.name} isEditing={expanded} />
|
||||||
|
|
||||||
{widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
|
{widget.metricOf !== FilterKey.SESSIONS &&
|
||||||
(widget.metricType === TABLE
|
widget.metricOf !== FilterKey.ERRORS &&
|
||||||
|| widget.metricType === TIMESERIES
|
(widget.metricType === TABLE ||
|
||||||
|| widget.metricType === HEATMAP
|
widget.metricType === TIMESERIES ||
|
||||||
|| widget.metricType === INSIGHTS
|
widget.metricType === HEATMAP ||
|
||||||
|| widget.metricType === FUNNEL
|
widget.metricType === INSIGHTS ||
|
||||||
|| widget.metricType === USER_PATH) ?
|
widget.metricType === FUNNEL ||
|
||||||
<WidgetSessions /> : null
|
widget.metricType === USER_PATH ? (
|
||||||
)}
|
<WidgetSessions />
|
||||||
|
) : null)}
|
||||||
{widget.metricType === RETENTION && <CardUserList />}
|
{widget.metricType === RETENTION && <CardUserList />}
|
||||||
</Space>
|
</Space>
|
||||||
</NoContent>
|
</NoContent>
|
||||||
</div>
|
</div>
|
||||||
</Loader>
|
</Loader>
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WidgetView;
|
export default observer(WidgetView);
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,17 @@ export default class MetricStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
changeType(value: string) {
|
changeType(value: string) {
|
||||||
const obj: any = { metricType: value };
|
const defaultData = {
|
||||||
|
sessionId: '',
|
||||||
|
sessions: [],
|
||||||
|
issues: [],
|
||||||
|
total: 0,
|
||||||
|
chart: [],
|
||||||
|
namesMap: {},
|
||||||
|
avg: 0,
|
||||||
|
percentiles: []
|
||||||
|
};
|
||||||
|
const obj: any = { metricType: value, data: defaultData };
|
||||||
obj.series = this.instance.series;
|
obj.series = this.instance.series;
|
||||||
|
|
||||||
obj.series = obj.series.slice(0, 1);
|
obj.series = obj.series.slice(0, 1);
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,7 @@ export default class Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: any) {
|
update(data: any) {
|
||||||
|
console.log(this.data, data.data)
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue