fix(ui) - dashboard date range selection reload, metric not found message
This commit is contained in:
parent
13ff1fd621
commit
36258e58c9
7 changed files with 85 additions and 86 deletions
|
|
@ -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]);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -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> }
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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, }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 || {});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue