change(ui): cards menu

This commit is contained in:
Shekar Siri 2024-07-02 16:10:52 +02:00
parent 0e42041aa8
commit ecec4d8fd7
8 changed files with 265 additions and 158 deletions

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { NoContent } from 'UI'; import { NoContent } from 'UI';
import { Styles, AvgLabel } from '../../common'; import { Styles, AvgLabel } from '../../common';
import { import {
ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer,
XAxis, YAxis, ReferenceLine, Tooltip XAxis, YAxis, ReferenceLine, Tooltip
} from 'recharts'; } from 'recharts';
import { NO_METRIC_DATA } from 'App/constants/messages' import { NO_METRIC_DATA } from 'App/constants/messages'
@ -41,26 +41,25 @@ const PercentileLine = props => {
interface Props { interface Props {
data: any data: any
metric?: any
} }
function ResponseTimeDistribution(props: Props) { function ResponseTimeDistribution(props: Props) {
const { data, metric } = props; const { data } = props;
const colors = Styles.colors; const colors = Styles.colors;
return ( return (
<NoContent <NoContent
size="small" size="small"
title={NO_METRIC_DATA} title={NO_METRIC_DATA}
show={ metric.data.chart.length === 0 } show={ data.chart.length === 0 }
style={ { height: '240px' } } style={ { height: '240px' } }
> >
<div className="flex items-center justify-end mb-3"> <div className="flex items-center justify-end mb-3">
<AvgLabel text="Avg" unit="ms" className="ml-3" count={metric.data.value} /> <AvgLabel text="Avg" unit="ms" className="ml-3" count={data.value} />
</div> </div>
<div className="flex mb-4"> <div className="flex mb-4">
<ResponsiveContainer height={ 240 } width="100%"> <ResponsiveContainer height={ 240 } width="100%">
<ComposedChart <ComposedChart
data={metric.data.chart} data={data.chart}
margin={Styles.chartMargins} margin={Styles.chartMargins}
barSize={50} barSize={50}
> >
@ -68,7 +67,7 @@ function ResponseTimeDistribution(props: Props) {
<XAxis <XAxis
{...Styles.xaxis} {...Styles.xaxis}
dataKey="responseTime" dataKey="responseTime"
label={{ label={{
...Styles.axisLabelLeft, ...Styles.axisLabelLeft,
angle: 0, angle: 0,
offset: 0, offset: 0,
@ -87,7 +86,7 @@ function ResponseTimeDistribution(props: Props) {
/> />
<Bar minPointSize={1} name="Calls" dataKey="count" stackId="a" fill={colors[2]} label="Backend" /> <Bar minPointSize={1} name="Calls" dataKey="count" stackId="a" fill={colors[2]} label="Backend" />
<Tooltip {...Styles.tooltip} labelFormatter={val => 'Page Response Time: ' + val} /> <Tooltip {...Styles.tooltip} labelFormatter={val => 'Page Response Time: ' + val} />
{ metric.data.percentiles && metric.data.percentiles.map((item: any, i: number) => ( { data.percentiles && data.percentiles.map((item: any, i: number) => (
<ReferenceLine <ReferenceLine
key={i} key={i}
label={ label={
@ -111,13 +110,13 @@ function ResponseTimeDistribution(props: Props) {
</ResponsiveContainer> </ResponsiveContainer>
<ResponsiveContainer height={ 240 } width="10%"> <ResponsiveContainer height={ 240 } width="10%">
<BarChart <BarChart
data={metric.data.extremeValues} data={data.extremeValues}
margin={Styles.chartMargins} margin={Styles.chartMargins}
barSize={40} barSize={40}
> >
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
<XAxis {...Styles.xaxis} dataKey="time" /> <XAxis {...Styles.xaxis} dataKey="time" />
<YAxis hide type="number" domain={[0, metric.data.total]} {...Styles.yaxis} allowDecimals={false} /> <YAxis hide type="number" domain={[0, data.total]} {...Styles.yaxis} allowDecimals={false} />
<Tooltip {...Styles.tooltip} /> <Tooltip {...Styles.tooltip} />
<Bar minPointSize={1} name="Extreme Values" dataKey="count" stackId="a" fill={colors[0]} /> <Bar minPointSize={1} name="Extreme Values" dataKey="count" stackId="a" fill={colors[0]} />
</BarChart> </BarChart>

View file

@ -7,7 +7,17 @@ import ByBrowser from './Examples/SessionsBy/ByBrowser';
import BySystem from './Examples/SessionsBy/BySystem'; import BySystem from './Examples/SessionsBy/BySystem';
import ByCountry from './Examples/SessionsBy/ByCountry'; import ByCountry from './Examples/SessionsBy/ByCountry';
import ByUrl from './Examples/SessionsBy/ByUrl'; import ByUrl from './Examples/SessionsBy/ByUrl';
import { ERRORS, FUNNEL, INSIGHTS, PERFORMANCE, TABLE, TIMESERIES, USER_PATH, WEB_VITALS } from 'App/constants/card'; import {
CLICKMAP,
ERRORS,
FUNNEL,
INSIGHTS,
PERFORMANCE,
TABLE,
TIMESERIES,
USER_PATH,
WEB_VITALS
} from 'App/constants/card';
import { FilterKey } from 'Types/filter/filterType'; import { FilterKey } from 'Types/filter/filterType';
import { Activity, BarChart, TableCellsMerge, TrendingUp } from 'lucide-react'; import { Activity, BarChart, TableCellsMerge, TrendingUp } from 'lucide-react';
import WebVital from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/WebVital'; import WebVital from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/WebVital';
@ -25,6 +35,7 @@ import SlowestDomains
from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains'; from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains';
import SpeedIndexByLocationExample import SpeedIndexByLocationExample
from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample'; from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample';
import HeatmapsExample from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/HeatmapsExample';
export const CARD_CATEGORY = { export const CARD_CATEGORY = {
PRODUCT_ANALYTICS: 'product-analytics', PRODUCT_ANALYTICS: 'product-analytics',
@ -90,6 +101,14 @@ export const CARD_LIST: CardType[] = [
totalDropDueToIssues: 294 totalDropDueToIssues: 294
} }
}, },
{
title: 'Heatmaps',
key: CLICKMAP,
cardType: CLICKMAP,
metricOf: 'sessionCount',
category: CARD_CATEGORIES[0].key,
example: HeatmapsExample
},
{ {
title: 'Path Finder', title: 'Path Finder',
key: USER_PATH, key: USER_PATH,
@ -122,6 +141,7 @@ export const CARD_LIST: CardType[] = [
example: ByIssues example: ByIssues
}, },
{ {
title: 'Insights', title: 'Insights',
key: INSIGHTS, key: INSIGHTS,

View file

@ -0,0 +1,60 @@
import React, { useEffect } from 'react';
import ExCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard';
import heatmapRenderer from 'Player/web/addons/simpleHeatmap';
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
}
function HeatmapsExample(props: Props) {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
useEffect(() => {
const pointMap: Record<string, { times: number; data: number[], original: any }> = {};
let maxIntensity = 0;
for (let i = 0; i < 20; i++) {
const x = Math.floor(Math.random() * 300);
const y = Math.floor(Math.random() * 180);
const key = `${x}-${y}`;
if (!pointMap[key]) {
pointMap[key] = {
times: Math.floor(Math.random() * 100),
data: [x, y],
original: { x, y }
};
}
maxIntensity = Math.max(maxIntensity, pointMap[key].times);
}
const heatmapData: number[][] = [];
for (const key in pointMap) {
const { data, times } = pointMap[key];
heatmapData.push([...data, times]);
}
heatmapRenderer
.setCanvas(canvasRef?.current!)
.setData(heatmapData)
.setRadius(15, 10)
.setMax(maxIntensity)
.resize()
.draw();
}, []);
// const data = {};
return (
<ExCard
{...props}
>
<canvas ref={canvasRef}
style={{ width: '100%', height: '224px', backgroundColor: '#F4F4F4', borderRadius: '10px' }} />
</ExCard>
);
}
export default HeatmapsExample;

View file

@ -0,0 +1,26 @@
import React from 'react';
import ExCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard';
import CustomMetricOverviewChart from 'Components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart';
import ResponseTimeDistribution from 'Components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution';
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
}
function PageResponseTimeDistributionExample(props: Props) {
const data = {
chart: []
}
return (
<ExCard
{...props}
>
<ResponseTimeDistribution data={data} />
</ExCard>
);
}
export default PageResponseTimeDistributionExample;

View file

@ -54,8 +54,8 @@ function DashboardWidgetGrid(props: Props) {
} }
dashboardId={dashboardId} dashboardId={dashboardId}
siteId={siteId} siteId={siteId}
isWidget={false}
grid="other" grid="other"
showMenu={true}
/> />
</React.Fragment> </React.Fragment>
)) ))

View file

@ -1,159 +1,161 @@
import React, {useRef} from 'react'; import React, { useRef } from 'react';
import cn from 'classnames'; import cn from 'classnames';
import {Card, Tooltip, Button} from 'antd'; import { Card, Tooltip, Button } from 'antd';
import {useDrag, useDrop} from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd';
import WidgetChart from '../WidgetChart'; import WidgetChart from '../WidgetChart';
import {observer} from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import {useStore} from 'App/mstore'; import { useStore } from 'App/mstore';
import {withRouter, RouteComponentProps} from 'react-router-dom'; import { withRouter, RouteComponentProps } from 'react-router-dom';
import {withSiteId, dashboardMetricDetails} from 'App/routes'; import { withSiteId, dashboardMetricDetails } from 'App/routes';
import TemplateOverlay from './TemplateOverlay'; import TemplateOverlay from './TemplateOverlay';
import stl from './widgetWrapper.module.css'; import stl from './widgetWrapper.module.css';
import {FilterKey} from 'App/types/filter/filterType'; import { FilterKey } from 'App/types/filter/filterType';
import LazyLoad from 'react-lazyload'; import LazyLoad from 'react-lazyload';
import {TIMESERIES} from "App/constants/card"; import { TIMESERIES } from 'App/constants/card';
import CardMenu from "Components/Dashboard/components/WidgetWrapper/CardMenu"; import CardMenu from 'Components/Dashboard/components/WidgetWrapper/CardMenu';
import AlertButton from "Components/Dashboard/components/WidgetWrapper/AlertButton"; import AlertButton from 'Components/Dashboard/components/WidgetWrapper/AlertButton';
interface Props { interface Props {
className?: string; className?: string;
widget?: any; widget?: any;
index?: number; index?: number;
moveListItem?: any; moveListItem?: any;
isPreview?: boolean; isPreview?: boolean;
isTemplate?: boolean; isTemplate?: boolean;
dashboardId?: string; dashboardId?: string;
siteId?: string; siteId?: string;
active?: boolean; active?: boolean;
history?: any; history?: any;
onClick?: () => void; onClick?: () => void;
isWidget?: boolean; isWidget?: boolean;
hideName?: boolean; hideName?: boolean;
grid?: string; grid?: string;
isGridView?: boolean; isGridView?: boolean;
showMenu?: boolean;
} }
function WidgetWrapperNew(props: Props & RouteComponentProps) { function WidgetWrapperNew(props: Props & RouteComponentProps) {
const {dashboardStore} = useStore(); const { dashboardStore } = useStore();
const { const {
isWidget = false, isWidget = false,
active = false, active = false,
index = 0, index = 0,
moveListItem = null, moveListItem = null,
isPreview = false, isPreview = false,
isTemplate = false, isTemplate = false,
siteId, siteId,
grid = '', grid = '',
isGridView = false, isGridView = false,
} = props; showMenu = false
const widget: any = props.widget; } = props;
const isTimeSeries = widget.metricType === TIMESERIES; const widget: any = props.widget;
const isPredefined = widget.metricType === 'predefined'; const isTimeSeries = widget.metricType === TIMESERIES;
const dashboard = dashboardStore.selectedDashboard; const isPredefined = widget.metricType === 'predefined';
const dashboard = dashboardStore.selectedDashboard;
const [{isDragging}, dragRef] = useDrag({ const [{ isDragging }, dragRef] = useDrag({
type: 'item', type: 'item',
item: {index, grid}, item: { index, grid },
collect: (monitor) => ({ collect: (monitor) => ({
isDragging: monitor.isDragging(), isDragging: monitor.isDragging()
}), })
}); });
const [{isOver, canDrop}, dropRef] = useDrop({ const [{ isOver, canDrop }, dropRef] = useDrop({
accept: 'item', accept: 'item',
drop: (item: any) => { drop: (item: any) => {
if (item.index === index || item.grid !== grid) return; if (item.index === index || item.grid !== grid) return;
moveListItem(item.index, index); moveListItem(item.index, index);
}, },
canDrop(item) { canDrop(item) {
return item.grid === grid; return item.grid === grid;
}, },
collect: (monitor: any) => ({ collect: (monitor: any) => ({
isOver: monitor.isOver(), isOver: monitor.isOver(),
canDrop: monitor.canDrop(), canDrop: monitor.canDrop()
}), })
}); });
const onChartClick = () => { const onChartClick = () => {
// if (!isWidget || isPredefined) return; // if (!isWidget || isPredefined) return;
props.history.push( props.history.push(
withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId) withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId)
);
};
const ref: any = useRef(null);
const dragDropRef: any = dragRef(dropRef(ref));
const addOverlay =
isTemplate ||
(!isPredefined &&
isWidget &&
widget.metricOf !== FilterKey.ERRORS &&
widget.metricOf !== FilterKey.SESSIONS);
return (
<Card
className={cn(
'relative group rounded-lg',
'col-span-' + widget.config.col,
{'hover:shadow-sm': !isTemplate && isWidget},
)}
style={{
userSelect: 'none',
opacity: isDragging ? 0.5 : 1,
borderColor:
(canDrop && isOver) || active ? '#394EFF' : isPreview ? 'transparent' : '#EEEEEE',
}}
ref={dragDropRef}
onClick={props.onClick ? props.onClick : () => null}
id={`widget-${widget.widgetId}`}
title={!props.hideName ? widget.name : null}
extra={isWidget ? [
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && !isGridView && (
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId}/>
)}
{!isTemplate && !isGridView && (
<CardMenu card={widget} key="card-menu"/>
)}
</div>
] : []}
styles={{
header: {
padding: '0 14px',
borderBottom: 'none',
minHeight: 44,
fontWeight: 500,
fontSize: 14,
},
body: {
padding: 0,
},
}}
>
{!isTemplate && isWidget && isPredefined && (
<Tooltip title="Cannot drill down system provided metrics">
<div
className={cn(stl.drillDownMessage, 'disabled text-gray text-sm invisible group-hover:visible')}>
{'Cannot drill down system provided metrics'}
</div>
</Tooltip>
)}
{addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate}/>}
<LazyLoad offset={!isTemplate ? 100 : 600}>
<div className="px-4" onClick={onChartClick}>
<WidgetChart
isPreview={isPreview}
metric={widget}
isTemplate={isTemplate}
isWidget={isWidget}
/>
</div>
</LazyLoad>
</Card>
); );
};
const ref: any = useRef(null);
const dragDropRef: any = dragRef(dropRef(ref));
const addOverlay =
isTemplate ||
(!isPredefined &&
isWidget &&
widget.metricOf !== FilterKey.ERRORS &&
widget.metricOf !== FilterKey.SESSIONS);
return (
<Card
className={cn(
'relative group rounded-lg',
'col-span-' + widget.config.col,
{ 'hover:shadow-sm': !isTemplate && isWidget }
)}
style={{
userSelect: 'none',
opacity: isDragging ? 0.5 : 1,
borderColor:
(canDrop && isOver) || active ? '#394EFF' : isPreview ? 'transparent' : '#EEEEEE'
}}
ref={dragDropRef}
onClick={props.onClick ? props.onClick : () => null}
id={`widget-${widget.widgetId}`}
title={!props.hideName ? widget.name : null}
extra={[
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && !isGridView && (
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} />
)}
{showMenu && (
<CardMenu card={widget} key="card-menu" />
)}
</div>
]}
styles={{
header: {
padding: '0 14px',
borderBottom: 'none',
minHeight: 44,
fontWeight: 500,
fontSize: 14
},
body: {
padding: 0
}
}}
>
{!isTemplate && isWidget && isPredefined && (
<Tooltip title="Cannot drill down system provided metrics">
<div
className={cn(stl.drillDownMessage, 'disabled text-gray text-sm invisible group-hover:visible')}>
{'Cannot drill down system provided metrics'}
</div>
</Tooltip>
)}
{addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />}
<LazyLoad offset={!isTemplate ? 100 : 600}>
<div className="px-4" onClick={onChartClick}>
<WidgetChart
isPreview={isPreview}
metric={widget}
isTemplate={isTemplate}
isWidget={isWidget}
/>
</div>
</LazyLoad>
</Card>
);
} }
export default withRouter(observer(WidgetWrapperNew)); export default withRouter(observer(WidgetWrapperNew));

View file

@ -62,7 +62,7 @@ function FunnelBar(props: Props) {
{/* @ts-ignore */} {/* @ts-ignore */}
<div className="flex items-center"> <div className="flex items-center">
<Icon name="arrow-right-short" size="20" color="green" /> <Icon name="arrow-right-short" size="20" color="green" />
<span className="mx-1 font-medium">{filter.sessionsCount} Sessions</span> <span className="mx-1">{filter.sessionsCount} Sessions</span>
<span className="color-gray-medium text-sm"> <span className="color-gray-medium text-sm">
({filter.completedPercentage}%) Completed ({filter.completedPercentage}%) Completed
</span> </span>
@ -70,7 +70,7 @@ function FunnelBar(props: Props) {
<Space className="items-center"> <Space className="items-center">
<Icon name="caret-down-fill" color={filter.droppedCount > 0 ? 'red' : 'gray-light'} size={16} /> <Icon name="caret-down-fill" color={filter.droppedCount > 0 ? 'red' : 'gray-light'} size={16} />
<span <span
className={'font-medium mx-1 ' + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>{filter.droppedCount} Sessions</span> className={'mx-1 ' + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>{filter.droppedCount} Sessions</span>
<span <span
className={'text-sm ' + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>({filter.droppedPercentage}%) Dropped</span> className={'text-sm ' + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>({filter.droppedPercentage}%) Dropped</span>
</Space> </Space>

View file

@ -132,7 +132,7 @@ export const Stage = observer(({ stage, index, isWidget, uxt, focusStage, focuse
> >
<IndexNumber index={index} /> <IndexNumber index={index} />
{!uxt ? <Funnelbar index={index} filter={stage} focusStage={focusStage} focusedFilter={focusedFilter} /> : <UxTFunnelBar filter={stage} />} {!uxt ? <Funnelbar index={index} filter={stage} focusStage={focusStage} focusedFilter={focusedFilter} /> : <UxTFunnelBar filter={stage} />}
{!isWidget && !uxt && <BarActions bar={stage} />} {/*{!isWidget && !uxt && <BarActions bar={stage} />}*/}
</div> </div>
) : ( ) : (
<></> <></>