fix ui: fix tab close calculation, fix widget preview request type

This commit is contained in:
nick-delirium 2024-07-01 12:29:55 +02:00
parent 93a60de04b
commit afc6b495c5
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
22 changed files with 273 additions and 328 deletions

View file

@ -73,7 +73,7 @@ function CardsLibrary(props: Props) {
// isPreview={true}
metric={metric}
isTemplate={true}
isWidget={true}
isSaved={true}
/>
</Card>
</LazyLoad>

View file

@ -137,7 +137,7 @@ function DashboardMetricSelection(props: IProps) {
widget={widget}
active={selectedWidgetIds.includes(widget.metricId)}
isTemplate={true}
isWidget={widget.metricType === 'predefined'}
isSaved={widget.metricType === 'predefined'}
onClick={() => dashboardStore.toggleWidgetSelection(widget)}
/>
))}

View file

@ -83,7 +83,7 @@ function AddMetric({ history, siteId, title, description }: IProps) {
widget={metric}
active={selectedWidgetIds.includes(metric.metricId)}
isTemplate={true}
isWidget={metric.metricType === 'predefined'}
isSaved={metric.metricType === 'predefined'}
onClick={() => dashboardStore.toggleWidgetSelection(metric)}
/>
))

View file

@ -129,7 +129,7 @@ function AddPredefinedMetric({ history, siteId, title, description }: IProps) {
widget={metric}
active={selectedWidgetIds.includes(metric.metricId)}
isTemplate={true}
isWidget={metric.metricType === 'predefined'}
isSaved={metric.metricType === 'predefined'}
onClick={() => dashboardStore.toggleWidgetSelection(metric)}
/>
</React.Fragment>

View file

@ -75,7 +75,7 @@ function DashboardWidgetGrid(props: Props) {
} dashboardId={dashboardId}
siteId={siteId}
isWidget={true}
isSaved={true}
grid="vitals"
/>
</React.Fragment>
@ -102,7 +102,7 @@ function DashboardWidgetGrid(props: Props) {
}
dashboardId={dashboardId}
siteId={siteId}
isWidget={true}
isSaved={true}
grid="other"
/>
</React.Fragment>

View file

@ -24,7 +24,7 @@ function GridView(props: Props) {
widget={metric}
isGridView={true}
active={selectedList.includes(metric.metricId)}
isWidget={true}
isSaved={true}
onClick={() => onItemClick(parseInt(metric.metricId))}
/>
</React.Fragment>

View file

@ -37,13 +37,13 @@ import SessionsBy from "Components/Dashboard/Widgets/CustomMetricsWidgets/Sessio
interface Props {
metric: any;
isWidget?: boolean;
isSaved?: boolean;
isTemplate?: boolean;
isPreview?: boolean;
}
function WidgetChart(props: Props) {
const {isWidget = false, metric, isTemplate} = props;
const {isSaved = false, metric, isTemplate} = props;
const {dashboardStore, metricStore, sessionStore} = useStore();
const _metric: any = metricStore.instance;
const period = dashboardStore.period;
@ -93,10 +93,10 @@ function WidgetChart(props: Props) {
..._metric.series, ..._metric.excludes, ..._metric.startPoint,
hideExcess: _metric.hideExcess
});
const fetchMetricChartData = (metric: any, payload: any, isWidget: any, period: any) => {
const fetchMetricChartData = (metric: any, payload: any, isSaved: any, period: any) => {
if (!isMounted()) return;
setLoading(true);
dashboardStore.fetchMetricChartData(metric, payload, isWidget, period).then((res: any) => {
dashboardStore.fetchMetricChartData(metric, payload, isSaved, period).then((res: any) => {
if (isMounted()) setData(res);
}).finally(() => {
setLoading(false);
@ -111,8 +111,8 @@ function WidgetChart(props: Props) {
}
prevMetricRef.current = metric;
const timestmaps = drillDownPeriod.toTimestamps();
const payload = isWidget ? {...params} : {...metricParams, ...timestmaps, ...metric.toJson()};
debounceRequest(metric, payload, isWidget, !isWidget ? drillDownPeriod : period);
const payload = isSaved ? {...params} : {...metricParams, ...timestmaps, ...metric.toJson()};
debounceRequest(metric, payload, isSaved, !isSaved ? drillDownPeriod : period);
};
useEffect(() => {
_metric.updateKey('page', 1);
@ -126,7 +126,7 @@ function WidgetChart(props: Props) {
const metricWithData = {...metric, data};
if (metricType === FUNNEL) {
return <FunnelWidget metric={metric} data={data} isWidget={isWidget || isTemplate}/>;
return <FunnelWidget metric={metric} data={data} isWidget={isSaved || isTemplate}/>;
}
if (metricType === 'predefined' || metricType === ERRORS || metricType === PERFORMANCE || metricType === RESOURCE_MONITORING || metricType === WEB_VITALS) {
@ -166,7 +166,7 @@ function WidgetChart(props: Props) {
metric={metric}
data={data}
isTemplate={isTemplate}
isEdit={!isWidget && !isTemplate}
isEdit={!isSaved && !isTemplate}
/>
);
}
@ -176,7 +176,7 @@ function WidgetChart(props: Props) {
metric={metric}
data={data}
// isTemplate={isTemplate}
isEdit={!isWidget && !isTemplate}
isEdit={!isSaved && !isTemplate}
/>
);
}

View file

@ -1,137 +1,65 @@
import React from 'react';
import { Space, Switch } from 'antd';
import cn from 'classnames';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { CLICKMAP, USER_PATH } from 'App/constants/card';
import { useStore } from 'App/mstore';
import ClickMapRagePicker from 'Components/Dashboard/components/ClickMapRagePicker';
import WidgetWrapper from '../WidgetWrapper';
import {useStore} from 'App/mstore';
// import {SegmentSelection, Button, Icon} from 'UI';
import {observer} from 'mobx-react-lite';
// import {FilterKey} from 'Types/filter/filterType';
// import WidgetDateRange from '../WidgetDateRange/WidgetDateRange';
import ClickMapRagePicker from "Components/Dashboard/components/ClickMapRagePicker";
// import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal';
import {CLICKMAP, TABLE, TIMESERIES, RETENTION, USER_PATH} from 'App/constants/card';
import {Space, Switch} from 'antd';
// import AddToDashboardButton from "Components/Dashboard/components/AddToDashboardButton";
interface Props {
className?: string;
name: string;
isEditing?: boolean;
className?: string;
name: string;
isEditing?: boolean;
}
function WidgetPreview(props: Props) {
const {className = ''} = props;
const {metricStore, dashboardStore} = useStore();
// const dashboards = dashboardStore.dashboards;
const metric: any = metricStore.instance;
// const isTimeSeries = metric.metricType === TIMESERIES;
// const isTable = metric.metricType === TABLE;
// const isRetention = metric.metricType === RETENTION;
// const disableVisualization = metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS;
//
// const changeViewType = (_, {name, value}: any) => {
// metric.update({[name]: value});
// }
const { className = '' } = props;
const { metricStore, dashboardStore } = useStore();
const metric: any = metricStore.instance;
return (
<>
<div className={cn(className, 'bg-white rounded-xl border shadow-sm mt-0')}>
<div className="flex items-center justify-between px-4 pt-2">
<h2 className="text-xl">
{props.name}
</h2>
<div className="flex items-center">
{metric.metricType === USER_PATH && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
metric.update({hideExcess: !metric.hideExcess});
}}
>
<Space>
<Switch
checked={metric.hideExcess}
size="small"
/>
<span className="mr-4 color-gray-medium">Hide Minor Paths</span>
</Space>
</a>
)}
return (
<>
<div
className={cn(className, 'bg-white rounded-xl border shadow-sm mt-0')}
>
<div className="flex items-center justify-between px-4 pt-2">
<h2 className="text-xl">{props.name}</h2>
<div className="flex items-center">
{metric.metricType === USER_PATH && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
metric.update({ hideExcess: !metric.hideExcess });
}}
>
<Space>
<Switch checked={metric.hideExcess} size="small" />
<span className="mr-4 color-gray-medium">
Hide Minor Paths
</span>
</Space>
</a>
)}
{/*{isTimeSeries && (*/}
{/* <>*/}
{/* <span className="mr-4 color-gray-medium">Visualization</span>*/}
{/* <SegmentSelection*/}
{/* name="viewType"*/}
{/* className="my-3"*/}
{/* primary*/}
{/* size="small"*/}
{/* onSelect={ changeViewType }*/}
{/* value={{ value: metric.viewType }}*/}
{/* list={ [*/}
{/* { value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' },*/}
{/* { value: 'progress', name: 'Progress', icon: 'hash' },*/}
{/* ]}*/}
{/* />*/}
{/* </>*/}
{/*)}*/}
{/*{!disableVisualization && isTable && (*/}
{/* <>*/}
{/* <span className="mr-4 color-gray-medium">Visualization</span>*/}
{/* <SegmentSelection*/}
{/* name="viewType"*/}
{/* className="my-3"*/}
{/* primary={true}*/}
{/* size="small"*/}
{/* onSelect={ changeViewType }*/}
{/* value={{ value: metric.viewType }}*/}
{/* list={[*/}
{/* { value: 'table', name: 'Table', icon: 'table' },*/}
{/* { value: 'pieChart', name: 'Chart', icon: 'pie-chart-fill' },*/}
{/* ]}*/}
{/* disabledMessage="Chart view is not supported"*/}
{/* />*/}
{/* </>*/}
{/*)}*/}
{/*{isRetention && (*/}
{/* <>*/}
{/* <span className="mr-4 color-gray-medium">Visualization</span>*/}
{/* <SegmentSelection*/}
{/* name="viewType"*/}
{/* className="my-3"*/}
{/* primary={true}*/}
{/* size="small"*/}
{/* onSelect={ changeViewType }*/}
{/* value={{ value: metric.viewType }}*/}
{/* list={[*/}
{/* { value: 'trend', name: 'Trend', icon: 'graph-up-arrow' },*/}
{/* { value: 'cohort', name: 'Cohort', icon: 'dice-3' },*/}
{/* ]}*/}
{/* disabledMessage="Chart view is not supported"*/}
{/* />*/}
{/*</>*/}
{/*)}*/}
<div className="mx-4"/>
{metric.metricType === CLICKMAP ? (
<ClickMapRagePicker/>
) : null}
{/* add to dashboard */}
{/*{metric.exists() && (*/}
{/* <AddToDashboardButton metricId={metric.metricId}/>*/}
{/*)}*/}
</div>
</div>
<div className="pt-0">
<WidgetWrapper widget={metric} isPreview={true} isWidget={false} hideName/>
</div>
</div>
</>
);
<div className="mx-4" />
{metric.metricType === CLICKMAP ? <ClickMapRagePicker /> : null}
</div>
</div>
<div className="pt-0">
<WidgetWrapper
widget={metric}
isPreview={true}
isSaved={metric.exists()}
hideName
/>
</div>
</div>
</>
);
}
export default observer(WidgetPreview);

View file

@ -1,168 +1,183 @@
import React, {useState} from 'react';
import {useStore} from 'App/mstore';
import {Icon, Loader, NoContent} from 'UI';
import WidgetPreview from '../WidgetPreview';
import WidgetSessions from '../WidgetSessions';
import {useObserver} from 'mobx-react-lite';
import {dashboardMetricDetails, metricDetails, withSiteId} from 'App/routes';
import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues';
import Breadcrumb from 'Shared/Breadcrumb';
import {FilterKey} from 'Types/filter/filterType';
import {Prompt, useHistory} from 'react-router';
import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG';
import { FilterKey } from 'Types/filter/filterType';
import { Space } from 'antd';
import { useObserver } from 'mobx-react-lite';
import React, { useState } from 'react';
import { Prompt, useHistory } from 'react-router';
import {
TIMESERIES,
TABLE,
CLICKMAP,
FUNNEL,
INSIGHTS,
USER_PATH,
RETENTION,
CLICKMAP,
FUNNEL,
INSIGHTS,
RETENTION,
TABLE,
TIMESERIES,
USER_PATH,
} from 'App/constants/card';
import { useStore } from 'App/mstore';
import Widget from 'App/mstore/types/widget';
import { dashboardMetricDetails, metricDetails, withSiteId } from 'App/routes';
import WidgetFormNew from 'Components/Dashboard/components/WidgetForm/WidgetFormNew';
import { renderClickmapThumbnail } from 'Components/Dashboard/components/WidgetForm/renderMap';
import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader';
import { Icon, Loader, NoContent } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import Breadcrumb from 'Shared/Breadcrumb';
import CardIssues from '../CardIssues';
import CardUserList from '../CardUserList/CardUserList';
import WidgetViewHeader from "Components/Dashboard/components/WidgetView/WidgetViewHeader";
import WidgetFormNew from "Components/Dashboard/components/WidgetForm/WidgetFormNew";
import {Space} from "antd";
import {renderClickmapThumbnail} from "Components/Dashboard/components/WidgetForm/renderMap";
import Widget from "App/mstore/types/widget";
import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues';
import WidgetPreview from '../WidgetPreview';
import WidgetSessions from '../WidgetSessions';
interface Props {
history: any;
match: any;
siteId: any;
history: any;
match: any;
siteId: any;
}
function WidgetView(props: Props) {
const {
match: {
params: {siteId, dashboardId, metricId},
},
} = props;
// const siteId = location.pathname.split('/')[1];
// const dashboardId = location.pathname.split('/')[3];
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 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);
const history = useHistory();
const [initialInstance, setInitialInstance] = useState();
const isClickMap = widget.metricType === CLICKMAP;
const {
match: {
params: { siteId, dashboardId, metricId },
},
} = props;
// const siteId = location.pathname.split('/')[1];
// const dashboardId = location.pathname.split('/')[3];
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 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);
const history = useHistory();
const [initialInstance, setInitialInstance] = useState();
const isClickMap = widget.metricType === CLICKMAP;
React.useEffect(() => {
if (metricId && metricId !== 'create') {
metricStore.fetch(metricId, dashboardStore.period).catch((e) => {
if (e.response.status === 404 || e.response.status === 422) {
setMetricNotFound(true);
}
});
} else {
metricStore.init();
React.useEffect(() => {
if (metricId && metricId !== 'create') {
metricStore.fetch(metricId, dashboardStore.period).catch((e) => {
if (e.response.status === 404 || e.response.status === 422) {
setMetricNotFound(true);
}
}, []);
});
} else {
metricStore.init();
}
}, []);
// const onBackHandler = () => {
// props.history.goBack();
// };
//
// const openEdit = () => {
// if (expanded) return;
// setExpanded(true);
// };
// const onBackHandler = () => {
// props.history.goBack();
// };
//
// const openEdit = () => {
// if (expanded) return;
// setExpanded(true);
// };
const undoChanges = () => {
const w = new Widget();
metricStore.merge(w.fromJson(initialInstance), false);
};
const undoChanges = () => {
const w = new Widget();
metricStore.merge(w.fromJson(initialInstance), false);
};
const onSave = async () => {
const wasCreating = !widget.exists();
if (isClickMap) {
try {
widget.thumbnail = await renderClickmapThumbnail();
} catch (e) {
console.error(e);
}
}
const savedMetric = await metricStore.save(widget);
setInitialInstance(widget.toJson());
if (wasCreating) {
if (parseInt(dashboardId, 10) > 0) {
history.replace(
withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)
);
void dashboardStore.addWidgetToDashboard(
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
[savedMetric.metricId]
);
} else {
history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId));
}
}
};
const onSave = async () => {
const wasCreating = !widget.exists();
if (isClickMap) {
try {
widget.sessionId = widget.data.sessionId;
widget.thumbnail = await renderClickmapThumbnail();
} catch (e) {
console.error(e);
}
}
const savedMetric = await metricStore.save(widget);
setInitialInstance(widget.toJson());
if (wasCreating) {
if (parseInt(dashboardId, 10) > 0) {
history.replace(
withSiteId(
dashboardMetricDetails(dashboardId, savedMetric.metricId),
siteId
)
);
void dashboardStore.addWidgetToDashboard(
dashboardStore.getDashboard(parseInt(dashboardId, 10))!,
[savedMetric.metricId]
);
} else {
history.replace(
withSiteId(metricDetails(savedMetric.metricId), siteId)
);
}
}
};
return useObserver(() => (
<Loader loading={loading}>
<Prompt
when={hasChanged}
message={(location: any) => {
if (location.pathname.includes('/metrics/') || location.pathname.includes('/metric/')) {
return true;
}
return 'You have unsaved changes. Are you sure you want to leave?';
}}
/>
return useObserver(() => (
<Loader loading={loading}>
<Prompt
when={hasChanged}
message={(location: any) => {
if (
location.pathname.includes('/metrics/') ||
location.pathname.includes('/metric/')
) {
return true;
}
return 'You have unsaved changes. Are you sure you want to leave?';
}}
/>
<div style={{maxWidth: '1360px', margin: 'auto'}}>
<Breadcrumb
items={[
{
label: dashboardName ? dashboardName : 'Cards',
to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId),
},
{label: widget.name},
]}
/>
<NoContent
show={metricNotFound}
title={
<div className="flex flex-col items-center justify-between">
<AnimatedSVG name={ICONS.EMPTY_STATE} size={100}/>
<div className="mt-4">Metric not found!</div>
</div>
}
>
<Space direction="vertical" size={20} className="w-full">
<WidgetViewHeader onSave={onSave} undoChanges={undoChanges}/>
<WidgetFormNew/>
{/*<div className="bg-white rounded border mt-3">*/}
{/* <WidgetForm expanded={expanded} onDelete={onBackHandler} {...props} />*/}
{/*</div>*/}
<WidgetPreview name={widget.name} isEditing={expanded}/>
{widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) &&
<WidgetSessions/>}
{widget.metricType === FUNNEL && <FunnelIssues/>}
</>
)}
{widget.metricType === USER_PATH && <CardIssues/>}
{widget.metricType === RETENTION && <CardUserList/>}
</Space>
</NoContent>
<div style={{ maxWidth: '1360px', margin: 'auto' }}>
<Breadcrumb
items={[
{
label: dashboardName ? dashboardName : 'Cards',
to: dashboardId
? withSiteId('/dashboard/' + dashboardId, siteId)
: withSiteId('/metrics', siteId),
},
{ label: widget.name },
]}
/>
<NoContent
show={metricNotFound}
title={
<div className="flex flex-col items-center justify-between">
<AnimatedSVG name={ICONS.EMPTY_STATE} size={100} />
<div className="mt-4">Metric not found!</div>
</div>
</Loader>
));
}
>
<Space direction="vertical" size={20} className="w-full">
<WidgetViewHeader onSave={onSave} undoChanges={undoChanges} />
<WidgetFormNew />
<WidgetPreview name={widget.name} isEditing={expanded} />
{widget.metricOf !== FilterKey.SESSIONS &&
widget.metricOf !== FilterKey.ERRORS && (
<>
{(widget.metricType === TABLE ||
widget.metricType === TIMESERIES ||
widget.metricType === CLICKMAP ||
widget.metricType === INSIGHTS) && <WidgetSessions />}
{widget.metricType === FUNNEL && <FunnelIssues />}
</>
)}
{widget.metricType === USER_PATH && <CardIssues />}
{widget.metricType === RETENTION && <CardUserList />}
</Space>
</NoContent>
</div>
</Loader>
));
}
export default WidgetView;

