Merge branch 'dashboards-redesign' of https://github.com/openreplay/openreplay into dashboards-redesign

This commit is contained in:
Sudheer Salavadi 2024-06-27 23:46:51 +05:30
commit 13631765e4
25 changed files with 780 additions and 460 deletions

View file

@ -1 +0,0 @@
export { default } from './CustomMetriLineChart';

View file

@ -1,7 +1,7 @@
import React from 'react'
import { Styles } from '../../common';
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
import { LineChart, Line, Legend } from 'recharts';
import {Styles} from '../../common';
import {ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip} from 'recharts';
import {LineChart, Line, Legend} from 'recharts';
interface Props {
data: any;
@ -9,46 +9,56 @@ interface Props {
// seriesMap: any;
colors: any;
onClick?: (event, index) => void;
yaxis?: any;
label?: string;
}
function CustomMetriLineChart(props: Props) {
const { data = { chart: [], namesMap: [] }, params, colors, onClick = () => null } = props;
function CustomMetricLineChart(props: Props) {
const {
data = {chart: [], namesMap: []},
params,
colors,
onClick = () => null,
yaxis = {...Styles.yaxis},
label = 'Number of Sessions'
} = props;
return (
<ResponsiveContainer height={ 240 } width="100%">
<ResponsiveContainer height={240} width="100%">
<LineChart
data={ data.chart }
data={data.chart}
margin={Styles.chartMargins}
// syncId={ showSync ? "domainsErrors_4xx" : undefined }
onClick={onClick}
// isAnimationActive={ false }
>
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEEEEE"/>
<XAxis
{...Styles.xaxis}
dataKey="time"
interval={params.density/7}
interval={params.density / 7}
/>
<YAxis
{...Styles.yaxis}
<YAxis
{...yaxis}
allowDecimals={false}
tickFormatter={val => Styles.tickFormatter(val)}
label={{
label={{
...Styles.axisLabelLeft,
value: "Number of Sessions"
value: label || "Number of Sessions"
}}
/>
<Legend />
<Legend/>
<Tooltip {...Styles.tooltip} />
{ Array.isArray(data.namesMap) && data.namesMap.map((key, index) => (
{Array.isArray(data.namesMap) && data.namesMap.map((key, index) => (
<Line
key={key}
name={key}
type="monotone"
dataKey={key}
stroke={colors[index]}
fillOpacity={ 1 }
strokeWidth={ 2 }
strokeOpacity={ 0.6 }
fillOpacity={1}
strokeWidth={2}
strokeOpacity={0.6}
// fill="url(#colorCount)"
dot={false}
/>
@ -58,4 +68,4 @@ function CustomMetriLineChart(props: Props) {
)
}
export default CustomMetriLineChart
export default CustomMetricLineChart

View file

@ -0,0 +1 @@
export { default } from './CustomMetricLineChart';

View file

@ -46,30 +46,28 @@ const cols = [
interface Props {
data: any
metric?: any
isTemplate?: boolean
}
function CallWithErrors(props: Props) {
const { data, metric } = props;
const { data } = props;
const [search, setSearch] = React.useState('')
const test = (value = '', serach: any) => getRE(serach, 'i').test(value);
const _data = search ? metric.data.chart.filter((i: any) => test(i.urlHostpath, search)) : metric.data.chart;
const _data = search ? data.chart.filter((i: any) => test(i.urlHostpath, search)) : data.chart;
const write = ({ target: { name, value } }: any) => {
setSearch(value)
};
return (
<NoContent
size="small"
title={NO_METRIC_DATA}
show={ metric.data.chart.length === 0 }
show={ data.chart.length === 0 }
style={{ height: '240px'}}
>
<div style={{ height: '240px'}}>
<div className={ cn(stl.topActions, 'py-3 flex text-right')}>
<input disabled={metric.data.chart.length === 0} className={stl.searchField} name="search" placeholder="Filter by Path" onChange={write} />
<input disabled={data.chart.length === 0} className={stl.searchField} name="search" placeholder="Filter by Path" onChange={write} />
</div>
<Table
small

View file

@ -1,54 +1,24 @@
import ExampleFunnel from "./Examples/Funnel";
import ExamplePath from "./Examples/Path";
import ExampleTrend from "./Examples/Trend";
import Trend from "./Examples/Trend";
import PerfBreakdown from "./Examples/PerfBreakdown";
import BarChartCard from "./Examples/BarChart";
import SlowestDomain from "./Examples/SlowestDomain";
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,
RESOURCE_MONITORING,
TABLE,
TIMESERIES,
USER_PATH,
WEB_VITALS
} from "App/constants/card";
import {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";
import Trend from "./Examples/Trend";
import Bars from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Bars";
import ByIssues from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByIssues";
import InsightsExample from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/InsightsExample";
// const TYPE = {
// FUNNEL: 'funnel',
// PATH_FINDER: 'path-finder',
// TREND: 'trend',
// SESSIONS_BY: 'sessions-by',
// BREAKDOWN: 'breakdown',
// SLOWEST_DOMAIN: 'slowest-domain',
// SESSIONS_BY_ERRORS: 'sessions-by-errors',
// SESSIONS_BY_ISSUES: 'sessions-by-issues',
// SESSIONS_BY_BROWSER: 'sessions-by-browser',
// SESSIONS_BY_SYSTEM: 'sessions-by-system',
// SESSIONS_BY_COUNTRY: 'sessions-by-country',
// SESSIONS_BY_URL: 'sessions-by-url',
//
//
// ERRORS_JS: 'js-errors',
// ERRORS_BY_ORIGIN: 'errors-by-origin',
// ERRORS_BY_DOMAIN: 'errors-by-domain',
// ERRORS_BY_TYPE: 'errors-by-type',
// CALLS_WITH_ERRORS: 'calls-with-errors',
// ERRORS_4XX: '4xx-errors',
// ERRORS_5XX: '5xx-errors',
// }
import ByUser from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUser";
import BarChartCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/BarChart";
import AreaChartCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard";
import CallsWithErrorsExample
from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/CallsWithErrorsExample";
export const CARD_CATEGORY = {
PRODUCT_ANALYTICS: 'product-analytics',
@ -85,6 +55,33 @@ export const CARD_LIST: CardType[] = [
cardType: FUNNEL,
category: CARD_CATEGORIES[0].key,
example: ExampleFunnel,
width: 4,
height: 356,
data: {
stages: [
{
"value": [
"/sessions"
],
"type": "location",
"operator": "contains",
"sessionsCount": 1586,
"dropPct": null,
"usersCount": 470,
"dropDueToIssues": 0
},
{
"value": [],
"type": "click",
"operator": "onAny",
"sessionsCount": 1292,
"dropPct": 18,
"usersCount": 450,
"dropDueToIssues": 294
}
],
totalDropDueToIssues: 294
}
},
{
title: 'Path Finder',
@ -94,11 +91,18 @@ export const CARD_LIST: CardType[] = [
example: ExamplePath,
},
{
title: 'Trend',
title: 'Sessions Trend',
key: TIMESERIES,
cardType: TIMESERIES,
metricOf: 'sessionCount',
category: CARD_CATEGORIES[0].key,
data: {
chart: generateTimeSeriesData(),
label: "Number of Sessions",
namesMap: [
"Series 1"
]
},
example: ExampleTrend,
},
@ -128,7 +132,14 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.CPU,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "CPU Load (%)",
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -137,7 +148,13 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.CRASHES,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -146,7 +163,14 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.FPS,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "Frames Per Second",
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -155,7 +179,14 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.PAGES_DOM_BUILD_TIME,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "DOM Build Time (ms)",
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -164,7 +195,15 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.MEMORY_CONSUMPTION,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "JS Heap Size (MB)",
unit: 'mb',
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -173,7 +212,14 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.PAGES_RESPONSE_TIME,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "Page Response Time (ms)",
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -182,7 +228,14 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
label: "Number of Calls",
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -191,7 +244,13 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateBarChartDate(),
namesMap: [
"Series 1"
]
},
example: BarChartCard,
},
{
@ -200,6 +259,7 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.SESSIONS_PER_BROWSER,
category: CARD_CATEGORIES[1].key,
data: generateRandomBarsData(),
example: Bars,
},
@ -209,6 +269,7 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.SLOWEST_DOMAINS,
category: CARD_CATEGORIES[1].key,
data: generateRandomBarsData(),
example: Bars,
},
@ -218,7 +279,13 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.SPEED_LOCATION,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -227,7 +294,13 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.TIME_TO_RENDER,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
{
@ -236,7 +309,13 @@ export const CARD_LIST: CardType[] = [
cardType: PERFORMANCE,
metricOf: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES,
category: CARD_CATEGORIES[1].key,
example: Trend,
data: {
chart: generateAreaData(),
namesMap: [
"Series 1"
]
},
example: AreaChartCard,
},
@ -247,7 +326,7 @@ export const CARD_LIST: CardType[] = [
cardType: TABLE,
metricOf: FilterKey.USERID,
category: CARD_CATEGORIES[2].key,
example: ByBrowser,
example: ByUser,
},
{
@ -299,7 +378,10 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS,
category: CARD_CATEGORIES[3].key,
example: PerfBreakdown,
data: {
chart: generateBarChartDate(),
},
example: BarChartCard,
},
{
title: 'Errors by Origin',
@ -307,7 +389,10 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.RESOURCES_BY_PARTY,
category: CARD_CATEGORIES[3].key,
example: PerfBreakdown,
data: {
chart: generateBarChartDate(),
},
example: BarChartCard,
},
{
title: 'Errors by Domain',
@ -324,7 +409,10 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.ERRORS_PER_TYPE,
category: CARD_CATEGORIES[3].key,
example: PerfBreakdown,
data: {
chart: generateBarChartDate(),
},
example: BarChartCard,
},
{
title: 'Calls with Errors',
@ -332,7 +420,33 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.CALLS_ERRORS,
category: CARD_CATEGORIES[3].key,
example: PerfBreakdown,
width: 4,
data: {
chart: [
{
"method": "GET",
"urlHostpath": 'https://openreplay.com',
"allRequests": 1333,
"4xx": 1333,
"5xx": 0
},
{
"method": "POST",
"urlHostpath": 'https://company.domain.com',
"allRequests": 10,
"4xx": 10,
"5xx": 0
},
{
"method": "PUT",
"urlHostpath": 'https://example.com',
"allRequests": 3,
"4xx": 3,
"5xx": 0
}
],
},
example: CallsWithErrorsExample,
},
{
@ -341,7 +455,14 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.DOMAINS_ERRORS_4XX,
category: CARD_CATEGORIES[3].key,
example: Trend,
data: {
chart: generateTimeSeriesData(),
label: "Number of Errors",
namesMap: [
"Series 1"
]
},
example: ExampleTrend,
},
{
@ -350,7 +471,14 @@ export const CARD_LIST: CardType[] = [
cardType: ERRORS,
metricOf: FilterKey.DOMAINS_ERRORS_5XX,
category: CARD_CATEGORIES[3].key,
example: Trend,
data: {
chart: generateTimeSeriesData(),
label: "Number of Errors",
namesMap: [
"Series 1"
]
},
example: ExampleTrend,
},
@ -588,3 +716,46 @@ function generateWebVitalData(): { value: number, chart: { timestamp: number, va
unit: "%"
};
}
function generateTimeSeriesData(): any[] {
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"];
const pointsPerMonth = 3; // Number of points for each month
const data = months.flatMap((month, monthIndex) =>
Array.from({length: pointsPerMonth}, (_, pointIndex) => ({
time: month,
"Series 1": Math.floor(Math.random() * 90),
timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000
}))
);
return data;
}
function generateAreaData(): any[] {
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"];
const pointsPerMonth = 3; // Number of points for each month
const data = months.flatMap((month, monthIndex) =>
Array.from({length: pointsPerMonth}, (_, pointIndex) => ({
time: month,
"value": Math.floor(Math.random() * 90),
timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000
}))
);
return data;
}
function generateRandomValue(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function generateBarChartDate(): any[] {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
return months.map(month => ({
time: month,
value: generateRandomValue(1000, 5000),
}));
}

View file

@ -0,0 +1,82 @@
import React from 'react';
import {NoContent} from 'UI';
import {
AreaChart, Area,
CartesianGrid, Tooltip,
ResponsiveContainer,
XAxis, YAxis
} from 'recharts';
import {NO_METRIC_DATA} from 'App/constants/messages'
import {AvgLabel, Styles} from "Components/Dashboard/Widgets/common";
import ExCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard";
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
onClick?: any;
data?: any,
}
// interface Props {
// data: any,
// label?: string
// }
function AreaChartCard(props: Props) {
const {data} = props;
const gradientDef = Styles.gradientDef();
return (
<ExCard
{...props}
title={
<div className={'flex items-center gap-2'}>
<div>{props.title}</div>
</div>
}
>
<NoContent
size="small"
title={NO_METRIC_DATA}
show={data?.chart.length === 0}
>
<>
{/*<div className="flex items-center justify-end mb-3">*/}
{/* <AvgLabel text="Avg" className="ml-3" count={data?.value}/>*/}
{/*</div>*/}
<ResponsiveContainer height={207} width="100%">
<AreaChart
data={data?.chart}
margin={Styles.chartMargins}
>
{gradientDef}
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEEEEE"/>
<XAxis {...Styles.xaxis} dataKey="time" interval={3}/>
<YAxis
{...Styles.yaxis}
allowDecimals={false}
tickFormatter={val => Styles.tickFormatter(val)}
label={{...Styles.axisLabelLeft, value: data?.label}}
/>
<Tooltip {...Styles.tooltip} />
<Area
name="Avg"
type="monotone"
dataKey="value"
stroke={Styles.strokeColor}
fillOpacity={1}
strokeWidth={2}
strokeOpacity={0.8}
fill={'url(#colorCount)'}
/>
</AreaChart>
</ResponsiveContainer>
</>
</NoContent>
</ExCard>
);
}
export default AreaChartCard;

View file

@ -6,64 +6,60 @@ import {PERFORMANCE} from "App/constants/card";
import {Bar, BarChart, CartesianGrid, Legend, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts";
import {Styles} from "Components/Dashboard/Widgets/common";
const _data = [
{
name: 'Jan',
uv: 4000,
pv: 2400,
},
{
name: 'Feb',
uv: 3000,
pv: 1398,
},
{
name: 'Mar',
uv: 2000,
pv: 9800,
},
{
name: 'Apr',
uv: 2780,
pv: 3908,
},
{
name: 'May',
uv: 1890,
pv: 4800,
},
{
name: 'Jun',
uv: 2390,
pv: 3800,
},
{
name: 'Jul',
uv: 3490,
pv: 4300,
},
];
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
onClick?: any;
data?: any,
}
function BarChartCard(props: any) {
function BarChartCard(props: Props) {
return (
<ExCard
{...props}
>
<ResponsiveContainer width="100%" height="100%">
{/*<ResponsiveContainer width="100%" height="100%">*/}
{/* <BarChart*/}
{/* width={400}*/}
{/* height={280}*/}
{/* data={_data}*/}
{/* margin={Styles.chartMargins}*/}
{/* >*/}
{/* /!*<CartesianGrid strokeDasharray="3 3"/>*!/*/}
{/* <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEEEEE"/>*/}
{/* <XAxis {...Styles.xaxis} dataKey="name"/>*/}
{/* <YAxis {...Styles.yaxis} />*/}
{/* <Tooltip/>*/}
{/* <Legend/>*/}
{/* <Bar dataKey="pv" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue"/>}/>*/}
{/* /!*<Bar dataKey="uv" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple"/>}/>*!/*/}
{/* </BarChart>*/}
{/*</ResponsiveContainer>*/}
<ResponsiveContainer height={240} width="100%">
<BarChart
width={400}
height={280}
data={_data}
data={props.data?.chart}
margin={Styles.chartMargins}
>
{/*<CartesianGrid strokeDasharray="3 3"/>*/}
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEEEEE"/>
<XAxis {...Styles.xaxis} dataKey="name"/>
<YAxis {...Styles.yaxis} />
<Tooltip {...Styles.tooltip} />
<XAxis
{...Styles.xaxis}
dataKey="time"
// interval={21}
/>
<YAxis
{...Styles.yaxis}
tickFormatter={val => Styles.tickFormatter(val)}
label={{...Styles.axisLabelLeft, value: "Number of Errors"}}
allowDecimals={false}
/>
<Legend/>
<Bar dataKey="pv" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue"/>}/>
{/*<Bar dataKey="uv" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple"/>}/>*/}
<Tooltip {...Styles.tooltip} />
<Bar minPointSize={1} name={<span className="float">One</span>}
dataKey="value" stackId="a" fill={Styles.colors[0]}/>
{/*<Bar name={<span className="float">3<sup>rd</sup> Party</span>} dataKey="thirdParty" stackId="a"*/}
{/* fill={Styles.colors[2]}/>*/}
</BarChart>
</ResponsiveContainer>
</ExCard>

View file

@ -0,0 +1,23 @@
import React from 'react';
import ExCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard";
import CallWithErrors from "Components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors";
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
onClick?: any;
data?: any,
}
function CallsWithErrorsExample(props: Props) {
return (
<ExCard
{...props}
>
<CallWithErrors data={props.data}/>
</ExCard>
);
}
export default CallsWithErrorsExample;

View file

@ -13,17 +13,18 @@ function ExCard({
onCard: (card: string) => void;
height?: number;
}) {
return (
<div
className={'rounded-lg overflow-hidden border border-transparent p-4 bg-white hover:border-blue hover:shadow-sm'}
style={{width: '100%', height: height || 286}}
>
<div className={'font-medium text-lg'}>{title}</div>
<div className={'flex flex-col gap-2 mt-2 cursor-pointer'}
style={{height: height ? height - 50 : 236,}}
onClick={() => onCard(type)}>{children}</div>
</div>
);
return (
<div
className={'rounded-lg overflow-hidden border border-transparent p-4 bg-white hover:border-blue hover:shadow-sm relative'}
style={{width: '100%', height: height || 286}}
>
<div className="absolute inset-0 z-10 cursor-pointer" onClick={() => onCard(type)}></div>
<div className={'font-medium text-lg'}>{title}</div>
<div className={'flex flex-col gap-2 mt-2 cursor-pointer'}
style={{height: height ? height - 50 : 236}}
onClick={() => onCard(type)}>{children}</div>
</div>
);
}
export default ExCard

View file

@ -1,54 +1,67 @@
import { ArrowRight } from 'lucide-react';
import {ArrowRight} from 'lucide-react';
import React from 'react';
import ExCard from './ExCard';
import {FUNNEL} from "App/constants/card";
import FunnelWidget from "Components/Funnels/FunnelWidget/FunnelWidget";
import Funnel from "App/mstore/types/funnel";
function ExampleFunnel(props: any) {
const steps = [
{
progress: 500,
},
{
progress: 250,
},
{
progress: 100,
},
];
return (
<ExCard
{...props}
>
<>
{steps.map((step, index) => (
<div key={index}>
<div>Step {index + 1}</div>
<div className={'rounded flex items-center w-full overflow-hidden'}>
<div
style={{
backgroundColor: step.progress <= 100 ? '#394EFF' : '#E2E4F6',
width: `${(step.progress / 500) * 100}%`,
height: 30,
}}
/>
<div
style={{
width: `${((500 - step.progress) / 500) * 100}%`,
height: 30,
background: '#FFF1F0',
}}
/>
</div>
<div className={'flex items-center gap-2'}>
<ArrowRight size={14} color={'#8C8C8C'} strokeWidth={1} />
<div className={'text-disabled-text'}>{step.progress}</div>
</div>
</div>
))}
</>
</ExCard>
);
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
data?: any,
}
function ExampleFunnel(props: Props) {
// const steps = [
// {
// progress: 500,
// },
// {
// progress: 250,
// },
// {
// progress: 100,
// },
// ];
const _data = {
funnel: new Funnel().fromJSON(props.data)
}
return (
<ExCard
{...props}
>
<FunnelWidget data={_data} isWidget={true}/>
{/*<>*/}
{/* {steps.map((step, index) => (*/}
{/* <div key={index}>*/}
{/* <div>Step {index + 1}</div>*/}
{/* <div className={'rounded flex items-center w-full overflow-hidden'}>*/}
{/* <div*/}
{/* style={{*/}
{/* backgroundColor: step.progress <= 100 ? '#394EFF' : '#E2E4F6',*/}
{/* width: `${(step.progress / 500) * 100}%`,*/}
{/* height: 30,*/}
{/* }}*/}
{/* />*/}
{/* <div*/}
{/* style={{*/}
{/* width: `${((500 - step.progress) / 500) * 100}%`,*/}
{/* height: 30,*/}
{/* background: '#FFF1F0',*/}
{/* }}*/}
{/* />*/}
{/* </div>*/}
{/* <div className={'flex items-center gap-2'}>*/}
{/* <ArrowRight size={14} color={'#8C8C8C'} strokeWidth={1}/>*/}
{/* <div className={'text-disabled-text'}>{step.progress}</div>*/}
{/* </div>*/}
{/* </div>*/}
{/* ))}*/}
{/*</>*/}
</ExCard>
);
}
export default ExampleFunnel;

View file

@ -0,0 +1,53 @@
import React from 'react';
import {Avatar, Icon} from 'UI';
import ExCard from '../ExCard';
import ByComponent from './Component';
import {hashString} from "Types/session/session";
function ByUser(props: any) {
const rows = [
{
label: 'Demo User',
progress: 85,
value: '2.5K',
icon: <Avatar seed={hashString("a")}/>,
},
{
label: 'Admin User',
progress: 25,
value: '405',
icon: <Avatar seed={hashString("b")}/>,
},
{
label: 'Management User',
progress: 5,
value: '302',
icon: <Avatar seed={hashString("c")}/>,
},
{
label: 'Sales User',
progress: 3,
value: '194',
icon: <Avatar seed={hashString("d")}/>,
},
{
label: 'Marketing User',
progress: 1,
value: '57',
icon: <Avatar seed={hashString("e")}/>,
},
];
const lineWidth = 200;
return (
<ByComponent
{...props}
rows={rows}
lineWidth={lineWidth}
/>
);
}
export default ByUser;

View file

@ -1,96 +1,43 @@
import { Segmented } from 'antd';
import React from 'react';
import ExCard from './ExCard';
import AreaChartCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard";
import CustomMetricLineChart from "Components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart";
import {Styles} from "Components/Dashboard/Widgets/common";
function ExampleTrend(props: any) {
const rows = [50, 40, 30, 20, 10];
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May'];
interface Props {
title: string;
type: string;
onCard: (card: string) => void;
onClick?: any;
data?: any,
}
const [isMulti, setIsMulti] = React.useState(false);
return (
<ExCard
{...props}
// onCard={onCard}
// type={'trend' + (isMulti ? '-multi' : '-single')}
title={
<div className={'flex items-center gap-2'}>
<div>{props.title}</div>
<div className={'font-normal'}>
<Segmented
options={[
{ label: 'Single-Series', value: 'single' },
{ label: 'Multi-Series', value: 'multi' },
]}
onChange={(v) => setIsMulti(v === 'multi')}
size='small'
/>
</div>
</div>
}
>
<div className={'relative'}>
<div className={'flex flex-col gap-4'}>
{rows.map((r) => (
<div className={'flex items-center gap-2'}>
<div className={'text-gray-dark'}>{r}K</div>
<div className="border-t border-dotted border-gray-lighter w-full"></div>
</div>
))}
</div>
<div className={'ml-4 flex items-center justify-around w-full'}>
{months.map((m) => (
<div className={'text-gray-dark'}>{m}</div>
))}
</div>
<div style={{ position: 'absolute', top: 50, left: 50 }}>
<svg
width="310"
height="65"
viewBox="0 0 310 65"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 55.3477L12.3778 48.184C23.7556 41.0204 46.5111 26.6931 69.2667 16.6138C92.0222 6.53442 114.778 0.703032 137.533 1.58821C160.289 2.47339 183.044 10.0751 205.8 18.5821C228.556 27.0891 251.311 36.5013 274.067 34.6878C296.822 32.8743 319.578 19.8351 342.333 12.8615C365.089 5.88789 387.844 4.97992 410.6 13.8627C433.356 22.7454 456.111 41.4189 478.867 51.0086C501.622 60.5983 524.378 61.1042 547.133 60.1189C569.889 59.1335 592.644 56.6569 615.4 51.4908C638.156 46.3247 660.911 38.4691 683.667 33.0755C706.422 27.6819 729.178 24.7502 751.933 18.4788C774.689 12.2073 797.444 2.59602 820.2 4.96937C842.956 7.34271 865.711 21.7007 888.467 24.9543C911.222 28.2078 933.978 20.357 956.733 24.9861C979.489 29.6152 1002.24 46.7243 1013.62 55.2788L1025 63.8333"
stroke="#394EFF"
strokeWidth="2"
strokeLinecap="round"
function ExampleTrend(props: Props) {
return (
<ExCard
{...props}
title={
<div className={'flex items-center gap-2'}>
<div>{props.title}</div>
</div>
}
>
{/*<AreaChartCard data={props.data} label={props.data?.label}/>*/}
<CustomMetricLineChart
data={props.data}
colors={Styles.customMetricColors}
params={{
density: 21,
}}
yaxis={
{...Styles.yaxis, domain: [0, 100]}
}
label={props.data?.label}
onClick={props.onClick}
/>
</svg>
</div>
{isMulti ? (
<div style={{ position: 'absolute', top: 50, left: 50 }}>
<svg
width="310"
height="66"
viewBox="0 0 310 66"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1028 60.5095L1016.64 55.3116C1005.28 50.1137 982.569 19.7179 959.854 12.4043C937.138 5.09082 914.423 0.85959 891.708 1.50187C868.992 2.14416 846.277 7.65995 823.561 13.8326C800.846 20.0052 778.131 46.8346 755.415 45.5188C732.7 44.2029 709.984 34.7417 687.269 29.6817C664.554 24.6217 641.838 23.9629 619.123 30.4082C596.407 36.8535 573.692 50.4029 550.977 57.3611C528.261 64.3193 505.546 64.6864 482.83 63.9714C460.115 63.2565 437.4 61.4595 414.684 57.711C391.969 53.9625 369.253 48.2625 346.538 44.3489C323.823 40.4353 301.107 38.3081 278.392 33.7576C255.676 29.207 232.961 22.2331 210.246 23.9552C187.53 25.6773 164.815 36.0954 142.099 38.4562C119.384 40.8169 96.6686 35.1204 73.9532 38.4793C51.2378 41.8381 16.7156 44.2524 5.35794 50.4595L-5.99987 56.6666"
stroke="#24959A"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
</div>
) : null}
</div>
<div className={'flex gap-4 justify-center hidden'}>
<div className={'flex gap-2 items-center'}>
<div className={'w-4 h-4 rounded-full bg-main'} />
<div>Series 1</div>
</div>
<div className={'flex gap-2 items-center'}>
<div className={'w-4 h-4 rounded-full bg-tealx'} />
<div>Series 2</div>
</div>
</div>
</ExCard>
);
</ExCard>
);
}
export default ExampleTrend;

View file

@ -2,6 +2,7 @@ import React, {useEffect} from 'react';
import {Modal} from 'antd';
import SelectCard from './SelectCard';
import CreateCard from "Components/Dashboard/components/DashboardList/NewDashModal/CreateCard";
import colors from "tailwindcss/colors";
interface NewDashboardModalProps {
onClose: () => void;
@ -25,28 +26,35 @@ const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
return (
<>
<Modal
open={open}
onCancel={onClose}
width={900}
destroyOnClose={true}
footer={null}
closeIcon={false}
className='chooseDashboardCards'
centered>
<div>
<div className="flex flex-col gap-4">
{step === 0 && <SelectCard onClose={onClose}
selected={selectedCategory}
setSelectedCategory={setSelectedCategory}
onCard={() => setStep(step + 1)}
isLibrary={isAddingFromLibrary}/>}
{step === 1 && <CreateCard onBack={() => setStep(0)}/>}
</div>
<Modal
open={open}
onCancel={onClose}
width={900}
destroyOnClose={true}
footer={null}
closeIcon={false}
styles={{
content: {
backgroundColor: colors.gray[100],
}
}}
>
<div className="flex flex-col gap-4" style={{
height: 700,
overflowY: 'auto',
overflowX: 'hidden',
}}>
{step === 0 && <SelectCard onClose={onClose}
selected={selectedCategory}
setSelectedCategory={setSelectedCategory}
onCard={() => setStep(step + 1)}
isLibrary={isAddingFromLibrary}/>}
{step === 1 && <CreateCard onBack={() => setStep(0)}/>}
</div>
</Modal>
</>
);
)
;
};
export default NewDashboardModal;

View file

@ -22,6 +22,7 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
const [libraryQuery, setLibraryQuery] = React.useState<string>('');
const handleCardSelection = (card: string) => {
metricStore.init();
const selectedCard = CARD_LIST.find((c) => c.key === card) as CardType;
const cardData: any = {

View file

@ -1,5 +1,5 @@
import React, {useState, useRef, useEffect} from 'react';
import CustomMetriLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart';
import CustomMetricLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart';
import CustomMetricPercentage from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage';
import CustomMetricTable from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable';
import CustomMetricPieChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart';
@ -141,7 +141,7 @@ function WidgetChart(props: Props) {
if (metricType === TIMESERIES) {
if (viewType === 'lineChart') {
return (
<CustomMetriLineChart
<CustomMetricLineChart
data={data}
colors={colors}
params={params}
@ -236,7 +236,7 @@ function WidgetChart(props: Props) {
if (metricType === RETENTION) {
if (viewType === 'trend') {
return (
<CustomMetriLineChart
<CustomMetricLineChart
data={data}
colors={colors}
params={params}

View file

@ -53,7 +53,7 @@ const MetricTabs = ({metric, writeOption}: any) => {
}
return (
<Segmented options={tableOptions} onChange={onChange} selected={metric.metricOf} />
<Segmented options={tableOptions} onChange={onChange} selected={metric.metricOf}/>
)
}
@ -138,19 +138,26 @@ const MetricOptions = ({metric, writeOption}: any) => {
);
};
const PathAnalysisFilter = ({metric}) => (
<div className='form-group flex flex-col'>
{metric.startType === 'start' ? 'Start Point' : 'End Point'}
<FilterItem
hideDelete
filter={metric.startPoint}
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
onUpdate={val => metric.updateStartPoint(val)}
onRemoveFilter={() => {
}}
/>
</div>
);
const PathAnalysisFilter = observer(({metric}: any) => (
<Card styles={{
body: {padding: '4px 20px'},
header: {padding: '4px 20px', fontSize: '14px', fontWeight: 'bold', borderBottom: 'none'},
}}
title={metric.startType === 'start' ? 'Start Point' : 'End Point'}
>
<div className='form-group flex flex-col'>
{/*{metric.startType === 'start' ? 'Start Point' : 'End Point'}*/}
<FilterItem
hideDelete
filter={metric.startPoint}
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
onUpdate={val => metric.updateStartPoint(val)}
onRemoveFilter={() => {
}}
/>
</div>
</Card>
));
const SeriesList = observer(() => {
const {metricStore, dashboardStore, aiFiltersStore} = useStore();

View file

@ -35,7 +35,7 @@ function WidgetFormNew() {
const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(metric.metricType);
return isPredefined ? <PredefinedMessage/> : (
<>
<Space direction="vertical" className="w-full">
<AdditionalFilters/>
<Card
styles={{
@ -51,7 +51,7 @@ function WidgetFormNew() {
{hasFilters && (
<FilterSection metric={metric} excludeFilterKeys={excludeFilterKeys}/>
)}
</>
</Space>
);
}
@ -140,17 +140,23 @@ const FilterSection = observer(({metric, excludeFilterKeys}: any) => {
const PathAnalysisFilter = observer(({metric}: any) => (
<div className='form-group flex flex-col'>
{metric.startType === 'start' ? 'Start Point' : 'End Point'}
<FilterItem
hideDelete
filter={metric.startPoint}
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
onUpdate={val => metric.updateStartPoint(val)}
onRemoveFilter={() => {
}}
/>
</div>
<Card styles={{
body: {padding: '4px 20px'},
header: {padding: '4px 20px', fontSize: '14px', fontWeight: 'bold', borderBottom: 'none'},
}}
title={metric.startType === 'start' ? 'Start Point' : 'End Point'}
>
<div className='form-group flex flex-col'>
<FilterItem
hideDelete
filter={metric.startPoint}
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
onUpdate={val => metric.updateStartPoint(val)}
onRemoveFilter={() => {
}}
/>
</div>
</Card>
));
const AdditionalFilters = observer(() => {
@ -158,9 +164,9 @@ const AdditionalFilters = observer(() => {
const metric: any = metricStore.instance;
return (
<>
<Space direction="vertical" className="w-full">
{metric.metricType === USER_PATH && <PathAnalysisFilter metric={metric}/>}
</>
</Space>
)
});

View file

@ -52,7 +52,7 @@ function WidgetPredefinedChart(props: Props) {
case FilterKey.DOMAINS_ERRORS_5XX:
return <CallsErrors5xx data={data} metric={metric} />
case FilterKey.CALLS_ERRORS:
return <CallWithErrors isTemplate={isTemplate} data={data} metric={metric} />
return <CallWithErrors isTemplate={isTemplate} data={data} />
// PERFORMANCE
case FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES:

View file

@ -1,146 +1,148 @@
import { durationFormatted } from 'App/date';
import {durationFormatted} from 'App/date';
import React from 'react';
import FunnelStepText from './FunnelStepText';
import { Icon } from 'UI';
import {Icon} from 'UI';
import {Space} from "antd";
interface Props {
filter: any;
index?: number;
focusStage?: (index: number, isFocused: boolean) => void
focusedFilter?: number | null
filter: any;
index?: number;
focusStage?: (index: number, isFocused: boolean) => void
focusedFilter?: number | null
}
function FunnelBar(props: Props) {
const { filter, index, focusStage, focusedFilter } = props;
const {filter, index, focusStage, focusedFilter} = props;
const isFocused = focusedFilter && index ? focusedFilter === index - 1 : false;
return (
<div className="w-full mb-4">
<FunnelStepText filter={filter} />
<div
style={{
height: '25px',
width: '100%',
backgroundColor: '#f5f5f5',
position: 'relative',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
className="flex items-center"
style={{
width: `${filter.completedPercentageTotal}%`,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
backgroundColor: '#00b5ad',
}}
>
<div className="color-white absolute right-0 flex items-center font-medium mr-2 leading-3">
{filter.completedPercentageTotal}%
</div>
</div>
<div
style={{
width: `${100.1 - filter.completedPercentageTotal}%`,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
backgroundColor: isFocused ? 'rgba(204, 0, 0, 0.3)' : '#f5f5f5',
cursor: 'pointer',
}}
onClick={() => focusStage?.(index! - 1, filter.isActive)}
className={'hover:border border-red-lightest'}
/>
</div>
<div className="flex justify-between py-2">
{/* @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="color-gray-medium text-sm">
const isFocused = focusedFilter && index ? focusedFilter === index - 1 : false;
return (
<div className="w-full mb-4">
<FunnelStepText filter={filter}/>
<div
style={{
height: '25px',
width: '100%',
backgroundColor: '#f5f5f5',
position: 'relative',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
className="flex items-center"
style={{
width: `${filter.completedPercentageTotal}%`,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
backgroundColor: '#394EFF',
}}
>
<div className="color-white absolute right-0 flex items-center font-medium mr-2 leading-3">
{filter.completedPercentageTotal}%
</div>
</div>
<div
style={{
width: `${100.1 - filter.completedPercentageTotal}%`,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
backgroundColor: isFocused ? 'rgba(204, 0, 0, 0.3)' : '#f5f5f5',
cursor: 'pointer',
}}
onClick={() => focusStage?.(index! - 1, filter.isActive)}
className={'hover:border border-red-lightest'}
/>
</div>
<div className="flex justify-between py-2">
{/* @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="color-gray-medium text-sm">
({filter.completedPercentage}%) Completed
</span>
</div>
<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>
<span
className={"text-sm " + (filter.droppedCount > 0 ? 'color-red' : 'disabled')}>({filter.droppedPercentage}%) Dropped</span>
</Space>
</div>
</div>
{/* @ts-ignore */}
<div className="flex items-center">
<Icon name="caret-down-fill" color="red" size={16} />
<span className="font-medium mx-1 color-red">{filter.droppedCount} Sessions</span>
<span className="text-sm color-red">({filter.droppedPercentage}%) Dropped</span>
</div>
</div>
</div>
);
);
}
export function UxTFunnelBar(props: Props) {
const { filter } = props;
const {filter} = props;
return (
<div className="w-full mb-4">
<div className={'font-medium'}>{filter.title}</div>
<div
style={{
height: '25px',
width: '100%',
backgroundColor: '#f5f5f5',
position: 'relative',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
className="flex items-center"
style={{
width: `${(filter.completed/(filter.completed+filter.skipped))*100}%`,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
backgroundColor: '#00b5ad',
}}
>
<div className="color-white absolute right-0 flex items-center font-medium mr-2 leading-3">
{((filter.completed/(filter.completed+filter.skipped))*100).toFixed(1)}%
</div>
</div>
</div>
<div className="flex justify-between py-2">
{/* @ts-ignore */}
<div className={'flex items-center gap-4'}>
<div className="flex items-center">
<Icon name="arrow-right-short" size="20" color="green" />
<span className="mx-1 font-medium">{filter.completed}</span><span>completed this step</span>
</div>
<div className={'flex items-center'}>
<Icon name="clock" size="16" />
<span className="mx-1 font-medium">
return (
<div className="w-full mb-4">
<div className={'font-medium'}>{filter.title}</div>
<div
style={{
height: '25px',
width: '100%',
backgroundColor: '#f5f5f5',
position: 'relative',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
className="flex items-center"
style={{
width: `${(filter.completed / (filter.completed + filter.skipped)) * 100}%`,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
backgroundColor: '#00b5ad',
}}
>
<div className="color-white absolute right-0 flex items-center font-medium mr-2 leading-3">
{((filter.completed / (filter.completed + filter.skipped)) * 100).toFixed(1)}%
</div>
</div>
</div>
<div className="flex justify-between py-2">
{/* @ts-ignore */}
<div className={'flex items-center gap-4'}>
<div className="flex items-center">
<Icon name="arrow-right-short" size="20" color="green"/>
<span className="mx-1 font-medium">{filter.completed}</span><span>completed this step</span>
</div>
<div className={'flex items-center'}>
<Icon name="clock" size="16"/>
<span className="mx-1 font-medium">
{durationFormatted(filter.avgCompletionTime)}
</span>
<span>
<span>
Avg. completion time
</span>
</div>
</div>
</div>
{/* @ts-ignore */}
<div className="flex items-center">
<Icon name="caret-down-fill" color="red" size={16}/>
<span className="font-medium mx-1">{filter.skipped}</span><span> skipped</span>
</div>
</div>
</div>
{/* @ts-ignore */}
<div className="flex items-center">
<Icon name="caret-down-fill" color="red" size={16} />
<span className="font-medium mx-1">{filter.skipped}</span><span> skipped</span>
</div>
</div>
</div>
);
);
}
export default FunnelBar;
const calculatePercentage = (completed: number, dropped: number) => {
const total = completed + dropped;
if (dropped === 0) return 100;
if (total === 0) return 0;
const total = completed + dropped;
if (dropped === 0) return 100;
if (total === 0) return 0;
return Math.round((completed / dropped) * 100);
return Math.round((completed / dropped) * 100);
};

View file

@ -8,7 +8,7 @@ import { NoContent, Icon } from 'UI';
import { useModal } from 'App/components/Modal';
interface Props {
metric: Widget;
metric?: Widget;
isWidget?: boolean;
data: any;
}

View file

@ -1,6 +1,7 @@
import {Icon} from "UI";
import {Avatar, Icon} from "UI";
import React from "react";
import * as Flags from "country-flag-icons/react/3x2";
import {hashString} from "Types/session/session";
interface IconProvider {
getIcon(name: string): React.ReactNode;
@ -106,13 +107,20 @@ class OsIconProvider implements IconProvider {
}
}
class UserIconProvider implements IconProvider {
getIcon(name: string): React.ReactNode {
return <Avatar seed={hashString(name)}/>
}
}
export {
BrowserIconProvider,
CountryIconProvider,
IssueIconProvider,
UrlIconProvider,
DeviceIconProvider,
OsIconProvider
OsIconProvider,
UserIconProvider
};
export type {IconProvider};

View file

@ -128,7 +128,7 @@ export default class Filter {
addFunnelDefaultFilters() {
this.filters = []
this.addFilter({...filtersMap[FilterKey.CLICK], value: [''], operator: 'onAny'})
this.addFilter({...filtersMap[FilterKey.LOCATION], value: [''], operator: 'isAny'})
this.addFilter({...filtersMap[FilterKey.CLICK], value: [''], operator: 'onAny'})
}
}

View file

@ -5,7 +5,7 @@ import {
CountryIconProvider, DeviceIconProvider,
IconProvider,
IssueIconProvider, OsIconProvider,
UrlIconProvider
UrlIconProvider, UserIconProvider
} from "./IconProvider";
interface NameFormatter {
@ -68,6 +68,8 @@ export class SessionsByRow {
return {nameFormatter: new BaseFormatter(), iconProvider: new DeviceIconProvider()};
case 'platform':
return {nameFormatter: new BaseFormatter(), iconProvider: new OsIconProvider()};
case 'userId':
return {nameFormatter: new BaseFormatter(), iconProvider: new UserIconProvider()};
default:
return {nameFormatter: new BaseFormatter(), iconProvider: new DefaultIconProvider()};
}

View file

@ -412,14 +412,6 @@ p {
background-color: #E6E9FA;
}
.chooseDashboardCards .ant-modal-content{
background-color: #efefef;
border-radius: 0.75rem;
max-height: 700px;
overflow: hidden;
}
.dashboardDataPeriodSelector .dashboardMoreOptionsLabel{
display: none;
}
}

View file

@ -41,7 +41,7 @@ export function sortEvents(a: Record<string, any>, b: Record<string, any>) {
return aTs - bTs;
}
function hashString(s: string): number {
export function hashString(s: string): number {
let mul = 1;
let hash = 0;
for (let i = 0; i < s.length; i++) {