fix(ui) - dashboard date range selection reload, metric not found message

This commit is contained in:
Shekar Siri 2022-07-04 15:58:10 +02:00
parent 13ff1fd621
commit 36258e58c9
7 changed files with 85 additions and 86 deletions

View file

@ -4,7 +4,7 @@ import CustomMetricPercentage from 'App/components/Dashboard/Widgets/CustomMetri
import CustomMetricTable from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable';
import CustomMetricPieChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart';
import { Styles } from 'App/components/Dashboard/Widgets/common';
import { observer } from 'mobx-react-lite';
import { observer, useObserver } from 'mobx-react-lite';
import { Loader } from 'UI';
import { useStore } from 'App/mstore';
import WidgetPredefinedChart from '../WidgetPredefinedChart';
@ -28,8 +28,8 @@ function WidgetChart(props: Props) {
const { isWidget = false, metric, isTemplate } = props;
const { dashboardStore, metricStore } = useStore();
const _metric: any = metricStore.instance;
const period = dashboardStore.period;
const drillDownPeriod = dashboardStore.drillDownPeriod;
const period = useObserver(() => dashboardStore.period);
const drillDownPeriod = useObserver(() => dashboardStore.drillDownPeriod);
const drillDownFilter = dashboardStore.drillDownFilter;
const colors = Styles.customMetricColors;
const [loading, setLoading] = useState(true)
@ -66,10 +66,10 @@ function WidgetChart(props: Props) {
}
const depsString = JSON.stringify(_metric.series);
const fetchMetricChartData = (metric: any, payload: any, isWidget: any) => {
const fetchMetricChartData = (metric: any, payload: any, isWidget: any, period: any) => {
if (!isMounted()) return;
setLoading(true)
dashboardStore.fetchMetricChartData(metric, payload, isWidget).then((res: any) => {
dashboardStore.fetchMetricChartData(metric, payload, isWidget, period).then((res: any) => {
if (isMounted()) setData(res);
}).finally(() => {
setLoading(false);
@ -85,7 +85,7 @@ function WidgetChart(props: Props) {
prevMetricRef.current = metric;
const timestmaps = drillDownPeriod.toTimestamps();
const payload = isWidget ? { ...params } : { ...metricParams, ...timestmaps, ...metric.toJson() };
debounceRequest(metric, payload, isWidget);
debounceRequest(metric, payload, isWidget, !isWidget ? drillDownPeriod : period);
}, [drillDownPeriod, period, depsString, _metric.page, metric.metricType, metric.metricOf, metric.viewType]);

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useStore } from 'App/mstore';
import cn from 'classnames'
import { Icon, Loader } from 'UI';
import cn from 'classnames';
import { Icon, Loader, NoContent } from 'UI';
import WidgetForm from '../WidgetForm';
import WidgetPreview from '../WidgetPreview';
import WidgetSessions from '../WidgetSessions';
@ -11,41 +11,51 @@ import { withSiteId } from 'App/routes';
import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues';
import Breadcrumb from 'Shared/Breadcrumb';
import { FilterKey } from 'Types/filter/filterType';
import { Prompt } from 'react-router'
import { Prompt } from 'react-router';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
interface Props {
history: any;
match: any
siteId: any
match: any;
siteId: any;
}
function WidgetView(props: Props) {
const { match: { params: { siteId, dashboardId, metricId } } } = props;
const {
match: {
params: { siteId, dashboardId, metricId },
},
} = props;
const { metricStore, dashboardStore } = useStore();
const widget = useObserver(() => metricStore.instance);
const loading = useObserver(() => metricStore.isLoading);
const [expanded, setExpanded] = useState(!metricId || metricId === 'create');
const hasChanged = useObserver(() => widget.hasChanged)
const hasChanged = useObserver(() => widget.hasChanged);
const dashboards = useObserver(() => dashboardStore.dashboards);
const dashboard = useObserver(() => dashboards.find((d: any) => d.dashboardId == dashboardId));
const dashboardName = dashboard ? dashboard.name : null;
const [metricNotFound, setMetricNotFound] = useState(false);
React.useEffect(() => {
if (metricId && metricId !== 'create') {
metricStore.fetch(metricId, dashboardStore.period);
metricStore.fetch(metricId, dashboardStore.period).catch((e) => {
if (e.status === 404 || e.status === 422) {
setMetricNotFound(true);
}
});
} else if (metricId === 'create') {
metricStore.init();
}
}, [])
}, []);
const onBackHandler = () => {
props.history.goBack();
}
};
const openEdit = () => {
if (expanded) return;
setExpanded(true)
}
setExpanded(true);
};
return useObserver(() => (
<Loader loading={loading}>
@ -58,50 +68,55 @@ function WidgetView(props: Props) {
return 'You have unsaved changes. Are you sure you want to leave?';
}}
/>
<div className="relative pb-10">
<Breadcrumb
items={[
{ label: dashboardName ? dashboardName : 'Metrics', to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId) },
{ label: widget.name, }
{
label: dashboardName ? dashboardName : 'Metrics',
to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId),
},
{ label: widget.name },
]}
/>
<div className="bg-white rounded border">
<div
className={cn(
"px-6 py-4 flex justify-between items-center",
{
<NoContent
show={metricNotFound}
title={
<div className="flex flex-col items-center justify-between">
<AnimatedSVG name={ICONS.EMPTY_STATE} size={100} />
<div className="mt-6 text-2xl">Metric not found!</div>
</div>
}
>
<div className="bg-white rounded border">
<div
className={cn('px-6 py-4 flex justify-between items-center', {
'cursor-pointer hover:bg-active-blue hover:shadow-border-blue rounded': !expanded,
}
)}
onClick={openEdit}
>
<h1 className="mb-0 text-2xl mr-4 min-w-fit">
<WidgetName
name={widget.name}
onUpdate={(name) => metricStore.merge({ name })}
canEdit={expanded}
/>
</h1>
<div className="text-gray-600 w-full cursor-pointer" onClick={() => setExpanded(!expanded)}>
<div
className="flex items-center select-none w-fit ml-auto"
>
<span className="mr-2 color-teal">{expanded ? 'Close' : 'Edit'}</span>
<Icon name={expanded ? 'chevron-up' : 'chevron-down'} size="16" color="teal" />
})}
onClick={openEdit}
>
<h1 className="mb-0 text-2xl mr-4 min-w-fit">
<WidgetName name={widget.name} onUpdate={(name) => metricStore.merge({ name })} canEdit={expanded} />
</h1>
<div className="text-gray-600 w-full cursor-pointer" onClick={() => setExpanded(!expanded)}>
<div className="flex items-center select-none w-fit ml-auto">
<span className="mr-2 color-teal">{expanded ? 'Close' : 'Edit'}</span>
<Icon name={expanded ? 'chevron-up' : 'chevron-down'} size="16" color="teal" />
</div>
</div>
</div>
{expanded && <WidgetForm onDelete={onBackHandler} {...props} />}
</div>
{ expanded && <WidgetForm onDelete={onBackHandler} {...props}/>}
</div>
<WidgetPreview className="mt-8" />
{ widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{ (widget.metricType === 'table' || widget.metricType === 'timeseries') && <WidgetSessions className="mt-8" /> }
{ widget.metricType === 'funnel' && <FunnelIssues /> }
</>
)}
<WidgetPreview className="mt-8" />
{widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{(widget.metricType === 'table' || widget.metricType === 'timeseries') && <WidgetSessions className="mt-8" />}
{widget.metricType === 'funnel' && <FunnelIssues />}
</>
)}
</NoContent>
</div>
</Loader>
));

View file

@ -3,9 +3,8 @@ import { Icon } from 'UI';
import styles from './noContent.module.css';
export default ({
title = "No data available.",
title = <div>No data available.</div>,
subtext,
animatedIcon = false,
icon,
iconSize = 100,
size,
@ -17,8 +16,7 @@ export default ({
}) => (!show ? children :
<div className={ `${ styles.wrapper } ${ size && styles[ size ] }` } style={style}>
{
// icon && <div className={ empty ? styles.emptyIcon : styles.icon } />
animatedIcon ? <div className={ styles[animatedIcon] } /> : (icon && <Icon name={icon} size={iconSize} />)
icon && <Icon name={icon} size={iconSize} />
}
{ title && <div className={ styles.title }>{ title }</div> }
{

View file

@ -74,12 +74,14 @@ export interface IDashboardSotre {
fetchTemplates(hardRefresh: boolean): Promise<any>;
deleteDashboardWidget(dashboardId: string, widgetId: string): Promise<any>;
addWidgetToDashboard(dashboard: IDashboard, metricIds: any): Promise<any>;
setDrillDownPeriod(period: any): void;
updatePinned(dashboardId: string): Promise<any>;
fetchMetricChartData(
metric: IWidget,
data: any,
isWidget: boolean
isWidget: boolean,
period: Period
): Promise<any>;
setPeriod(period: any): void;
}
@ -484,10 +486,12 @@ export default class DashboardStore implements IDashboardSotre {
fetchMetricChartData(
metric: IWidget,
data: any,
isWidget: boolean = false
isWidget: boolean = false,
period: Period
): Promise<any> {
const period = this.period.toTimestamps();
period = period.toTimestamps();
const params = { ...period, ...data, key: metric.predefinedKey };
if (metric.page && metric.limit) {
params["page"] = metric.page;
@ -504,7 +508,7 @@ export default class DashboardStore implements IDashboardSotre {
) {
const _data = {
...data,
chart: getChartFormatter(this.period)(data.chart),
chart: getChartFormatter(period)(data.chart),
};
metric.setData(_data);
resolve(_data);
@ -529,7 +533,7 @@ export default class DashboardStore implements IDashboardSotre {
);
} else {
if (data.hasOwnProperty("chart")) {
_data["chart"] = getChartFormatter(this.period)(
_data["chart"] = getChartFormatter(period)(
data.chart
);
_data["namesMap"] = data.chart
@ -545,7 +549,7 @@ export default class DashboardStore implements IDashboardSotre {
return unique;
}, []);
} else {
_data["chart"] = getChartFormatter(this.period)(
_data["chart"] = getChartFormatter(period)(
Array.isArray(data) ? data : []
);
_data["namesMap"] = Array.isArray(data)

View file

@ -172,7 +172,8 @@ export default class MetricStore implements IMetricStore {
return metricService.getMetric(id)
.then((metric: any) => {
return this.instance = new Widget().fromJson(metric, period)
}).finally(() => {
})
.finally(() => {
this.isLoading = false
})
}
@ -199,22 +200,3 @@ export default class MetricStore implements IMetricStore {
})
}
}
const sampleJsonFunnel = {
// metricId: 1,
name: "Funnel Sample",
metricType: 'funnel',
series: [
{
name: 'Series 1',
filter: {
eventsOrder: 'then',
filters: [
{ type: 'LOCATION', operator: 'is', value: ['/sessions', '/errors', '/users'], percent: 100, completed: 60, dropped: 40, },
{ type: 'LOCATION', operator: 'is', value: ['/sessions'], percent: 80, completed: 40, dropped: 60, },
{ type: 'CLICK', operator: 'on', value: ['DASHBOARDS'], percent: 80, completed: 10, dropped: 90, }
]
}
}
],
}

View file

@ -45,7 +45,7 @@ export default class MetricService implements IMetricService {
*/
getMetric(metricId: string): Promise<any> {
return this.client.get('/metrics/' + metricId)
.then((response: { json: () => any; }) => response.json())
.then(fetchErrorCheck)
.then((response: { data: any; }) => response.data || {});
}

View file

@ -314,9 +314,9 @@ export const exportCSVFile = (headers, items, fileTitle) => {
}
};
export const fetchErrorCheck = (response: any) => {
export const fetchErrorCheck = async (response: any) => {
if (!response.ok) {
throw Error(response.statusText);
return Promise.reject(response);
}
return response.json();
};