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 { NoContent } from 'UI';
import { Styles, AvgLabel } from '../../common';
import {
ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer,
import {
ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer,
XAxis, YAxis, ReferenceLine, Tooltip
} from 'recharts';
import { NO_METRIC_DATA } from 'App/constants/messages'
@ -41,26 +41,25 @@ const PercentileLine = props => {
interface Props {
data: any
metric?: any
}
function ResponseTimeDistribution(props: Props) {
const { data, metric } = props;
const { data } = props;
const colors = Styles.colors;
return (
<NoContent
size="small"
title={NO_METRIC_DATA}
show={ metric.data.chart.length === 0 }
show={ data.chart.length === 0 }
style={ { height: '240px' } }
>
<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 className="flex mb-4">
<ResponsiveContainer height={ 240 } width="100%">
<ComposedChart
data={metric.data.chart}
data={data.chart}
margin={Styles.chartMargins}
barSize={50}
>
@ -68,7 +67,7 @@ function ResponseTimeDistribution(props: Props) {
<XAxis
{...Styles.xaxis}
dataKey="responseTime"
label={{
label={{
...Styles.axisLabelLeft,
angle: 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" />
<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
key={i}
label={
@ -111,13 +110,13 @@ function ResponseTimeDistribution(props: Props) {
</ResponsiveContainer>
<ResponsiveContainer height={ 240 } width="10%">
<BarChart
data={metric.data.extremeValues}
data={data.extremeValues}
margin={Styles.chartMargins}
barSize={40}
>
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
<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} />
<Bar minPointSize={1} name="Extreme Values" dataKey="count" stackId="a" fill={colors[0]} />
</BarChart>

View file

@ -7,7 +7,17 @@ import ByBrowser from './Examples/SessionsBy/ByBrowser';
import BySystem from './Examples/SessionsBy/BySystem';
import ByCountry from './Examples/SessionsBy/ByCountry';
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 { Activity, BarChart, TableCellsMerge, TrendingUp } from 'lucide-react';
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';
import SpeedIndexByLocationExample
from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample';
import HeatmapsExample from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/HeatmapsExample';
export const CARD_CATEGORY = {
PRODUCT_ANALYTICS: 'product-analytics',
@ -90,6 +101,14 @@ export const CARD_LIST: CardType[] = [
totalDropDueToIssues: 294
}
},
{
title: 'Heatmaps',
key: CLICKMAP,
cardType: CLICKMAP,
metricOf: 'sessionCount',
category: CARD_CATEGORIES[0].key,
example: HeatmapsExample
},
{
title: 'Path Finder',
key: USER_PATH,
@ -122,6 +141,7 @@ export const CARD_LIST: CardType[] = [
example: ByIssues
},
{
title: '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}
siteId={siteId}
isWidget={false}
grid="other"
showMenu={true}
/>
</React.Fragment>
))

View file

@ -1,159 +1,161 @@
import React, {useRef} from 'react';
import React, { useRef } from 'react';
import cn from 'classnames';
import {Card, Tooltip, Button} from 'antd';
import {useDrag, useDrop} from 'react-dnd';
import { Card, Tooltip, Button } from 'antd';
import { useDrag, useDrop } from 'react-dnd';
import WidgetChart from '../WidgetChart';
import {observer} from 'mobx-react-lite';
import {useStore} from 'App/mstore';
import {withRouter, RouteComponentProps} from 'react-router-dom';
import {withSiteId, dashboardMetricDetails} from 'App/routes';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { withSiteId, dashboardMetricDetails } from 'App/routes';
import TemplateOverlay from './TemplateOverlay';
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 {TIMESERIES} from "App/constants/card";
import CardMenu from "Components/Dashboard/components/WidgetWrapper/CardMenu";
import AlertButton from "Components/Dashboard/components/WidgetWrapper/AlertButton";
import { TIMESERIES } from 'App/constants/card';
import CardMenu from 'Components/Dashboard/components/WidgetWrapper/CardMenu';
import AlertButton from 'Components/Dashboard/components/WidgetWrapper/AlertButton';
interface Props {
className?: string;
widget?: any;
index?: number;
moveListItem?: any;
isPreview?: boolean;
isTemplate?: boolean;
dashboardId?: string;
siteId?: string;
active?: boolean;
history?: any;
onClick?: () => void;
isWidget?: boolean;
hideName?: boolean;
grid?: string;
isGridView?: boolean;
className?: string;
widget?: any;
index?: number;
moveListItem?: any;
isPreview?: boolean;
isTemplate?: boolean;
dashboardId?: string;
siteId?: string;
active?: boolean;
history?: any;
onClick?: () => void;
isWidget?: boolean;
hideName?: boolean;
grid?: string;
isGridView?: boolean;
showMenu?: boolean;
}
function WidgetWrapperNew(props: Props & RouteComponentProps) {
const {dashboardStore} = useStore();
const {
isWidget = false,
active = false,
index = 0,
moveListItem = null,
isPreview = false,
isTemplate = false,
siteId,
grid = '',
isGridView = false,
} = props;
const widget: any = props.widget;
const isTimeSeries = widget.metricType === TIMESERIES;
const isPredefined = widget.metricType === 'predefined';
const dashboard = dashboardStore.selectedDashboard;
const { dashboardStore } = useStore();
const {
isWidget = false,
active = false,
index = 0,
moveListItem = null,
isPreview = false,
isTemplate = false,
siteId,
grid = '',
isGridView = false,
showMenu = false
} = props;
const widget: any = props.widget;
const isTimeSeries = widget.metricType === TIMESERIES;
const isPredefined = widget.metricType === 'predefined';
const dashboard = dashboardStore.selectedDashboard;
const [{isDragging}, dragRef] = useDrag({
type: 'item',
item: {index, grid},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const [{ isDragging }, dragRef] = useDrag({
type: 'item',
item: { index, grid },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [{isOver, canDrop}, dropRef] = useDrop({
accept: 'item',
drop: (item: any) => {
if (item.index === index || item.grid !== grid) return;
moveListItem(item.index, index);
},
canDrop(item) {
return item.grid === grid;
},
collect: (monitor: any) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
});
const [{ isOver, canDrop }, dropRef] = useDrop({
accept: 'item',
drop: (item: any) => {
if (item.index === index || item.grid !== grid) return;
moveListItem(item.index, index);
},
canDrop(item) {
return item.grid === grid;
},
collect: (monitor: any) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
});
const onChartClick = () => {
// if (!isWidget || isPredefined) return;
props.history.push(
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 onChartClick = () => {
// if (!isWidget || isPredefined) return;
props.history.push(
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={[
<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));

View file

@ -62,7 +62,7 @@ function FunnelBar(props: Props) {
{/* @ts-ignore */}
<div className="flex items-center">
<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">
({filter.completedPercentage}%) Completed
</span>
@ -70,7 +70,7 @@ function FunnelBar(props: Props) {
<Space className="items-center">
<Icon name="caret-down-fill" color={filter.droppedCount > 0 ? 'red' : 'gray-light'} size={16} />
<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
className={'text-sm ' + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>({filter.droppedPercentage}%) Dropped</span>
</Space>

View file

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