change(ui): remove unused files
This commit is contained in:
parent
c2d9a9768a
commit
0ad417d0dc
153 changed files with 4 additions and 5311 deletions
|
|
@ -1,40 +0,0 @@
|
|||
import React from 'react'
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } from 'Types/app/period';
|
||||
import { ALL, DESKTOP, MOBILE } from 'Types/app/platform';
|
||||
import { connect } from 'react-redux';
|
||||
import { setPeriod, setPlatform } from 'Duck/dashboard';
|
||||
import cn from 'classnames';
|
||||
import styles from './DashboardHeader.module.css';
|
||||
import Filters from '../Filters/Filters';
|
||||
|
||||
export const PERIOD_OPTIONS = [
|
||||
{ text: 'Past 30 Min', value: LAST_30_MINUTES },
|
||||
{ text: 'Past 24 Hours', value: LAST_24_HOURS },
|
||||
{ text: 'Past 7 Days', value: LAST_7_DAYS },
|
||||
{ text: 'Past 30 Days', value: LAST_30_DAYS },
|
||||
{ text: 'Choose Date', value: CUSTOM_RANGE },
|
||||
];
|
||||
|
||||
const PLATFORM_OPTIONS = [
|
||||
{ text: 'All Platforms', value: ALL },
|
||||
{ text: 'Desktop', value: DESKTOP },
|
||||
{ text: 'Mobile', value: MOBILE }
|
||||
];
|
||||
|
||||
const DashboardHeader = props => {
|
||||
return (
|
||||
<div className={ cn(styles.header, 'w-full') }>
|
||||
<Filters />
|
||||
|
||||
<div className="flex items-center hidden">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
period: state.getIn([ 'dashboard', 'period' ]),
|
||||
platform: state.getIn([ 'dashboard', 'platform' ]),
|
||||
currentProjectId: state.getIn([ 'site', 'siteId' ]),
|
||||
sites: state.getIn([ 'site', 'list' ]),
|
||||
}), { setPeriod, setPlatform })(DashboardHeader)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
.dropdown {
|
||||
display: 'flex' !important;
|
||||
align-items: 'center';
|
||||
padding: 5px 8px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: #DDDDDD;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.dateInput {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: $gray-darkest;
|
||||
|
||||
&:hover {
|
||||
background-color: lightgray;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default as DashboardHeader } from './DashboardHeader';
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader } from 'UI';
|
||||
import { msToSec } from 'App/date';
|
||||
import { CountBadge, Divider, widgetHOC } from './common';
|
||||
|
||||
@widgetHOC('applicationActivity')
|
||||
export default class ApplicationActivity extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading } = this.props;
|
||||
return (
|
||||
<div className="flex-1 flex-shrink-0 flex justify-around items-center">
|
||||
<Loader loading={ loading } size="small">
|
||||
<CountBadge
|
||||
title="Avg. Page Load Time"
|
||||
unit="s"
|
||||
icon="window"
|
||||
count={ msToSec(data.avgPageLoad) }
|
||||
change={ data.avgPageLoadProgress }
|
||||
oppositeColors
|
||||
/>
|
||||
<Divider />
|
||||
<CountBadge
|
||||
title="Avg. Image Load Time"
|
||||
unit="ms"
|
||||
icon="eye"
|
||||
count={ data.avgImgLoad }
|
||||
change={ data.avgImgLoadProgress }
|
||||
oppositeColors
|
||||
/>
|
||||
<Divider />
|
||||
<CountBadge
|
||||
title="Avg. Request Load"
|
||||
unit="ms"
|
||||
icon="clock"
|
||||
count={ data.avgReqLoad }
|
||||
change={ data.avgReqLoadProgress }
|
||||
oppositeColors
|
||||
/>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import {
|
||||
BarChart, Bar, CartesianGrid, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis, Tooltip
|
||||
} from 'recharts';
|
||||
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 28 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 21
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 28
|
||||
if (rangeName === YESTERDAY) params.density = 28
|
||||
if (rangeName === LAST_7_DAYS) params.density = 28
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('resourcesCountByType', { customParams })
|
||||
export default class BreakdownOfLoadedResources extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "resourcesCountByType" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Resources" }}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} name="CSS" dataKey="stylesheet" stackId="a" fill={colors[0]} />
|
||||
<Bar name="Images" dataKey="img" stackId="a" fill={colors[2]} />
|
||||
<Bar name="Scripts" dataKey="script" stackId="a" fill={colors[3]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './BreakdownOfLoadedResources';
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Table, widgetHOC, domain } from '../common';
|
||||
import {
|
||||
Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis,
|
||||
PieChart, Pie, Cell, Tooltip, ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts';
|
||||
|
||||
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'];
|
||||
const RADIAN = Math.PI / 180;
|
||||
|
||||
@widgetHOC('busiestTimeOfDay', { fitContent: true })
|
||||
export default class BusiestTimeOfTheDay extends React.PureComponent {
|
||||
renderCustomizedLabel = ({
|
||||
cx, cy, midAngle, innerRadius, outerRadius, percent, index,
|
||||
}) => {
|
||||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
|
||||
return (
|
||||
<text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central">
|
||||
{`${(percent * 100).toFixed(0)}%`}
|
||||
</text>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, loading } = this.props;
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<ResponsiveContainer height={ 140 } width="100%">
|
||||
<RadarChart outerRadius={50} width={180} height={180} data={data.toJS()}>
|
||||
<PolarGrid />
|
||||
<PolarAngleAxis dataKey="hour" tick={{ fill: '#3EAAAF', fontSize: 12 }} />
|
||||
<PolarRadiusAxis />
|
||||
<Radar name="count" dataKey="count" stroke="#3EAAAF" fill="#3EAAAF" fillOpacity={0.6} />
|
||||
</RadarChart>
|
||||
|
||||
</ResponsiveContainer>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area } from 'recharts';
|
||||
|
||||
const Chart = ({ data }) => {
|
||||
return (
|
||||
<AreaChart width={ 90 } height={ 30 } data={ data.chart } >
|
||||
<Area type="monotone" dataKey="avgDuration" stroke="#3EAAAF" fill="#A8E0DA" fillOpacity={ 0.5 } />
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
Chart.displayName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { Tooltip, Icon } from 'UI';
|
||||
import styles from './imageInfo.module.css';
|
||||
|
||||
const ImageInfo = ({ data }) => (
|
||||
<div className={styles.name}>
|
||||
<Tooltip
|
||||
className={styles.Tooltip}
|
||||
title={
|
||||
<img
|
||||
src={`//${data.url}`}
|
||||
className={styles.imagePreview}
|
||||
alt="One of the slowest images"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={styles.imageWrapper}>
|
||||
<Icon name="camera-alt" size="18" color="gray-light" />
|
||||
<div className={styles.label}>{'Preview'}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip title={data.url}>
|
||||
<span>{data.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
ImageInfo.displayName = 'ImageInfo';
|
||||
|
||||
export default ImageInfo;
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.imagePreview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.imageWrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
& > span {
|
||||
height: 16px;
|
||||
}
|
||||
& .label {
|
||||
font-size: 9px;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #f5f5f5 !important;
|
||||
&:before {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './BusiestTimeOfTheDay';
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Table, widgetHOC } from '../common';
|
||||
import { getRE } from 'App/utils';
|
||||
import ImageInfo from './ImageInfo';
|
||||
import MethodType from './MethodType';
|
||||
import cn from 'classnames';
|
||||
import stl from './callWithErrors.module.css';
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'method',
|
||||
title: 'Method',
|
||||
className: 'text-left',
|
||||
Component: MethodType,
|
||||
cellClass: 'ml-2',
|
||||
width: '8%',
|
||||
},
|
||||
{
|
||||
key: 'urlHostpath',
|
||||
title: 'Path',
|
||||
Component: ImageInfo,
|
||||
width: '40%',
|
||||
},
|
||||
{
|
||||
key: 'allRequests',
|
||||
title: 'Requests',
|
||||
className: 'text-left',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
key: '4xx',
|
||||
title: '4xx',
|
||||
className: 'text-left',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
key: '5xx',
|
||||
title: '5xx',
|
||||
className: 'text-left',
|
||||
width: '15%',
|
||||
}
|
||||
];
|
||||
|
||||
@widgetHOC('callsErrors', { fitContent: true })
|
||||
export default class CallWithErrors extends React.PureComponent {
|
||||
state = { search: ''}
|
||||
|
||||
test = (value = '', serach) => getRE(serach, 'i').test(value);
|
||||
|
||||
write = ({ target: { name, value } }) => {
|
||||
this.setState({ [ name ]: value })
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data: images, loading } = this.props;
|
||||
const { search } = this.state;
|
||||
const _data = search ? images.filter(i => this.test(i.urlHostpath, search)) : images;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<div className={ cn(stl.topActions, 'py-3 flex text-right')}>
|
||||
<input disabled={images.size === 0} className={stl.searchField} name="search" placeholder="Filter by Path" onChange={this.write} />
|
||||
</div>
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ images.size === 0 }
|
||||
>
|
||||
<Table
|
||||
cols={ cols }
|
||||
rows={ _data }
|
||||
isTemplate={this.props.isTemplate}
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area } from 'recharts';
|
||||
|
||||
const Chart = ({ data }) => {
|
||||
return (
|
||||
<AreaChart width={ 90 } height={ 30 } data={ data.chart } >
|
||||
<Area type="monotone" dataKey="avgDuration" stroke="#3EAAAF" fill="#A8E0DA" fillOpacity={ 0.5 } />
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
Chart.displayName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react';
|
||||
import { TextEllipsis } from 'UI';
|
||||
import styles from './imageInfo.module.css';
|
||||
|
||||
const ImageInfo = ({ data }) => (
|
||||
<div className={ styles.name }>
|
||||
<TextEllipsis text={data.urlHostpath} />
|
||||
</div>
|
||||
);
|
||||
|
||||
ImageInfo.displayName = 'ImageInfo';
|
||||
|
||||
export default ImageInfo;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Label } from 'UI';
|
||||
|
||||
const MethodType = ({ data }) => {
|
||||
return (
|
||||
<Label className="ml-1">{data.method}</Label>
|
||||
)
|
||||
}
|
||||
|
||||
export default MethodType
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
.topActions {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 50px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.searchField {
|
||||
padding: 4px 5px;
|
||||
border-bottom: dotted thin $gray-light;
|
||||
border-radius: 3px;
|
||||
&:focus,
|
||||
&:active {
|
||||
border: solid thin transparent !important;
|
||||
box-shadow: none;
|
||||
background-color: $gray-light;
|
||||
}
|
||||
&:hover {
|
||||
border: solid thin $gray-light !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.imagePreview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.imageWrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
& > span {
|
||||
height: 16px;
|
||||
}
|
||||
& .label {
|
||||
font-size: 9px;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #f5f5f5 !important;
|
||||
&:before {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CallWithErrors';
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid,
|
||||
LineChart, Line, Legend, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('domainsErrors_4xx', { customParams })
|
||||
export default class CallsErrors4xx extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
const namesMap = data.chart
|
||||
.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
.reduce(
|
||||
(unique, item) => (unique.includes(item) ? unique : [...unique, item]),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<LineChart
|
||||
data={ data.chart }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "domainsErrors_4xx" : undefined }
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={colors[4]} stopOpacity={ 0.9 } />
|
||||
<stop offset="95%" stopColor={colors[4]} stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Errors"
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
{ namesMap.map((key, index) => (
|
||||
<Line key={key} name={key} type="monotone" dataKey={key} stroke={colors[index]} fillOpacity={ 1 } strokeWidth={ 2 } strokeOpacity={ 0.8 } fill="url(#colorCount)" dot={false} />
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CallsErrors4xx';
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid,
|
||||
LineChart, Line, Legend, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('domainsErrors_5xx', { customParams })
|
||||
export default class CallsErrors5xx extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
const namesMap = data.chart
|
||||
.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
.reduce(
|
||||
(unique, item) => (unique.includes(item) ? unique : [...unique, item]),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<LineChart
|
||||
data={ data.chart }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "domainsErrors_5xx" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Errors"
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
{ namesMap.map((key, index) => (
|
||||
<Line
|
||||
key={key}
|
||||
name={key}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={colors[index]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
// fill="url(#colorCount)"
|
||||
dot={false}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CallsErrors5xx';
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, AreaChart, Area, Tooltip } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('cpu', { customParams })
|
||||
export default class CpuLoad extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
<AvgLabel text="Avg" unit="%" className="ml-3" count={data.avgCpu} />
|
||||
</div>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? "cpu" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
type="monotone"
|
||||
unit="%"
|
||||
dataKey="avgCpu"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CpuLoad';
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import {
|
||||
AreaChart, Area, ResponsiveContainer,
|
||||
XAxis, YAxis, CartesianGrid, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('crashes', { customParams })
|
||||
export default class Crashes extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? "crashes" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Crashes" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Crashes"
|
||||
type="monotone"
|
||||
dataKey="count"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Crashes';
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
.wrapper {
|
||||
background-color: white;
|
||||
/* border: solid thin $gray-medium; */
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, Icon, Tooltip } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { ResponsiveContainer } from 'recharts';
|
||||
import stl from './CustomMetricWidget.module.css';
|
||||
import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper';
|
||||
import {
|
||||
init,
|
||||
edit,
|
||||
remove,
|
||||
setActiveWidget,
|
||||
updateActiveState,
|
||||
} from 'Duck/customMetrics';
|
||||
import { setShowAlerts } from 'Duck/dashboard';
|
||||
import CustomMetriLineChart from '../CustomMetriLineChart';
|
||||
import CustomMetricPieChart from '../CustomMetricPieChart';
|
||||
import CustomMetricPercentage from '../CustomMetricPercentage';
|
||||
import CustomMetricTable from '../CustomMetricTable';
|
||||
import { NO_METRIC_DATA } from 'App/constants/messages';
|
||||
|
||||
const customParams = (rangeName) => {
|
||||
const params = { density: 70 };
|
||||
|
||||
// if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
// if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
// if (rangeName === YESTERDAY) params.density = 70
|
||||
// if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
// loading?: boolean;
|
||||
data?: any;
|
||||
compare?: boolean;
|
||||
period?: any;
|
||||
onClickEdit: (e) => void;
|
||||
remove: (id) => void;
|
||||
setShowAlerts: (showAlerts) => void;
|
||||
setAlertMetricId: (id) => void;
|
||||
onAlertClick: (e) => void;
|
||||
init: (metric: any) => void;
|
||||
edit: (setDefault?) => void;
|
||||
setActiveWidget: (widget) => void;
|
||||
updateActiveState: (metricId, state) => void;
|
||||
isTemplate?: boolean;
|
||||
}
|
||||
function CustomMetricWidget(props: Props) {
|
||||
const { metric, period, isTemplate } = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<any>([]);
|
||||
// const [seriesMap, setSeriesMap] = useState<any>([]);
|
||||
|
||||
const colors = Styles.customMetricColors;
|
||||
const params = customParams(period.rangeName);
|
||||
// const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end }
|
||||
const isLineChart = metric.viewType === 'lineChart';
|
||||
const isProgress = metric.viewType === 'progress';
|
||||
const isTable = metric.viewType === 'table';
|
||||
const isPieChart = metric.viewType === 'pieChart';
|
||||
|
||||
const clickHandlerTable = (filters) => {
|
||||
const activeWidget = {
|
||||
widget: metric,
|
||||
period: period,
|
||||
...period.toTimestamps(),
|
||||
filters,
|
||||
};
|
||||
props.setActiveWidget(activeWidget);
|
||||
};
|
||||
|
||||
const clickHandler = (event, index) => {
|
||||
if (event) {
|
||||
const payload = event.activePayload[0].payload;
|
||||
const timestamp = payload.timestamp;
|
||||
const periodTimestamps =
|
||||
metric.metricType === 'timeseries'
|
||||
? getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density)
|
||||
: period.toTimestamps();
|
||||
|
||||
const activeWidget = {
|
||||
widget: metric,
|
||||
period: period,
|
||||
...periodTimestamps,
|
||||
timestamp: payload.timestamp,
|
||||
index,
|
||||
};
|
||||
|
||||
props.setActiveWidget(activeWidget);
|
||||
}
|
||||
};
|
||||
|
||||
const updateActiveState = (metricId, state) => {
|
||||
props.updateActiveState(metricId, state);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<div className="flex items-center p-2">
|
||||
<div className="font-medium">{metric.name}</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
{!isTable && !isPieChart && (
|
||||
<WidgetIcon
|
||||
className="cursor-pointer mr-6"
|
||||
icon="bell-plus"
|
||||
tooltip="Set Alert"
|
||||
onClick={props.onAlertClick}
|
||||
/>
|
||||
)}
|
||||
<WidgetIcon
|
||||
className="cursor-pointer mr-6"
|
||||
icon="pencil"
|
||||
tooltip="Edit Metric"
|
||||
onClick={() => props.init(metric)}
|
||||
/>
|
||||
<WidgetIcon
|
||||
className="cursor-pointer"
|
||||
icon="close"
|
||||
tooltip="Hide Metric"
|
||||
onClick={() => updateActiveState(metric.metricId, false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3">
|
||||
<Loader loading={loading} size="small">
|
||||
<NoContent size="small" title={NO_METRIC_DATA} show={data.length === 0}>
|
||||
<ResponsiveContainer height={240} width="100%">
|
||||
<>
|
||||
{isLineChart && (
|
||||
<CustomMetriLineChart
|
||||
data={data}
|
||||
params={params}
|
||||
// seriesMap={ seriesMap }
|
||||
colors={colors}
|
||||
onClick={clickHandler}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isPieChart && (
|
||||
<CustomMetricPieChart
|
||||
metric={metric}
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
onClick={clickHandlerTable}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isProgress && (
|
||||
<CustomMetricPercentage
|
||||
data={data[0]}
|
||||
params={params}
|
||||
colors={colors}
|
||||
onClick={clickHandler}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTable && (
|
||||
<CustomMetricTable
|
||||
metric={metric}
|
||||
data={data[0]}
|
||||
onClick={clickHandlerTable}
|
||||
isTemplate={isTemplate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
period: state.getIn(['dashboard', 'period']),
|
||||
}),
|
||||
{
|
||||
remove,
|
||||
setShowAlerts,
|
||||
edit,
|
||||
setActiveWidget,
|
||||
updateActiveState,
|
||||
init,
|
||||
}
|
||||
)(CustomMetricWidget);
|
||||
|
||||
const WidgetIcon = ({
|
||||
className = '',
|
||||
tooltip = '',
|
||||
icon,
|
||||
onClick,
|
||||
}: {
|
||||
className: string;
|
||||
tooltip: string;
|
||||
icon: string;
|
||||
onClick: any;
|
||||
}) => (
|
||||
<Tooltip title={tooltip}>
|
||||
<div className={className} onClick={onClick}>
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={icon} size="14" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetricWidget';
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import { withRequest } from 'HOCs';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import WidgetAutoComplete from 'Shared/WidgetAutoComplete';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const WIDGET_KEY = 'pagesDomBuildtime';
|
||||
const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase();
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
@withRequest({
|
||||
dataName: "options",
|
||||
initialData: [],
|
||||
dataWrapper: data => data,
|
||||
loadingName: 'optionsLoading',
|
||||
requestName: "fetchOptions",
|
||||
endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search',
|
||||
method: 'GET'
|
||||
})
|
||||
@widgetHOC(WIDGET_KEY, { customParams })
|
||||
export default class DomBuildingTime extends React.PureComponent {
|
||||
onSelect = (params) => {
|
||||
const _params = customParams(this.props.period.rangeName)
|
||||
this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<React.Fragment>
|
||||
<div className="flex items-center mb-3">
|
||||
<WidgetAutoComplete
|
||||
loading={optionsLoading}
|
||||
fetchOptions={this.props.fetchOptions}
|
||||
options={this.props.options}
|
||||
onSelect={this.onSelect}
|
||||
placeholder="Search for Page"
|
||||
/>
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(data.avg)} unit="ms" />
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.size === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? WIDGET_KEY : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis} dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "DOM Build Time (ms)" }}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
type="monotone"
|
||||
dataKey="avg"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</React.Fragment>
|
||||
</NoContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './DomBuildingTime';
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { CountBadge, domain, widgetHOC } from '../common';
|
||||
import styles from './errors.module.css';
|
||||
|
||||
@widgetHOC('errors')
|
||||
export default class Errors extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading } = this.props;
|
||||
|
||||
const isMoreThanKSessions = data.impactedSessions > 1000;
|
||||
const impactedSessionsView = isMoreThanKSessions ? Math.trunc(data.impactedSessions / 1000) : data.impactedSessions;
|
||||
return (
|
||||
<div className="flex justify-between items-center flex-1 flex-shrink-0">
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
title="No exceptions."
|
||||
size="small"
|
||||
show={ data.count === 0 && data.progress === 0 }
|
||||
>
|
||||
<CountBadge
|
||||
title={ <div className={ styles.label }>{ 'Events' }</div> }
|
||||
count={ data.count }
|
||||
change={ data.progress }
|
||||
oppositeColors
|
||||
/>
|
||||
<CountBadge
|
||||
title={ <div className={ styles.label }>{ 'Sessions' }</div> }
|
||||
count={ impactedSessionsView }
|
||||
change={ data.impactedSessionsProgress }
|
||||
unit={ isMoreThanKSessions ? 'k' : '' }
|
||||
oppositeColors
|
||||
/>
|
||||
<ResponsiveContainer height={ 140 } width="60%">
|
||||
<AreaChart data={ data.chart } margin={ { top: 10, right: 20, left: 20, bottom: 0 } }>
|
||||
<defs>
|
||||
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#A8E0DA" stopOpacity={ 0.9 } />
|
||||
<stop offset="95%" stopColor="#A8E0DA" stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis interval={ 0 } dataKey="time" tick={ { fill: '#999999', fontSize: 9 } } />
|
||||
<YAxis interval={ 0 } hide domain={ domain }/>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<Area type="monotone" dataKey="count" stroke="#3EAAAF" fill="url(#colorCount)" fillOpacity={ 1 } strokeWidth={ 1 } strokeOpacity={ 0.8 } />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.label {
|
||||
max-width: 65px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Errors';
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import {
|
||||
BarChart, Bar, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 28 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 28
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 28
|
||||
if (rangeName === YESTERDAY) params.density = 28
|
||||
if (rangeName === LAST_7_DAYS) params.density = 28
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('resourcesByParty', { fullwidth: true, customParams })
|
||||
export default class ErrorsByOrigin extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "resourcesByParty" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Errors" }}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} name={<span className="float">1<sup>st</sup> Party</span>} dataKey="firstParty" stackId="a" fill={colors[0]} />
|
||||
<Bar name={<span className="float">3<sup>rd</sup> Party</span>} dataKey="thirdParty" stackId="a" fill={colors[2]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ErrorsByOrigin';
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, domain, Styles } from '../common';
|
||||
import { numberWithCommas} from 'App/utils';
|
||||
import {
|
||||
BarChart, Bar, CartesianGrid, Tooltip,
|
||||
LineChart, Line, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 28 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 21
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 28
|
||||
if (rangeName === YESTERDAY) params.density = 28
|
||||
if (rangeName === LAST_7_DAYS) params.density = 28
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('errorsPerType', { fullwidth: true, customParams })
|
||||
export default class ErrorsByType extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId="errorsPerType"
|
||||
syncId={ showSync ? "errorsPerType" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Errors" }}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} name="Integrations" dataKey="integrations" stackId="a" fill={colors[0]}/>
|
||||
<Bar name="4xx" dataKey="4xx" stackId="a" fill={colors[1]} />
|
||||
<Bar name="5xx" dataKey="5xx" stackId="a" fill={colors[2]} />
|
||||
<Bar name="Javascript" dataKey="js" stackId="a" fill={colors[3]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ErrorsByType';
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
|
||||
const Bar = ({ className = '', width = 0, avg, domain, color }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width > 0 ? width : 5 }%`, backgroundColor: color }}></div>
|
||||
<div className="ml-2">
|
||||
<span className="font-medium">{`${avg}`}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm leading-3 color-gray-medium">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
.bar {
|
||||
height: 5px;
|
||||
background-color: red;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Table, widgetHOC, domain, AvgLabel, Styles } from '../common';
|
||||
import Bar from './Bar';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
|
||||
@widgetHOC('errorsPerDomains')
|
||||
export default class ErrorsPerDomain extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, compare = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const firstAvg = data.first() && data.first().errorsCount;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.size === 0 }
|
||||
>
|
||||
<div className="w-full pt-3" style={{ height: '240px' }}>
|
||||
{data.map((item, i) =>
|
||||
<Bar
|
||||
key={i}
|
||||
className="mb-4"
|
||||
avg={numberWithCommas(Math.round(item.errorsCount))}
|
||||
width={Math.round((item.errorsCount * 100) / firstAvg) - 10}
|
||||
domain={item.domain}
|
||||
color={colors[i]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ErrorsPerDomain';
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('fps', { customParams })
|
||||
export default class FPS extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
title="No recordings found"
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
<AvgLabel text="Avg" className="ml-3" count={data.avgFps} />
|
||||
</div>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? "fps" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Frames Per Second" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
type="monotone"
|
||||
dataKey="avgFps"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './FPS';
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent, BrowserIcon, OsIcon } from 'UI';
|
||||
import { countries } from 'App/constants';
|
||||
import { diffFromNowString } from 'App/date';
|
||||
import { widgetHOC, SessionLine } from '../common';
|
||||
|
||||
@widgetHOC('sessionsFrustration', { fitContent: true })
|
||||
export default class LastFeedbacks extends React.PureComponent {
|
||||
render() {
|
||||
const { data: sessions, loading } = this.props;
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ sessions.size === 0 }
|
||||
>
|
||||
{ sessions.map(({
|
||||
startedAt,
|
||||
sessionId,
|
||||
clickRage,
|
||||
returningLocation,
|
||||
userBrowser,
|
||||
userOs,
|
||||
userCountry,
|
||||
}) => (
|
||||
<SessionLine
|
||||
sessionId={ sessionId }
|
||||
icon={ clickRage ? "event/click" : "event/link" }
|
||||
info={
|
||||
<span className="flex items-center" >
|
||||
{ clickRage ? "Click Rage" : "Returning Location" }
|
||||
<BrowserIcon browser={ userBrowser } size="16" className="ml-10 mr-10" />
|
||||
<OsIcon os={ userOs } size="16" />
|
||||
</span>
|
||||
}
|
||||
subInfo={ `${ diffFromNowString(startedAt) } ago - ${ countries[ userCountry ] || '' }` }
|
||||
/>
|
||||
))}
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './LastFrustrations';
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
@widgetHOC('memoryConsumption', { customParams })
|
||||
export default class MemoryConsumption extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.chart.length === 0 }
|
||||
>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
<AvgLabel text="Avg" unit="mb" className="ml-3" count={data.avgUsedJsHeapSize} />
|
||||
</div>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? "memoryConsumption" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis} dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatterBytes(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "JS Heap Size (mb)" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
unit=" mb"
|
||||
type="monotone"
|
||||
dataKey="avgUsedJsHeapSize"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './MemoryConsumption';
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area } from 'recharts';
|
||||
import { Styles } from '../common';
|
||||
|
||||
const Chart = ({ data, compare }) => {
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
|
||||
return (
|
||||
<AreaChart width={ 90 } height={ 30 } data={ data.chart } >
|
||||
<Area type="monotone" dataKey="count" stroke={colors[0]} fill={colors[3]} fillOpacity={ 0.5 } />
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
Chart.displayName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import React from 'react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useState } from 'react'
|
||||
|
||||
const CopyPath = ({ data }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const copyHandler = () => {
|
||||
copy(data.url);
|
||||
setCopied(true);
|
||||
setTimeout(function() {
|
||||
setCopied(false)
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cursor-pointer color-teal" onClick={copyHandler}>
|
||||
{ copied ? 'Copied' : 'Copy Path'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyPath
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Table, widgetHOC } from '../common';
|
||||
import Chart from './Chart';
|
||||
import ResourceInfo from './ResourceInfo';
|
||||
import CopyPath from './CopyPath';
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'resource',
|
||||
title: 'Resource',
|
||||
Component: ResourceInfo,
|
||||
width: '40%',
|
||||
},
|
||||
{
|
||||
key: 'sessions',
|
||||
title: 'Sessions',
|
||||
toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`,
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
key: 'trend',
|
||||
title: 'Trend',
|
||||
Component: Chart,
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
key: 'copy-path',
|
||||
title: '',
|
||||
Component: CopyPath,
|
||||
cellClass: 'invisible group-hover:visible text-right',
|
||||
width: '20%',
|
||||
}
|
||||
];
|
||||
|
||||
@widgetHOC('missingResources', { })
|
||||
export default class MissingResources extends React.PureComponent {
|
||||
render() {
|
||||
const { data: resources, loading, compare } = this.props;
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
title="No resources missing."
|
||||
size="small"
|
||||
show={ resources.size === 0 }
|
||||
>
|
||||
<Table
|
||||
small
|
||||
cols={ cols }
|
||||
rows={ resources }
|
||||
rowClass="group"
|
||||
compare={compare}
|
||||
isTemplate={this.props.isTemplate}
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
import { diffFromNowString } from 'App/date';
|
||||
import { TextEllipsis } from 'UI';
|
||||
|
||||
import styles from './resourceInfo.module.css';
|
||||
|
||||
export default class ResourceInfo extends React.PureComponent {
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
return (
|
||||
<div className="flex flex-col" >
|
||||
<TextEllipsis className={ styles.name } text={ data.name } hintText={ data.url } />
|
||||
<div className={ styles.timings }>
|
||||
{ data.endedAt && data.startedAt && `${ diffFromNowString(data.endedAt) } ago - ${ diffFromNowString(data.startedAt) } old` }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './MissingResources';
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
.name {
|
||||
letter-spacing: -.04em;
|
||||
font-size: .9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.timings {
|
||||
color: $gray-medium;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { BarChart, Bar } from 'recharts';
|
||||
|
||||
const Chart = ({ data }) => (
|
||||
<BarChart width={ 90 } height={ 30 } data={ data.chart }>
|
||||
<Bar dataKey="count" fill="#A8E0DA" />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
Chart.displaName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import React from 'react';
|
||||
import { diffFromNowString } from 'App/date';
|
||||
import { TextEllipsis } from 'UI';
|
||||
import styles from './errorInfo.module.css';
|
||||
|
||||
export default class ErrorInfo extends React.PureComponent {
|
||||
findJourneys = () => this.props.findJourneys(this.props.data.error)
|
||||
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
return (
|
||||
<div className="flex flex-col" >
|
||||
<TextEllipsis
|
||||
onClick={ this.findJourneys }
|
||||
className={ styles.errorText }
|
||||
text={ data.error }
|
||||
popupProps={{
|
||||
position: 'left center',
|
||||
wide: 'very',
|
||||
offset: '100',
|
||||
positionFixed: true,
|
||||
size: 'small'
|
||||
}}
|
||||
/>
|
||||
<div className={ styles.timings }>
|
||||
{ `${ diffFromNowString(data.lastOccurrenceAt) } ago - ${ diffFromNowString(data.firstOccurrenceAt) } old` }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import withSiteIdRouter from 'HOCs/withSiteIdRouter';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { addEvent } from 'Duck/filters';
|
||||
import { TYPES } from 'Types/filter/event';
|
||||
import { sessions } from 'App/routes';
|
||||
import { Table, widgetHOC } from '../common';
|
||||
import Chart from './Chart';
|
||||
import ErrorInfo from './ErrorInfo';
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'error',
|
||||
title: 'Error Info',
|
||||
Component: ErrorInfo,
|
||||
width: '80%',
|
||||
},
|
||||
{
|
||||
key: 'trend',
|
||||
title: 'Trend',
|
||||
Component: Chart,
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
key: 'sessions',
|
||||
title: 'Sessions',
|
||||
toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`,
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
@withSiteIdRouter
|
||||
@widgetHOC('errorsTrend', { fullwidth: true })
|
||||
@connect(null, { addEvent })
|
||||
export default class MostImpactfulErrors extends React.PureComponent {
|
||||
findJourneys = (error) => {
|
||||
this.props.addEvent({
|
||||
type: TYPES.CONSOLE,
|
||||
value: error,
|
||||
}, true);
|
||||
this.props.history.push(sessions());
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data: errors, loading } = this.props;
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ errors.size === 0 }
|
||||
>
|
||||
<Table
|
||||
cols={ cols }
|
||||
rows={ errors }
|
||||
rowProps={ { findJourneys: this.findJourneys } }
|
||||
isTemplate={this.props.isTemplate}
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
.errorText {
|
||||
font-family: 'menlo', 'monaco', 'consolas', monospace;
|
||||
letter-spacing: -.04em;
|
||||
font-size: .9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.timings {
|
||||
color: $gray-medium;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './MostImpactfulErrors';
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader } from 'UI';
|
||||
import { CountBadge, Divider, widgetHOC } from './common';
|
||||
|
||||
@widgetHOC('pageMetrics')
|
||||
export default class PageMetrics extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading } = this.props;
|
||||
return (
|
||||
<div className="flex-1 flex-shrink-0 flex justify-around items-center">
|
||||
<Loader loading={ loading } size="small">
|
||||
<CountBadge
|
||||
title="Avg. Dom Load Time"
|
||||
unit="ms"
|
||||
icon="file"
|
||||
count={ data.avgLoad }
|
||||
change={ data.avgLoadProgress }
|
||||
oppositeColors
|
||||
/>
|
||||
<Divider />
|
||||
<CountBadge
|
||||
title="Avg. First Meaningful Paint"
|
||||
unit="ms"
|
||||
icon="file"
|
||||
count={ data.avgFirstContentfulPixel }
|
||||
change={ data.avgFirstContentfulPixelProgress }
|
||||
oppositeColors
|
||||
/>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Map } from 'immutable';
|
||||
import cn from 'classnames';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Legend } from 'recharts';
|
||||
import { Loader, TextEllipsis, Tooltip } from 'UI';
|
||||
import { TYPES } from 'Types/resource';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS } from 'Types/app/period';
|
||||
import { fetchPerformanseSearch } from 'Duck/dashboard';
|
||||
import { widgetHOC } from '../common';
|
||||
|
||||
import styles from './performance.module.css';
|
||||
|
||||
const BASE_KEY = 'resource';
|
||||
|
||||
const pagesColor = '#7FCC33';
|
||||
const imagesColor = '#40C4FF';
|
||||
const requestsColor = '#DAB72F';
|
||||
|
||||
@widgetHOC('performance', { fullwidth: true })
|
||||
@connect((state, props) => ({
|
||||
performanceChartSpecified: state.getIn([ 'dashboard', 'performanceChart' ]),
|
||||
period: state.getIn([ 'dashboard', 'period' ]),
|
||||
loading: state.getIn([ 'dashboard', 'performanceSearchRequest', 'loading' ]) ||
|
||||
props.loading,
|
||||
}), {
|
||||
fetchPerformanseSearch,
|
||||
})
|
||||
export default class Performance extends React.PureComponent {
|
||||
state = {
|
||||
comparing: false,
|
||||
resources: Map(),
|
||||
opacity: {},
|
||||
}
|
||||
|
||||
onResourceSelect = (resource) => {
|
||||
if (!resource || this.state.resources.size > 1) return;
|
||||
|
||||
resource.fillColor = this.getFillColor(resource);
|
||||
resource.strokeColor = this.getStrokeColor(resource);
|
||||
this.setResources(this.state.resources.set(this.state.resources.size, resource));
|
||||
}
|
||||
|
||||
onResourceSelect0 = resource => this.onResourceSelect(0, resource)
|
||||
onResourceSelect1 = resource => this.onResourceSelect(1, resource)
|
||||
|
||||
getInterval = () => {
|
||||
switch (this.props.period.rangeName) {
|
||||
case LAST_30_MINUTES:
|
||||
return 0;
|
||||
case LAST_24_HOURS:
|
||||
return 2;
|
||||
case LAST_7_DAYS:
|
||||
return 3;
|
||||
case LAST_30_DAYS:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
setResources = (resources) => {
|
||||
this.setState({
|
||||
resources,
|
||||
});
|
||||
this.props.fetchPerformanseSearch({
|
||||
...this.props.period.toTimestamps(),
|
||||
resources: resources.valueSeq().toJS(),
|
||||
});
|
||||
}
|
||||
|
||||
getFillColor = (resource) => {
|
||||
switch (resource.type) {
|
||||
case TYPES.IMAGE:
|
||||
return 'url(#colorAvgImageLoadTime)';
|
||||
case TYPES.PAGE:
|
||||
return 'url(#colorAvgPageLoadTime)';
|
||||
case TYPES.REQUEST:
|
||||
return 'url(#colorAvgRequestLoadTime)';
|
||||
default:
|
||||
return 'blue';
|
||||
}
|
||||
}
|
||||
|
||||
getStrokeColor = (resource) => {
|
||||
switch (resource.type) {
|
||||
case TYPES.IMAGE:
|
||||
return imagesColor;
|
||||
case TYPES.PAGE:
|
||||
return pagesColor;
|
||||
case TYPES.REQUEST:
|
||||
return requestsColor;
|
||||
default:
|
||||
return 'blue';
|
||||
}
|
||||
}
|
||||
|
||||
removeResource = (index) => {
|
||||
this.setResources(this.state.resources.remove(index));
|
||||
}
|
||||
|
||||
compare = () => this.setState({ comparing: true })
|
||||
|
||||
legendPopup = (component, trigger) => <Tooltip size="mini" content={ component }>{trigger}</Tooltip>
|
||||
|
||||
legendFormatter = (value, entry, index) => {
|
||||
const { opacity } = this.state;
|
||||
|
||||
if (value === 'avgPageLoadTime') return (this.legendPopup(opacity.avgPageLoadTime === 0 ? 'Show' : 'Hide', <span className={ opacity.avgPageLoadTime === 0 ? styles.muted : '' }>{'Pages'}</span>));
|
||||
if (value === 'avgRequestLoadTime') return (this.legendPopup(opacity.avgRequestLoadTime === 0 ? 'Show' : 'Hide', <span className={ opacity.avgRequestLoadTime === 0 ? styles.muted : '' }>{'Requests'}</span>));
|
||||
if (value === 'avgImageLoadTime') return (this.legendPopup(opacity.avgImageLoadTime === 0 ? 'Show' : 'Hide', <span className={ opacity.avgImageLoadTime === 0 ? styles.muted : '' }>{'Images'}</span>));
|
||||
// if (value === 'avgImageLoadTime') return (<span className={ opacity.avgImageLoadTime === 0 ? styles.muted : '' }>{'Images'}</span>);
|
||||
if (value.includes(BASE_KEY)) {
|
||||
const resourceIndex = Number.parseInt(value.substr(BASE_KEY.length));
|
||||
return (
|
||||
<Tooltip
|
||||
title={ this.state.resources.getIn([ resourceIndex, 'value' ]) }
|
||||
>
|
||||
<TextEllipsis
|
||||
maxWidth="200px"
|
||||
style={ { verticalAlign: 'middle' } }
|
||||
text={ this.state.resources.getIn([ resourceIndex, 'value' ]) }
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleLegendClick = (legend) => {
|
||||
const { dataKey } = legend;
|
||||
const { opacity } = this.state;
|
||||
|
||||
if (dataKey === 'resource0') {
|
||||
this.removeResource(0);
|
||||
} else if (dataKey === 'resource1') {
|
||||
this.removeResource(1);
|
||||
} else {
|
||||
this.setState({
|
||||
opacity: { ...opacity, [ dataKey ]: opacity[ dataKey ] === 0 ? 1 : 0 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
render() {
|
||||
const { comparing, resources, opacity } = this.state;
|
||||
const { data, performanceChartSpecified, loading } = this.props;
|
||||
const r0Presented = !!resources.get(0);
|
||||
const r1Presented = !!resources.get(1);
|
||||
const resourcesPresented = r0Presented || r1Presented;
|
||||
const defaultData = !resourcesPresented || performanceChartSpecified.length === 0; // TODO: more safe?
|
||||
const interval = this.getInterval();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="flex justify-between">
|
||||
<div className={ cn("flex items-center", { "hidden": loading }) }>
|
||||
</div>
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<ResponsiveContainer height={ 300 }>
|
||||
<AreaChart data={ defaultData ? data.chart : performanceChartSpecified } margin={ { top: 10, right: 20, left: 20, bottom: 0 } }>
|
||||
<defs>
|
||||
<linearGradient id="colorAvgPageLoadTime" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={ pagesColor } stopOpacity={ 0.7 } />
|
||||
<stop offset="95%" stopColor={ pagesColor } stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorAvgImageLoadTime" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={ imagesColor } stopOpacity={ 0.7 } />
|
||||
<stop offset="95%" stopColor={ imagesColor } stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorAvgRequestLoadTime" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={ requestsColor } stopOpacity={ 0.7 } />
|
||||
<stop offset="95%" stopColor={ requestsColor } stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis interval={ interval } dataKey="time" tick={ { fill: '#999999', fontSize: 9 } } />
|
||||
<YAxis hide />
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } />
|
||||
{ defaultData && <Area connectNulls type="monotone" dataKey="avgPageLoadTime" stroke={ pagesColor } strokeOpacity={ opacity.avgPageLoadTime } fill="url(#colorAvgPageLoadTime)" fillOpacity={ opacity.avgPageLoadTime } /> }
|
||||
{ defaultData && <Area connectNulls type="monotone" dataKey="avgImageLoadTime" stroke={ imagesColor } strokeOpacity={ opacity.avgImageLoadTime } fill="url(#colorAvgImageLoadTime)" fillOpacity={ opacity.avgImageLoadTime } /> }
|
||||
{ defaultData && <Area connectNulls type="monotone" dataKey="avgRequestLoadTime" stroke={ requestsColor } strokeOpacity={ opacity.avgRequestLoadTime } fill="url(#colorAvgRequestLoadTime)" fillOpacity={ opacity.avgRequestLoadTime } /> }
|
||||
{ !defaultData && r0Presented && <Area type="monotone" dataKey={ `${ BASE_KEY }0` } stroke={ resources.get(0).strokeColor } fill={ resources.get(0).fillColor } fillOpacity={ 0.5 } /> }
|
||||
{ !defaultData && r1Presented && <Area type="monotone" dataKey={ `${ BASE_KEY }1` } stroke={ resources.get(1).strokeColor } fill={ resources.get(1).fillColor } fillOpacity={ 0.5 } /> }
|
||||
<Legend
|
||||
verticalAlign="top"
|
||||
height={ 36 }
|
||||
margin={ { left: 20, right: 20 } }
|
||||
formatter={ this.legendFormatter }
|
||||
onClick={ this.handleLegendClick }
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Loader>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Performance';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
.muted {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts';
|
||||
import { Loader } from 'UI';
|
||||
import { CountBadge, domain, widgetHOC, Styles } from './common';
|
||||
|
||||
@widgetHOC('sessions', { trendChart: true, fitContent: true })
|
||||
export default class ProcessedSessions extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading } = this.props;
|
||||
const isMoreThanK = data.count > 1000;
|
||||
const countView = isMoreThanK ? Math.trunc(data.count / 1000) : data.count;
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center flex-1">
|
||||
<Loader loading={ loading } size="small">
|
||||
<CountBadge
|
||||
className="absolute top-0 pl-4"
|
||||
title="New and Returning Visitors"
|
||||
count={ countView }
|
||||
change={ data.progress }
|
||||
unit={ isMoreThanK ? 'k' : '' }
|
||||
/>
|
||||
<ResponsiveContainer height={ 90 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ {
|
||||
top: 40, right: 0, left: 0, bottom: -10,
|
||||
} }
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#A8E0DA" stopOpacity={ 0.9 } />
|
||||
<stop offset="95%" stopColor="#A8E0DA" stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis {...Styles.xaxis} dataKey="time" />
|
||||
<YAxis hide interval={ 0 } domain={ domain } />
|
||||
<Area type="monotone" dataKey="count" stroke={Styles.strokeColor} fillOpacity={ 1 } strokeWidth={ 2 } strokeOpacity={ 0.8 } fill="url(#colorCount)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import { ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 30 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 24
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 18
|
||||
if (rangeName === YESTERDAY) params.density = 24
|
||||
if (rangeName === LAST_7_DAYS) params.density = 30
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('resourceTypeVsResponseEnd', { customParams })
|
||||
export default class ResourceLoadedVsResponseEnd extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<ResponsiveContainer height={ 247 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "resourceTypeVsResponseEnd" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Resources" }}
|
||||
tickFormatter={val => Styles.tickFormatter(val, 'ms')}
|
||||
yAxisId="left"
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
tickFormatter={val => Styles.tickFormatter(val, 'ms')}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
offset: 70,
|
||||
value: "Response End (ms)"
|
||||
}}
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} yAxisId="left" name="XHR" dataKey="xhr" stackId="a" fill={colors[0]} />
|
||||
<Bar yAxisId="left" name="Other" dataKey="total" stackId="a" fill={colors[2]} />
|
||||
<Line
|
||||
yAxisId="right"
|
||||
strokeWidth={2}
|
||||
name="Response End"
|
||||
type="monotone"
|
||||
dataKey="avgResponseEnd"
|
||||
stroke={compare ? Styles.lineColorCompare : Styles.lineColor}
|
||||
dot={false}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ResourceLoadedVsResponseEnd';
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import {
|
||||
ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 21 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 21
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 21
|
||||
if (rangeName === YESTERDAY) params.density = 21
|
||||
if (rangeName === LAST_7_DAYS) params.density = 21
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('resourcesVsVisuallyComplete', { customParams })
|
||||
export default class ResourceLoadedVsVisuallyComplete extends React.PureComponent {
|
||||
render() {
|
||||
const {className, data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
title="No recordings found"
|
||||
show={ data.size === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
margin={ Styles.chartMargins}
|
||||
syncId={ showSync ? "resourcesVsVisuallyComplete" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={3}
|
||||
interval={(params.density / 7)}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Visually Complete (ms)" }}
|
||||
yAxisId="left"
|
||||
tickFormatter={val => Styles.tickFormatter(val, 'ms')}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Resources",
|
||||
position: "insideRight",
|
||||
offset: 0
|
||||
}}
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Legend />
|
||||
<Bar minPointSize={1} yAxisId="right" name="Images" type="monotone" dataKey="types.img" stackId="a" fill={colors[0]} />
|
||||
<Bar yAxisId="right" name="Scripts" type="monotone" dataKey="types.script" stackId="a" fill={colors[2]} />
|
||||
<Bar yAxisId="right" name="CSS" type="monotone" dataKey="types.stylesheet" stackId="a" fill={colors[4]} />
|
||||
<Line
|
||||
yAxisId="left"
|
||||
name="Visually Complete"
|
||||
type="monotone"
|
||||
dataKey="avgTimeToRender"
|
||||
stroke={compare? Styles.lineColorCompare : Styles.lineColor }
|
||||
dot={false}
|
||||
unit=" ms"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ResourceLoadedVsVisuallyComplete';
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent, DropdownPlain } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import { withRequest } from 'HOCs';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import WidgetAutoComplete from 'Shared/WidgetAutoComplete';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const WIDGET_KEY = 'resourcesLoadingTime';
|
||||
const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase();
|
||||
|
||||
// other' = -1, 'script' = 0, 'stylesheet' = 1, 'fetch' = 2, 'img' = 3, 'media' = 4
|
||||
export const RESOURCE_OPTIONS = [
|
||||
{ text: 'All', value: 'all', },
|
||||
{ text: 'JS', value: "SCRIPT", },
|
||||
{ text: 'CSS', value: "STYLESHEET", },
|
||||
{ text: 'Fetch', value: "REQUEST", },
|
||||
{ text: 'Image', value: "IMG", },
|
||||
{ text: 'Media', value: "MEDIA", },
|
||||
{ text: 'Other', value: "OTHER", },
|
||||
];
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = {density: 70, type: null }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@withRequest({
|
||||
dataName: "options",
|
||||
initialData: [],
|
||||
dataWrapper: data => data,
|
||||
loadingName: 'optionsLoading',
|
||||
requestName: "fetchOptions",
|
||||
endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search',
|
||||
method: 'GET'
|
||||
})
|
||||
@widgetHOC(WIDGET_KEY, { customParams })
|
||||
export default class ResourceLoadingTime extends React.PureComponent {
|
||||
state = { autoCompleteSelected: null, type: null }
|
||||
onSelect = (params) => {
|
||||
const _params = customParams(this.props.period.rangeName)
|
||||
this.setState({ autoCompleteSelected: params.value });
|
||||
this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value })
|
||||
}
|
||||
|
||||
writeOption = (e, { name, value }) => {
|
||||
this.setState({ [name]: value })
|
||||
const _params = customParams(this.props.period.rangeName)
|
||||
this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, [ name ]: value === 'all' ? null : value })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props;
|
||||
const { autoCompleteSelected, type } = this.state;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<React.Fragment>
|
||||
<div className="flex items-center mb-3">
|
||||
<WidgetAutoComplete
|
||||
loading={optionsLoading}
|
||||
fetchOptions={this.props.fetchOptions}
|
||||
options={this.props.options}
|
||||
onSelect={this.onSelect}
|
||||
placeholder="Search for Fetch, CSS or Media"
|
||||
filterParams={{ type: type }}
|
||||
/>
|
||||
<DropdownPlain
|
||||
disabled={!!autoCompleteSelected}
|
||||
name="type"
|
||||
label="Resource"
|
||||
options={ RESOURCE_OPTIONS }
|
||||
onChange={ this.writeOption }
|
||||
defaultValue={'all'}
|
||||
wrapperStyle={{
|
||||
position: 'absolute',
|
||||
top: '12px',
|
||||
left: '170px',
|
||||
}}
|
||||
/>
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(data.avg)} unit="ms" />
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
syncId={ showSync ? WIDGET_KEY : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Resource Fetch Time (ms)" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
unit=" ms"
|
||||
type="monotone"
|
||||
dataKey="avg"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</React.Fragment>
|
||||
</NoContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ResourceLoadingTime';
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, domain, Styles, AvgLabel } from '../common';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
import WidgetAutoComplete from 'Shared/WidgetAutoComplete';
|
||||
import { withRequest } from 'HOCs';
|
||||
import { toUnderscore } from 'App/utils';
|
||||
|
||||
const WIDGET_KEY = 'pagesResponseTime';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@withRequest({
|
||||
dataName: "options",
|
||||
initialData: [],
|
||||
dataWrapper: data => data,
|
||||
// resetBeforeRequest: true,
|
||||
loadingName: "optionsLoading",
|
||||
requestName: "fetchOptions",
|
||||
endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search',
|
||||
method: 'GET'
|
||||
})
|
||||
@widgetHOC(WIDGET_KEY, { customParams })
|
||||
export default class ResponseTime extends React.PureComponent {
|
||||
onSelect = (params) => {
|
||||
const _params = customParams(this.props.period.rangeName)
|
||||
this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, {..._params, url: params.value }, this.props.filters)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, loading, optionsLoading, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<WidgetAutoComplete
|
||||
loading={optionsLoading}
|
||||
fetchOptions={this.props.fetchOptions}
|
||||
options={this.props.options}
|
||||
onSelect={this.onSelect}
|
||||
placeholder="Search for Page"
|
||||
/>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
<AvgLabel text="Avg" unit="ms" className="ml-3" count={data.avg} />
|
||||
</div>
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "pagesResponseTime" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} interval={10} dataKey="time" />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
// tickFormatter={(val) => `${val}` }
|
||||
label={{ ...Styles.axisLabelLeft, value: "Page Response Time (ms)" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Avg"
|
||||
type="monotone"
|
||||
unit=" ms"
|
||||
dataKey="avg"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ResponseTime';
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import {
|
||||
ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer,
|
||||
XAxis, YAxis, ReferenceLine, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 40 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 40
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 40
|
||||
if (rangeName === YESTERDAY) params.density = 40
|
||||
if (rangeName === LAST_7_DAYS) params.density = 40
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const PercentileLine = props => {
|
||||
const {
|
||||
viewBox: { x, y },
|
||||
xoffset,
|
||||
yheight,
|
||||
height,
|
||||
label
|
||||
} = props;
|
||||
return (
|
||||
<svg>
|
||||
<line
|
||||
x1={x + xoffset}
|
||||
x2={x + xoffset}
|
||||
y1={height + 10}
|
||||
y2={172}
|
||||
{...props}
|
||||
/>
|
||||
<text
|
||||
x={x + 5}
|
||||
y={height + 20}
|
||||
fontSize="8"
|
||||
fontFamily="Roboto"
|
||||
fill="#000000"
|
||||
textAnchor="start"
|
||||
>
|
||||
{label}
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@widgetHOC('pagesResponseTimeDistribution', { customParams })
|
||||
export default class ResponseTimeDistribution extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
<AvgLabel text="Avg" unit="ms" className="ml-3" count={data.avg} />
|
||||
</div>
|
||||
<div className="flex">
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "pagesResponseTimeDistribution" : undefined }
|
||||
barSize={50}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="responseTime"
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
angle: 0,
|
||||
offset: 0,
|
||||
value: "Page Response Time (ms)",
|
||||
style: { textAnchor: 'middle' },
|
||||
position: "insideBottom"
|
||||
}}
|
||||
dataKey="responseTime"
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Calls"
|
||||
}}
|
||||
/>
|
||||
<Bar minPointSize={1} name="Calls" dataKey="count" stackId="a" fill={colors[2]} label="Backend" />
|
||||
<Tooltip {...Styles.tooltip} labelFormatter={val => 'Page Response Time: ' + val} />
|
||||
{ data.percentiles.map((item, i) => (
|
||||
<ReferenceLine
|
||||
key={i}
|
||||
label={
|
||||
<PercentileLine
|
||||
xoffset={0}
|
||||
// y={130}
|
||||
height={i * 20}
|
||||
stroke={'#000'}
|
||||
strokeWidth={0.5}
|
||||
strokeOpacity={1}
|
||||
label={`${item.percentile}th Percentile (${item.responseTime}ms)`}
|
||||
/>
|
||||
}
|
||||
allowDecimals={false}
|
||||
x={item.responseTime}
|
||||
strokeWidth={0}
|
||||
strokeOpacity={1}
|
||||
/>
|
||||
))}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
<ResponsiveContainer height={ 207 } width="10%">
|
||||
<BarChart
|
||||
data={data.extremeValues}
|
||||
margin={Styles.chartMargins}
|
||||
barSize={40}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" />
|
||||
<YAxis hide type="number" domain={[0, data.total]} {...Styles.yaxis} allowDecimals={false} />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} name="Extreme Values" dataKey="count" stackId="a" fill={colors[0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ResponseTimeDistribution';
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles, AvgLabel } from '../common';
|
||||
import {
|
||||
ComposedChart, Bar, CartesianGrid, Legend, ResponsiveContainer,
|
||||
XAxis, YAxis, Tooltip
|
||||
} from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 28 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 28
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 28
|
||||
if (rangeName === YESTERDAY) params.density = 28
|
||||
if (rangeName === LAST_7_DAYS) params.density = 28
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('impactedSessionsByJsErrors', { customParams })
|
||||
export default class SessionsAffectedByJSErrors extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="flex items-center mb-3">
|
||||
<div className="ml-auto">
|
||||
<AvgLabel text="Errors" count={data.errorsCount} />
|
||||
</div>
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "impactedSessionsByJsErrors" : undefined }
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Sessions"
|
||||
}}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Bar minPointSize={1} name="Sessions" dataKey="sessionsCount" stackId="a" fill={colors[0]} />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionsAffectedByJSErrors';
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
@widgetHOC('impactedSessionsBySlowPages', { customParams })
|
||||
export default class SessionsImpactedBySlowRequests extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, period, compare = false, showSync = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "impactedSessionsBySlowPages" : undefined }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Sessions"
|
||||
type="monotone"
|
||||
dataKey="count"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionsImpactedBySlowRequests';
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
import { Styles } from '../common'
|
||||
import { TextEllipsis } from 'UI';
|
||||
|
||||
const Bar = ({ className = '', versions = [], width = 0, avg, domain, colors }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width < 15 ? 15 : width }%`}}>
|
||||
{versions.map((v, i) => {
|
||||
const w = (v.value * 100)/ avg;
|
||||
return (
|
||||
<div key={i} className="text-xs" style={{ width: `${w }%`, backgroundColor: colors[i]}}>
|
||||
<TextEllipsis text={v.key} hintText={
|
||||
<div className="text-sm">
|
||||
<div>Version: {v.key}</div>
|
||||
<div>Sessions: {v.value}</div>
|
||||
</div>
|
||||
} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
<span className="font-medium">{`${avg}`}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.bar {
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& div {
|
||||
padding: 0 5px;
|
||||
height: 20px;
|
||||
color: #FFF;
|
||||
}
|
||||
& div:first-child {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
& div:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, AvgLabel, Styles } from '../common';
|
||||
import Bar from './Bar';
|
||||
|
||||
@widgetHOC('sessionsPerBrowser')
|
||||
export default class SessionsPerBrowser extends React.PureComponent {
|
||||
|
||||
getVersions = item => {
|
||||
return Object.keys(item)
|
||||
.filter(i => i !== 'browser' && i !== 'count')
|
||||
.map(i => ({ key: 'v' +i, value: item[i]}))
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, loading, compare = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const firstAvg = data.chart[0] && data.chart[0].count;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<div className="w-full pt-3" style={{ height: '240px' }}>
|
||||
{data.chart.map((item, i) =>
|
||||
<Bar
|
||||
key={i}
|
||||
className="mb-4"
|
||||
avg={Math.round(item.count)}
|
||||
versions={this.getVersions(item)}
|
||||
width={Math.round((item.count * 100) / firstAvg) - 10}
|
||||
domain={item.browser}
|
||||
colors={colors}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SessionsPerBrowser';
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
|
||||
const Bar = ({ className = '', width = 0, avg, domain, color }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width < 5 ? 5 : width }%`, backgroundColor: color }}></div>
|
||||
<div className="ml-2 shrink-0">
|
||||
<span className="font-medium">{avg}</span>
|
||||
<span> ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm leading-3">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
.bar {
|
||||
height: 10px;
|
||||
background-color: red;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { widgetHOC, Styles } from '../common';
|
||||
import Bar from './Bar';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
|
||||
@widgetHOC('slowestDomains')
|
||||
export default class ResponseTime extends React.PureComponent {
|
||||
render() {
|
||||
const { data, loading, compare = false } = this.props;
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const firstAvg = data.partition.first() && data.partition.first().avg;
|
||||
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.partition && data.partition.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<div className="w-full pt-3" style={{ height: '240px' }}>
|
||||
{data.partition && data.partition.map((item, i) =>
|
||||
<Bar className="mb-4"
|
||||
avg={numberWithCommas(Math.round(item.avg))}
|
||||
width={Math.round((item.avg * 100) / firstAvg) - 20}
|
||||
domain={item.domain}
|
||||
color={colors[i]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SlowestDomains';
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area } from 'recharts';
|
||||
|
||||
const Chart = ({ data }) => {
|
||||
return (
|
||||
<AreaChart width={ 90 } height={ 30 } data={ data.chart } >
|
||||
<Area type="monotone" dataKey="avgDuration" stroke="#3EAAAF" fill="#A8E0DA" fillOpacity={ 0.5 } />
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
Chart.displayName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Tooltip, Icon } from 'UI';
|
||||
import styles from './imageInfo.module.css';
|
||||
|
||||
const ImageInfo = ({ data }) => (
|
||||
<div className={styles.name}>
|
||||
<Tooltip
|
||||
className={styles.popup}
|
||||
title={
|
||||
<img
|
||||
src={`//${data.url}`}
|
||||
className={styles.imagePreview}
|
||||
alt="One of the slowest images"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={styles.imageWrapper}>
|
||||
<Icon name="camera-alt" size="18" color="gray-light" />
|
||||
<div className={styles.label}>{'Preview'}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip disabled content={data.url}>
|
||||
<span>{data.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
ImageInfo.displayName = 'ImageInfo';
|
||||
|
||||
export default ImageInfo;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Table, widgetHOC } from '../common';
|
||||
import Chart from './Chart';
|
||||
import ImageInfo from './ImageInfo';
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'image',
|
||||
title: 'Image',
|
||||
Component: ImageInfo,
|
||||
width: '40%',
|
||||
},
|
||||
{
|
||||
key: 'avgDuration',
|
||||
title: 'Load Time',
|
||||
toText: time => `${ Math.trunc(time) }ms`,
|
||||
width: '25%',
|
||||
},
|
||||
{
|
||||
key: 'trend',
|
||||
title: 'Trend',
|
||||
Component: Chart,
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
key: 'sessions',
|
||||
title: 'Sessions',
|
||||
width: '15%',
|
||||
toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`,
|
||||
className: 'text-left'
|
||||
},
|
||||
];
|
||||
|
||||
@widgetHOC('slowestImages', { fitContent: true })
|
||||
export default class SlowestImages extends React.PureComponent {
|
||||
render() {
|
||||
const { data: images, loading } = this.props;
|
||||
return (
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ images.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<Table
|
||||
cols={ cols }
|
||||
rows={ images }
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.imagePreview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.imageWrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
& > span {
|
||||
height: 16px;
|
||||
}
|
||||
& .label {
|
||||
font-size: 9px;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #f5f5f5 !important;
|
||||
&:before {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SlowestImages';
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react';
|
||||
import { AreaChart, Area } from 'recharts';
|
||||
import { Styles } from '../common';
|
||||
|
||||
const Chart = ({ data, compare }) => {
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
return (
|
||||
<AreaChart width={ 90 } height={ 30 } data={ data.chart } >
|
||||
<Area type="monotone" dataKey="avg" stroke={colors[0]} fill={ colors[3] } fillOpacity={ 0.5 } />
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
Chart.displayName = 'Chart';
|
||||
|
||||
export default Chart;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import React from 'react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useState } from 'react'
|
||||
|
||||
const CopyPath = ({ data }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const copyHandler = () => {
|
||||
copy(data.url);
|
||||
setCopied(true);
|
||||
setTimeout(function() {
|
||||
setCopied(false)
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cursor-pointer color-teal" onClick={copyHandler}>
|
||||
{ copied ? 'Copied' : 'Copy Path'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyPath
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Tooltip } from 'UI';
|
||||
import cn from 'classnames';
|
||||
import styles from './imageInfo.module.css';
|
||||
|
||||
const supportedTypes = ['png', 'jpg', 'jpeg', 'svg'];
|
||||
|
||||
const ImageInfo = ({ data }) => {
|
||||
const canPreview = supportedTypes.includes(data.type);
|
||||
return (
|
||||
<div className={styles.name}>
|
||||
<Tooltip
|
||||
className={styles.popup}
|
||||
disabled={!canPreview}
|
||||
title={
|
||||
<img
|
||||
src={`${data.url}`}
|
||||
className={styles.imagePreview}
|
||||
alt="One of the slowest images"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={cn({ [styles.hasPreview]: canPreview })}>
|
||||
<div className={styles.label}>{data.name}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ImageInfo.displayName = 'ImageInfo';
|
||||
|
||||
export default ImageInfo;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
|
||||
const ResourceType = ({ data : { type = 'js' }, compare }) => {
|
||||
return (
|
||||
<div className={ cn("rounded-full p-2 color-white h-12 w-12 flex items-center justify-center", { 'bg-teal': compare, 'bg-tealx': !compare})}>
|
||||
<span className="overflow-hidden whitespace-no-wrap text-xs">{ type.toUpperCase() }</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResourceType
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent, DropdownPlain } from 'UI';
|
||||
import { Table, widgetHOC } from '../common';
|
||||
import Chart from './Chart';
|
||||
import ImageInfo from './ImageInfo';
|
||||
import { getRE } from 'App/utils';
|
||||
import cn from 'classnames';
|
||||
import stl from './SlowestResources.module.css';
|
||||
import ResourceType from './ResourceType';
|
||||
import CopyPath from './CopyPath';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
|
||||
export const RESOURCE_OPTIONS = [
|
||||
{ text: 'All', value: 'ALL', },
|
||||
{ text: 'CSS', value: 'STYLESHEET', },
|
||||
{ text: 'JS', value: 'SCRIPT', },
|
||||
];
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'type',
|
||||
title: 'Type',
|
||||
Component: ResourceType,
|
||||
className: 'text-center justify-center',
|
||||
cellClass: 'ml-2',
|
||||
width: '8%',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'File Name',
|
||||
Component: ImageInfo,
|
||||
cellClass: '-ml-2',
|
||||
width: '40%',
|
||||
},
|
||||
{
|
||||
key: 'avg',
|
||||
title: 'Load Time',
|
||||
toText: avg => `${ avg ? numberWithCommas(Math.trunc(avg)) : 0} ms`,
|
||||
className: 'justify-center',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
key: 'trend',
|
||||
title: 'Trend',
|
||||
Component: Chart,
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
key: 'copy-path',
|
||||
title: '',
|
||||
Component: CopyPath,
|
||||
cellClass: 'invisible group-hover:visible text-right',
|
||||
width: '15%',
|
||||
}
|
||||
];
|
||||
const WIDGET_KEY = 'slowestResources'
|
||||
|
||||
@widgetHOC(WIDGET_KEY, { fitContent: true })
|
||||
export default class SlowestResources extends React.PureComponent {
|
||||
state = { resource: 'all', search: ''}
|
||||
|
||||
test = (key, value = '') => getRE(key, 'i').test(value);
|
||||
|
||||
write = ({ target: { name, value } }) => {
|
||||
this.setState({ [ name ]: value })
|
||||
};
|
||||
|
||||
writeOption = (e, { name, value }) => {
|
||||
this.setState({ [ name ]: value })
|
||||
this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { [ name ]: value === 'all' ? null : value })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, loading, compare, isTemplate } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ cn(stl.topActions, 'py-3 flex text-right')}>
|
||||
<DropdownPlain
|
||||
name="type"
|
||||
label="Resource"
|
||||
options={ RESOURCE_OPTIONS }
|
||||
onChange={ this.writeOption }
|
||||
/>
|
||||
</div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.size === 0 }
|
||||
title="No recordings found"
|
||||
>
|
||||
<Table cols={ cols } rows={ data } isTemplate={isTemplate} rowClass="group" compare={compare} />
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
.topActions {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 50px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
& .label {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.hasPreview {
|
||||
/* text-decoration: underline; */
|
||||
border-bottom: 1px dotted;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.imagePreview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.imageWrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
& > span {
|
||||
height: 16px;
|
||||
}
|
||||
& .label {
|
||||
font-size: 9px;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #f5f5f5 !important;
|
||||
&:before {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './SlowestResources';
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
|
||||
const Bar = ({ className = '', width = 0, avg, domain, color }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width}%`, backgroundColor: color }}></div>
|
||||
<div className="ml-2">{avg}</div>
|
||||
</div>
|
||||
<div className="text-sm leading-none">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue