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 { formatTimeOrDate } from 'App/date';
|
||||
import { Button, Table } from 'antd';
|
||||
import type { TableProps } from 'antd';
|
||||
import React from 'react';
|
||||
import CustomTooltip from "../CustomChartTooltip";
|
||||
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
import { Styles } from '../../common';
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
|
|
@ -16,7 +11,7 @@ import {
|
|||
Line,
|
||||
Legend,
|
||||
} from 'recharts';
|
||||
import cn from 'classnames';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
|
|
@ -34,7 +29,6 @@ function CustomMetricLineChart(props: Props) {
|
|||
const {
|
||||
data = { chart: [], namesMap: [] },
|
||||
compData = { chart: [], namesMap: [] },
|
||||
params,
|
||||
colors,
|
||||
onClick = () => null,
|
||||
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 { useStore } from 'App/mstore';
|
||||
import WidgetWrapperNew from 'Components/Dashboard/components/WidgetWrapper/WidgetWrapperNew';
|
||||
import { Loader } from 'UI';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import AddCardSection from '../AddCardSection/AddCardSection';
|
||||
import cn from 'classnames';
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ function WidgetChart(props: Props) {
|
|||
const { isSaved = false, metric, isTemplate } = props;
|
||||
const { dashboardStore, metricStore } = useStore();
|
||||
const _metric: any = metricStore.instance;
|
||||
const data = _metric.data;
|
||||
const period = dashboardStore.period;
|
||||
const drillDownPeriod = dashboardStore.drillDownPeriod;
|
||||
const drillDownFilter = dashboardStore.drillDownFilter;
|
||||
|
|
@ -61,7 +62,6 @@ function WidgetChart(props: Props) {
|
|||
const metricParams = _metric.params;
|
||||
const prevMetricRef = useRef<any>();
|
||||
const isMounted = useIsMounted();
|
||||
const [data, setData] = useState<any>(metric.data);
|
||||
const [compData, setCompData] = useState<any>(null);
|
||||
const [enabledRows, setEnabledRows] = useState([]);
|
||||
const isTableWidget =
|
||||
|
|
@ -134,10 +134,7 @@ function WidgetChart(props: Props) {
|
|||
dashboardStore
|
||||
.fetchMetricChartData(metric, payload, isSaved, period, isComparison)
|
||||
.then((res: any) => {
|
||||
if (isMounted()) {
|
||||
if (isComparison) setCompData(res);
|
||||
else setData(res);
|
||||
}
|
||||
if (isComparison) setCompData(res);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
|
@ -210,13 +207,12 @@ function WidgetChart(props: Props) {
|
|||
]);
|
||||
useEffect(loadPage, [_metric.page]);
|
||||
|
||||
const renderChart = () => {
|
||||
const renderChart = React.useCallback(() => {
|
||||
const { metricType, metricOf } = metric;
|
||||
const viewType = metric.viewType;
|
||||
const metricWithData = { ...metric, data };
|
||||
|
||||
if (metricType === FUNNEL) {
|
||||
console.log(data, compData);
|
||||
if (viewType === 'table') {
|
||||
return (
|
||||
<FunnelTable data={data} compData={compData} />
|
||||
|
|
@ -503,7 +499,7 @@ function WidgetChart(props: Props) {
|
|||
|
||||
console.log('Unknown metric type', metricType, viewType);
|
||||
return <div>Unknown metric type</div>;
|
||||
};
|
||||
}, [data, compData, enabledRows, metric]);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
|
|
|
|||
|
|
@ -1,98 +1,103 @@
|
|||
import {useHistory} from "react-router";
|
||||
import {useStore} from "App/mstore";
|
||||
import {useObserver} from "mobx-react-lite";
|
||||
import {Button, Dropdown, MenuProps, message, Modal} from "antd";
|
||||
import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react";
|
||||
import {toast} from "react-toastify";
|
||||
import React from "react";
|
||||
import {useModal} from "Components/ModalContext";
|
||||
import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal";
|
||||
import { useHistory } from 'react-router';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Button, Dropdown, MenuProps, message, Modal } from 'antd';
|
||||
import { BellIcon, EllipsisVertical, TrashIcon } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import React from 'react';
|
||||
import { useModal } from 'Components/ModalContext';
|
||||
import AlertFormModal from 'Components/Alerts/AlertFormModal/AlertFormModal';
|
||||
|
||||
const CardViewMenu = () => {
|
||||
const history = useHistory();
|
||||
const {alertsStore, dashboardStore, metricStore} = useStore();
|
||||
const widget = useObserver(() => metricStore.instance);
|
||||
const {openModal, closeModal} = useModal();
|
||||
const history = useHistory();
|
||||
const { alertsStore, metricStore } = useStore();
|
||||
const widget = metricStore.instance;
|
||||
const { openModal, closeModal } = useModal();
|
||||
|
||||
const showAlertModal = () => {
|
||||
const seriesId = widget.series[0] && widget.series[0].seriesId || '';
|
||||
alertsStore.init({query: {left: seriesId}})
|
||||
openModal(<AlertFormModal
|
||||
onClose={closeModal}
|
||||
/>, {
|
||||
// title: 'Set Alerts',
|
||||
placement: 'right',
|
||||
width: 620,
|
||||
const showAlertModal = () => {
|
||||
const seriesId = (widget.series[0] && widget.series[0].seriesId) || '';
|
||||
alertsStore.init({ query: { left: seriesId } });
|
||||
openModal(<AlertFormModal onClose={closeModal} />, {
|
||||
// title: 'Set Alerts',
|
||||
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'] = [
|
||||
{
|
||||
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 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');
|
||||
});
|
||||
},
|
||||
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');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Dropdown menu={{items}}>
|
||||
<Button icon={<EllipsisVertical size={16}/>}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Dropdown menu={{ items }}>
|
||||
<Button icon={<EllipsisVertical size={16} />} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardViewMenu;
|
||||
export default observer(CardViewMenu);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useStore } from 'App/mstore';
|
|||
import { Loader, NoContent } from 'UI';
|
||||
import WidgetPreview from '../WidgetPreview';
|
||||
import WidgetSessions from '../WidgetSessions';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { dashboardMetricDetails, metricDetails, withSiteId } from 'App/routes';
|
||||
import Breadcrumb from 'Shared/Breadcrumb';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
|
|
@ -16,7 +16,7 @@ import {
|
|||
FUNNEL,
|
||||
INSIGHTS,
|
||||
USER_PATH,
|
||||
RETENTION
|
||||
RETENTION,
|
||||
} from 'App/constants/card';
|
||||
import CardUserList from '../CardUserList/CardUserList';
|
||||
import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader';
|
||||
|
|
@ -34,16 +34,16 @@ interface Props {
|
|||
function WidgetView(props: Props) {
|
||||
const {
|
||||
match: {
|
||||
params: { siteId, dashboardId, metricId }
|
||||
}
|
||||
params: { siteId, dashboardId, metricId },
|
||||
},
|
||||
} = props;
|
||||
const { metricStore, dashboardStore } = useStore();
|
||||
const widget = useObserver(() => metricStore.instance);
|
||||
const loading = useObserver(() => metricStore.isLoading);
|
||||
const widget = metricStore.instance;
|
||||
const loading = metricStore.isLoading;
|
||||
const [expanded, setExpanded] = useState(!metricId || metricId === 'create');
|
||||
const hasChanged = useObserver(() => widget.hasChanged);
|
||||
const dashboards = useObserver(() => dashboardStore.dashboards);
|
||||
const dashboard = useObserver(() => dashboards.find((d: any) => d.dashboardId == dashboardId));
|
||||
const hasChanged = widget.hasChanged;
|
||||
const dashboards = dashboardStore.dashboards;
|
||||
const dashboard = dashboards.find((d: any) => d.dashboardId == dashboardId);
|
||||
const dashboardName = dashboard ? dashboard.name : null;
|
||||
const [metricNotFound, setMetricNotFound] = useState(false);
|
||||
const history = useHistory();
|
||||
|
|
@ -83,24 +83,32 @@ function WidgetView(props: Props) {
|
|||
if (wasCreating) {
|
||||
if (parseInt(dashboardId, 10) > 0) {
|
||||
history.replace(
|
||||
withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)
|
||||
withSiteId(
|
||||
dashboardMetricDetails(dashboardId, savedMetric.metricId),
|
||||
siteId
|
||||
)
|
||||
);
|
||||
void dashboardStore.addWidgetToDashboard(
|
||||
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
|
||||
[savedMetric.metricId]
|
||||
);
|
||||
} else {
|
||||
history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId));
|
||||
history.replace(
|
||||
withSiteId(metricDetails(savedMetric.metricId), siteId)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<Loader loading={loading}>
|
||||
<Prompt
|
||||
when={hasChanged}
|
||||
message={(location: any) => {
|
||||
if (location.pathname.includes('/metrics/') || location.pathname.includes('/metric/')) {
|
||||
if (
|
||||
location.pathname.includes('/metrics/') ||
|
||||
location.pathname.includes('/metric/')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return 'You have unsaved changes. Are you sure you want to leave?';
|
||||
|
|
@ -112,9 +120,11 @@ function WidgetView(props: Props) {
|
|||
items={[
|
||||
{
|
||||
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
|
||||
|
|
@ -131,21 +141,22 @@ function WidgetView(props: Props) {
|
|||
<WidgetFormNew />
|
||||
<WidgetPreview name={widget.name} isEditing={expanded} />
|
||||
|
||||
{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) ?
|
||||
<WidgetSessions /> : null
|
||||
)}
|
||||
{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 ? (
|
||||
<WidgetSessions />
|
||||
) : null)}
|
||||
{widget.metricType === RETENTION && <CardUserList />}
|
||||
</Space>
|
||||
</NoContent>
|
||||
</div>
|
||||
</Loader>
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetView;
|
||||
export default observer(WidgetView);
|
||||
|
|
|
|||
|
|
@ -160,7 +160,17 @@ export default class MetricStore {
|
|||
}
|
||||
|
||||
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 = obj.series.slice(0, 1);
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ export default class Widget {
|
|||
}
|
||||
|
||||
update(data: any) {
|
||||
console.log(this.data, data.data)
|
||||
runInAction(() => {
|
||||
Object.assign(this, data);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue