Merge branch 'dashboards-redesign' of https://github.com/openreplay/openreplay into dashboards-redesign
This commit is contained in:
commit
13631765e4
25 changed files with 780 additions and 460 deletions
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetriLineChart';
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './CustomMetricLineChart';
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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'})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue