change(ui): remove unused files

This commit is contained in:
sylenien 2023-01-05 17:34:40 +01:00 committed by Delirium
parent c2d9a9768a
commit 0ad417d0dc
153 changed files with 4 additions and 5311 deletions

View file

@ -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)

View file

@ -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;
}
}

View file

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

View file

@ -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>
);
}
}

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View file

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

View file

@ -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>
);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -1,6 +0,0 @@
.wrapper {
background-color: white;
/* border: solid thin $gray-medium; */
border-radius: 3px;
padding: 10px;
}

View file

@ -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>
);

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

@ -1,4 +0,0 @@
.label {
max-width: 65px;
line-height: 14px;
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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

View file

@ -1,6 +0,0 @@
.bar {
height: 5px;
background-color: red;
width: 100%;
border-radius: 3px;
}

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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;

View file

@ -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

View file

@ -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>
);
}
}

View file

@ -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>
);
}
}

View file

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

View file

@ -1,10 +0,0 @@
.name {
letter-spacing: -.04em;
font-size: .9rem;
cursor: pointer;
}
.timings {
color: $gray-medium;
font-size: 12px;
}

View file

@ -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;

View file

@ -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>
);
}
}

View file

@ -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>
);
}
}

View file

@ -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;
}

View file

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

View file

@ -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>
);
}
}

View file

@ -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>
);
}
}

View file

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

View file

@ -1,3 +0,0 @@
.muted {
color: rgba(0, 0, 0, 0.3);
}

View file

@ -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>
);
}
}

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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>
);
}
}

View file

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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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>
);
}
}

View file

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

View file

@ -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

View file

@ -1,6 +0,0 @@
.bar {
height: 10px;
background-color: red;
width: 100%;
border-radius: 3px;
}

View file

@ -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>
);
}
}

View file

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

View file

@ -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;

View file

@ -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;

View file

@ -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>
);
}
}

View file

@ -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;
}
}

View file

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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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>
);
}
}

View file

@ -1,7 +0,0 @@
.topActions {
position: absolute;
top: 0px;
right: 50px;
display: flex;
justify-content: flex-end;
}

View file

@ -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;
}
}

View file

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

View file

@ -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