View file

@ -26,7 +26,7 @@ interface Props {
active?: boolean;
history?: any;
onClick?: () => void;
isWidget?: boolean;
isSaved?: boolean;
hideName?: boolean;
grid?: string;
isGridView?: boolean;
@ -34,7 +34,7 @@ interface Props {
function WidgetWrapper(props: Props & RouteComponentProps) {
const { dashboardStore } = useStore();
const {
isWidget = false,
isSaved = false,
active = false,
index = 0,
moveListItem = null,
@ -78,7 +78,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
};
const onChartClick = () => {
if (!isWidget || isPredefined) return;
if (!isSaved || isPredefined) return;
props.history.push(
withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId)
@ -90,7 +90,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
const addOverlay =
isTemplate ||
(!isPredefined &&
isWidget &&
isSaved &&
widget.metricOf !== FilterKey.ERRORS &&
widget.metricOf !== FilterKey.SESSIONS);
@ -99,7 +99,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
className={cn(
'relative rounded bg-white border group rounded-lg',
'col-span-' + widget.config.col,
{ 'hover:shadow-border-gray': !isTemplate && isWidget },
{ 'hover:shadow-border-gray': !isTemplate && isSaved },
{ 'hover:shadow-border-main': isTemplate }
)}
style={{
@ -112,7 +112,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
onClick={props.onClick ? props.onClick : () => {}}
id={`widget-${widget.widgetId}`}
>
{!isTemplate && isWidget && isPredefined && (
{!isTemplate && isSaved && isPredefined && (
<div
className={cn(
stl.drillDownMessage,
@ -126,7 +126,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
{addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />}
<div
className={cn('p-3 pb-4 flex items-center justify-between', {
'cursor-move': !isTemplate && isWidget,
'cursor-move': !isTemplate && isSaved,
})}
>
{!props.hideName ? (
@ -134,7 +134,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
<TextEllipsis text={widget.name} />
</div>
) : null}
{isWidget && (
{isSaved && (
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && !isGridView && (
<>
@ -171,7 +171,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
isPreview={isPreview}
metric={widget}
isTemplate={isTemplate}
isWidget={isWidget}
isSaved={isSaved}
/>
</div>
</LazyLoad>

View file

@ -27,7 +27,7 @@ interface Props {
active?: boolean;
history?: any;
onClick?: () => void;
isWidget?: boolean;
isSaved?: boolean;
hideName?: boolean;
grid?: string;
isGridView?: boolean;
@ -36,7 +36,7 @@ interface Props {
function WidgetWrapperNew(props: Props & RouteComponentProps) {
const {dashboardStore} = useStore();
const {
isWidget = false,
isSaved = false,
active = false,
index = 0,
moveListItem = null,
@ -75,7 +75,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
});
const onChartClick = () => {
if (!isWidget || isPredefined) return;
if (!isSaved || isPredefined) return;
props.history.push(
withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId)
);
@ -86,7 +86,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
const addOverlay =
isTemplate ||
(!isPredefined &&
isWidget &&
isSaved &&
widget.metricOf !== FilterKey.ERRORS &&
widget.metricOf !== FilterKey.SESSIONS);
@ -95,7 +95,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
className={cn(
'relative group',
'col-span-' + widget.config.col,
{'hover:shadow': !isTemplate && isWidget},
{'hover:shadow': !isTemplate && isSaved},
)}
style={{
userSelect: 'none',
@ -107,7 +107,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
onClick={props.onClick ? props.onClick : () => null}
id={`widget-${widget.widgetId}`}
title={!props.hideName ? widget.name : null}
extra={isWidget ? [
extra={isSaved ? [
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && !isGridView && (
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId}/>
@ -131,7 +131,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
},
}}
>
{!isTemplate && isWidget && isPredefined && (
{!isTemplate && isSaved && isPredefined && (
<Tooltip title="Cannot drill down system provided metrics">
<div
className={cn(stl.drillDownMessage, 'disabled text-gray text-sm invisible group-hover:visible')}>
@ -148,7 +148,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
isPreview={isPreview}
metric={widget}
isTemplate={isTemplate}
isWidget={isWidget}
isSaved={isSaved}
/>
</div>
</LazyLoad>

View file

@ -147,7 +147,7 @@ function FilterList(props: Props) {
width: 'calc(100% + 2.5rem)',
}}
className={
'hover:bg-active-blue px-5 gap-2 items-center flex'
'hover:bg-active-blue px-5 gap-2 items-center flex z-10'
}
id={`${filter.key}-${filterIndex}`}
onDragOver={(e) => handleDragOverEv(e, filterIndex)}

View file

@ -1,13 +1,8 @@
import React from 'react';
import { Divider as AntdDivider, DividerProps as AntdDividerProps } from 'antd';
interface DividerProps extends AntdDividerProps {
customProp?: boolean;
}
const Divider: React.FC<DividerProps> = ({ customProp, ...restProps }) => {
return <AntdDivider {...restProps} />;
const Divider: React.FC = (props: AntdDividerProps) => {
return <AntdDivider className={props.className} style={props.style} />;
};
export default Divider;

View file

@ -12,7 +12,7 @@ interface Props {
function Pdf_download(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg xmlns="http://www.w3.org/2000/svg" width={ `${ width }px` } height={ `${ height }px` } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-down"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M12 18v-6"/><path d="m9 15 3 3 3-3"/></svg>
<svg viewBox="0 0 19 19" width={ `${ width }px` } height={ `${ height }px` } ><path d="M10.094 5.249a.594.594 0 0 0-1.188 0v4.504l-1.36-1.362a.595.595 0 0 0-.841.84l2.375 2.376a.596.596 0 0 0 .84 0l2.375-2.375a.595.595 0 0 0-.84-.841l-1.361 1.362V5.249Z"/><path d="M16.625 16.625V5.344L11.281 0H4.75a2.375 2.375 0 0 0-2.375 2.375v14.25A2.375 2.375 0 0 0 4.75 19h9.5a2.375 2.375 0 0 0 2.375-2.375ZM11.281 3.562a1.781 1.781 0 0 0 1.781 1.782h2.376v11.281a1.188 1.188 0 0 1-1.188 1.188h-9.5a1.187 1.187 0 0 1-1.188-1.188V2.375A1.188 1.188 0 0 1 4.75 1.187h6.531v2.375Z"/><path clipRule="evenodd" d="M15.58 13.49H3.42v4.37h1.789v-3.512H6.61c.282 0 .524.051.726.154.205.103.361.245.47.425.11.178.165.383.165.613 0 .226-.055.424-.164.593-.11.169-.266.3-.47.396-.203.093-.445.14-.727.14h-.554v1.191h2.37v-3.512h1.13c.238 0 .455.041.653.123a1.537 1.537 0 0 1 .854.88c.08.205.12.431.12.68v.148c0 .247-.04.474-.12.68a1.512 1.512 0 0 1-.849.88c-.193.08-.404.12-.635.121h2.046v-3.512h2.35v.654h-1.503v.808h1.365v.651h-1.365v1.399h3.108v-4.37Zm-9.524 1.512v1.013h.554c.12 0 .216-.02.29-.06a.367.367 0 0 0 .161-.167.547.547 0 0 0 .054-.244.668.668 0 0 0-.054-.267.431.431 0 0 0-.161-.198.496.496 0 0 0-.29-.077h-.554Zm3.512 2.207h-.295v-2.207h.283c.123 0 .233.021.328.065a.604.604 0 0 1 .24.195.88.88 0 0 1 .146.321c.033.127.05.275.05.444v.152c0 .225-.03.415-.089.569a.707.707 0 0 1-.256.345.696.696 0 0 1-.407.116Z"/></svg>
);
}

View file

@ -12,7 +12,7 @@ interface Props {
function Pencil(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg xmlns="http://www.w3.org/2000/svg" width={ `${ width }px` } height={ `${ height }px` } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-pen-line"><path d="m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2"/><path d="M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/><path d="M8 18h1"/></svg>
<svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/></svg>
);
}

View file

@ -12,7 +12,7 @@ interface Props {
function Trash(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg xmlns="http://www.w3.org/2000/svg" width={ `${ width }px` } height={ `${ height }px` } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width={ `${ width }px` } height={ `${ height }px` } ><path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
);
}

View file

@ -12,8 +12,7 @@ interface Props {
function Users(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg xmlns="http://www.w3.org/2000/svg" width={ `${ width }px` } height={ `${ height }px` } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users-round"><path d="M18 21a8 8 0 0 0-16 0"/><circle cx="10" cy="8" r="5"/><path d="M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3"/></svg>
<svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/></svg>
);
}

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Divider, Menu, Typography } from 'antd';
import { Menu, Typography } from 'antd';
import SVG from 'UI/SVG';
import * as routes from 'App/routes';
import { bookmarks, client, CLIENT_DEFAULT_TAB, CLIENT_TABS, fflags, notes, sessions, withSiteId } from 'App/routes';
@ -8,7 +8,7 @@ import { categories as main_menu, MENU, preferences, PREFERENCES_MENU } from './
import { connect } from 'react-redux';
import { MODULES } from 'Components/Client/Modules';
import cn from 'classnames';
import { Icon } from 'UI';
import { Icon, Divider } from 'UI';
import SupportModal from 'App/layout/SupportModal';
import { setActiveTab } from 'Duck/search';

View file

@ -410,7 +410,7 @@ export default class DashboardStore {
fetchMetricChartData(
metric: Widget,
data: any,
isWidget: boolean = false,
isSaved: boolean = false,
period: Record<string, any>
): Promise<any> {
period = period.toTimestamps();
@ -425,7 +425,7 @@ export default class DashboardStore {
this.pendingRequests += 1;
try {
const data = await metricService.getMetricChartData(metric, params, isWidget);
const data = await metricService.getMetricChartData(metric, params, isSaved);
resolve(metric.setData(data, period));
} catch (error) {
reject(error);

View file

@ -85,6 +85,7 @@ export default class Widget {
page: number = 1;
limit: number = 5;
thumbnail?: string;
sessionId?: string;
params: any = {density: 70};
startType: string = 'start';
startPoint: FilterItem = new FilterItem(filtersMap[FilterKey.LOCATION]);
@ -165,6 +166,7 @@ export default class Widget {
this.predefinedKey = json.predefinedKey;
this.category = json.category;
this.thumbnail = json.thumbnail;
this.sessionId = json.sessionId;
this.isPublic = json.isPublic;
if (this.metricType === FUNNEL) {
@ -220,6 +222,7 @@ export default class Widget {
name: this.name,
series: this.series.map((series: any) => series.toJson()),
thumbnail: this.thumbnail,
sessionId: this.sessionId,
page: this.page,
limit: this.limit,
config: {

View file

@ -202,10 +202,15 @@ export default class MessageManager {
namesObj[tabId] = ''
}
}
lastMsgArr.sort((a, b) => a[1] - b[1])
lastMsgArr.forEach(([tabId, lastMessageTs]) => {
this.tabCloseManager.append({ tabId, time: lastMessageTs })
})
if (Object.keys(namesObj).length === 1) {
this.tabCloseManager.append({ tabId: lastMsgArr[0][0], time: this.session.durationMs - 250 })
} else {
lastMsgArr.forEach(([tabId, lastMessageTs]) => {
this.tabCloseManager.append({ tabId, time: lastMessageTs })
})
}
this.state.update({ tabNames: namesObj })
}

View file

@ -72,7 +72,7 @@ export default class MetricService {
.then((response: { data: any; }) => response.data || []);
}
async getMetricChartData(metric: Widget, data: any, isWidget: boolean = false): Promise<any> {
async getMetricChartData(metric: Widget, data: any, isSaved: boolean = false): Promise<any> {
if (
metric.metricType === CLICKMAP
&& document.location.pathname.split('/').pop() === 'metrics'
@ -80,7 +80,7 @@ export default class MetricService {
) {
return Promise.resolve({});
}
const path = isWidget ? `/cards/${metric.metricId}/chart` : `/cards/try`;
const path = isSaved ? `/cards/${metric.metricId}/chart` : `/cards/try`;
if (metric.metricType === USER_PATH) {
data.density = 5;
data.metricOf = 'sessionCount';