feat(ui) - dashboard - widget chart review and updates
This commit is contained in:
parent
bb6401b675
commit
471d6068c1
33 changed files with 373 additions and 164 deletions
|
|
@ -10,25 +10,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function BreakdownOfLoadedResources(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 28 }
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={params.density/7} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={metric.params.density/7} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
|
|||
|
|
@ -10,25 +10,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function CPULoad(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import React from 'react';
|
||||
import { Loader, NoContent } from 'UI';
|
||||
import { Styles, Table } from '../../common';
|
||||
import { getRE } from 'App/utils';
|
||||
import ImageInfo from './ImageInfo';
|
||||
import MethodType from './MethodType';
|
||||
import cn from 'classnames';
|
||||
import stl from './callWithErrors.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%',
|
||||
}
|
||||
];
|
||||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function CallWithErrors(props: Props) {
|
||||
const { data, metric } = props;
|
||||
const [search, setSearch] = React.useState('')
|
||||
const test = (value = '', serach) => getRE(serach, 'i').test(value);
|
||||
const _data = search ? metric.data.chart.filter(i => test(i.urlHostpath, search)) : metric.data.chart.images;
|
||||
|
||||
console.log('data', metric.data)
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
setSearch(value)
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ metric.data.chart.length === 0 }
|
||||
style={{ height: '240px'}}
|
||||
>
|
||||
<div style={{ height: '240px'}}>
|
||||
<div className={ cn(stl.topActions, 'py-3 flex text-right')}>
|
||||
<input disabled={metric.data.chart.length === 0} className={stl.searchField} name="search" placeholder="Filter by Path" onChange={write} />
|
||||
</div>
|
||||
<Table
|
||||
cols={ cols }
|
||||
rows={ _data }
|
||||
/>
|
||||
</div>
|
||||
</NoContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CallWithErrors;
|
||||
16
frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/Chart.js
vendored
Normal file
16
frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/Chart.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Popup, Icon, TextEllipsis } from 'UI';
|
||||
import styles from './imageInfo.css';
|
||||
|
||||
const ImageInfo = ({ data }) => (
|
||||
<div className={ styles.name }>
|
||||
<TextEllipsis text={data.urlHostpath} />
|
||||
</div>
|
||||
);
|
||||
|
||||
ImageInfo.displayName = 'ImageInfo';
|
||||
|
||||
export default ImageInfo;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import { Label } from 'UI';
|
||||
|
||||
const MethodType = ({ data }) => {
|
||||
return (
|
||||
<Label className="ml-1">{data.method}</Label>
|
||||
)
|
||||
}
|
||||
|
||||
export default MethodType
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './CallWithErrors'
|
||||
|
|
@ -9,26 +9,26 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function CallsErrors4xx(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
console.log('asd', metric.data.namesMap)
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
data={metric.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}
|
||||
interval={metric.params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
@ -37,9 +37,9 @@ function CallsErrors4xx(props: Props) {
|
|||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
{/* { data.namesMap.map((key, index) => (
|
||||
{ Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => (
|
||||
<Line key={key} name={key} type="monotone" dataKey={key} stroke={Styles.colors[index]} fillOpacity={ 1 } strokeWidth={ 2 } strokeOpacity={ 0.8 } fill="url(#colorCount)" dot={false} />
|
||||
))} */}
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
|
|
|
|||
|
|
@ -9,26 +9,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function CallsErrors5xx(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
data={metric.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}
|
||||
interval={metric.params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
@ -37,9 +36,9 @@ function CallsErrors5xx(props: Props) {
|
|||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
{/* { data.namesMap.map((key, index) => (
|
||||
{ Array.isArray(metric.data.namesMap) && metric.data.namesMap.map((key, index) => (
|
||||
<Line key={key} name={key} type="monotone" dataKey={key} stroke={Styles.colors[index]} fillOpacity={ 1 } strokeWidth={ 2 } strokeOpacity={ 0.8 } fill="url(#colorCount)" dot={false} />
|
||||
))} */}
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
|
|
|
|||
|
|
@ -10,24 +10,24 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function Crashes(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
@ -38,7 +38,7 @@ function Crashes(props: Props) {
|
|||
<Area
|
||||
name="Crashes"
|
||||
type="monotone"
|
||||
dataKey="avg"
|
||||
dataKey="count"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -18,15 +18,14 @@ interface Props {
|
|||
optionsLoading: any
|
||||
fetchOptions: any
|
||||
options: any
|
||||
metric?: any
|
||||
}
|
||||
function DomBuildingTime(props: Props) {
|
||||
const { data, optionsLoading } = props;
|
||||
const { data, optionsLoading, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
|
||||
const onSelect = (params) => {
|
||||
const _params = { density: 70 }
|
||||
// const _params = { density: 70 }
|
||||
console.log('params', params) // TODO reload the data with new params;
|
||||
// this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value })
|
||||
}
|
||||
|
|
@ -34,7 +33,7 @@ function DomBuildingTime(props: Props) {
|
|||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center mb-3">
|
||||
|
|
@ -45,7 +44,7 @@ function DomBuildingTime(props: Props) {
|
|||
onSelect={onSelect}
|
||||
placeholder="Search for Page"
|
||||
/>
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(data.avg)} unit="ms" />
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(metric.data.avg)} unit="ms" />
|
||||
</div>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
|
|
@ -54,7 +53,7 @@ function DomBuildingTime(props: Props) {
|
|||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
@ -66,7 +65,7 @@ function DomBuildingTime(props: Props) {
|
|||
name="Avg"
|
||||
type="monotone"
|
||||
// unit="%"
|
||||
dataKey="avg"
|
||||
dataKey="value"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -9,26 +9,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function ErrorsByOrigin(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
data={metric.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}
|
||||
interval={metric.params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
|
|||
|
|
@ -9,26 +9,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function ErrorsByType(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
data={metric.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}
|
||||
interval={metric.params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar';
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function ErrorsPerDomain(props: Props) {
|
||||
const { data } = props;
|
||||
console.log('ErrorsPerDomain', data);
|
||||
const { data, metric } = props;
|
||||
// const firstAvg = 10;
|
||||
const firstAvg = data.chart[0] && data.chart[0].errorsCount;
|
||||
const firstAvg = metric.data.chart[0] && metric.data.chart[0].errorsCount;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<div className="w-full" style={{ height: '240px' }}>
|
||||
{data.chart.map((item, i) =>
|
||||
{metric.data.chart.map((item, i) =>
|
||||
<Bar
|
||||
key={i}
|
||||
className="mb-2"
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function FPS(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
|
|
@ -27,12 +27,12 @@ function FPS(props: Props) {
|
|||
</div>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
@ -43,7 +43,7 @@ function FPS(props: Props) {
|
|||
<Area
|
||||
name="Avg"
|
||||
type="monotone"
|
||||
dataKey="avg"
|
||||
dataKey="value"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function MemoryConsumption(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center justify-end mb-3">
|
||||
|
|
@ -27,12 +27,12 @@ function MemoryConsumption(props: Props) {
|
|||
</div>
|
||||
<ResponsiveContainer height={ 207 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
@ -44,7 +44,7 @@ function MemoryConsumption(props: Props) {
|
|||
name="Avg"
|
||||
unit=" mb"
|
||||
type="monotone"
|
||||
dataKey="avg"
|
||||
dataKey="value"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -37,21 +37,22 @@ const cols = [
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function MissingResources(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
title="No resources missing."
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<div style={{ height: '240px'}}>
|
||||
<Table
|
||||
small
|
||||
cols={ cols }
|
||||
rows={ List(data.chart) }
|
||||
rows={ List(metric.data.chart) }
|
||||
rowClass="group"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,19 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function ResourceLoadedVsResponseEnd(props: Props) {
|
||||
const { data } = props;
|
||||
const params = { density: 70 }
|
||||
const { data, metric } = props;
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
data={metric.data.chart}
|
||||
margin={ Styles.chartMargins}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
|
|
@ -28,7 +28,7 @@ function ResourceLoadedVsResponseEnd(props: Props) {
|
|||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
// interval={3}
|
||||
interval={(params.density / 7)}
|
||||
interval={(metric.params.density / 7)}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
|
|||
|
|
@ -8,28 +8,27 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function ResourceLoadedVsVisuallyComplete(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<ComposedChart
|
||||
data={data.chart}
|
||||
data={metric.data.chart}
|
||||
margin={ Styles.chartMargins}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
// interval={3}
|
||||
interval={(params.density / 7)}
|
||||
interval={(metric.params.density / 7)}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
|
|||
|
|
@ -27,16 +27,16 @@ interface Props {
|
|||
optionsLoading: any
|
||||
fetchOptions: any
|
||||
options: any
|
||||
metric?: any
|
||||
}
|
||||
function ResourceLoadingTime(props: Props) {
|
||||
const { data, optionsLoading } = props;
|
||||
const { data, optionsLoading, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
const [autoCompleteSelected, setSutoCompleteSelected] = React.useState('');
|
||||
const [type, setType] = React.useState('');
|
||||
|
||||
const onSelect = (params) => {
|
||||
const _params = { density: 70 }
|
||||
// const _params = { density: 70 }
|
||||
setSutoCompleteSelected(params.value);
|
||||
console.log('params', params) // TODO reload the data with new params;
|
||||
// this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value })
|
||||
|
|
@ -52,7 +52,7 @@ function ResourceLoadingTime(props: Props) {
|
|||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center mb-3">
|
||||
|
|
@ -80,17 +80,17 @@ function ResourceLoadingTime(props: Props) {
|
|||
</div>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Resource Fetch Time (ms)" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ interface Props {
|
|||
optionsLoading: any
|
||||
fetchOptions: any
|
||||
options: any
|
||||
metric?: any
|
||||
}
|
||||
function ResponseTime(props: Props) {
|
||||
const { data, optionsLoading } = props;
|
||||
const { data, optionsLoading, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
|
||||
const onSelect = (params) => {
|
||||
const _params = { density: 70 }
|
||||
// const _params = { density: 70 }
|
||||
console.log('params', params) // TODO reload the data with new params;
|
||||
// this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value })
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ function ResponseTime(props: Props) {
|
|||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center mb-3">
|
||||
|
|
@ -45,7 +45,7 @@ function ResponseTime(props: Props) {
|
|||
onSelect={onSelect}
|
||||
placeholder="Search for Page"
|
||||
/>
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(data.avg)} unit="ms" />
|
||||
<AvgLabel className="ml-auto" text="Avg" count={Math.round(metric.data.avg)} unit="ms" />
|
||||
</div>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
|
|
@ -54,7 +54,7 @@ function ResponseTime(props: Props) {
|
|||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
|
|
@ -66,7 +66,7 @@ function ResponseTime(props: Props) {
|
|||
name="Avg"
|
||||
type="monotone"
|
||||
unit=" ms"
|
||||
dataKey="avgCpu"
|
||||
dataKey="value"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -9,26 +9,25 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function SessionsAffectedByJSErrors(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<BarChart
|
||||
data={data.chart}
|
||||
data={metric.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}
|
||||
interval={metric.params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
|
|
|
|||
|
|
@ -10,30 +10,32 @@ import {
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function SessionsImpactedBySlowRequests(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
console.log('SessionsImpactedBySlowRequests', metric.data)
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
tickFormatter={val => Styles.tickFormatter(val)}
|
||||
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }}
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { NoContent } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar';
|
||||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function SessionsPerBrowser(props: Props) {
|
||||
const { data, metric } = props;
|
||||
const firstAvg = metric.data.chart[0] && metric.data.chart[0].count;
|
||||
|
||||
const getVersions = item => {
|
||||
return Object.keys(item)
|
||||
.filter(i => i !== 'browser' && i !== 'count')
|
||||
.map(i => ({ key: 'v' +i, value: item[i]}))
|
||||
}
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<div className="w-full" style={{ height: '240px' }}>
|
||||
{metric.data.chart.map((item, i) =>
|
||||
<Bar
|
||||
key={i}
|
||||
className="mb-4"
|
||||
avg={Math.round(item.count)}
|
||||
versions={getVersions(item)}
|
||||
width={Math.round((item.count * 100) / firstAvg) - 10}
|
||||
domain={item.browser}
|
||||
colors={Styles.colors}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</NoContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default SessionsPerBrowser;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SessionsPerBrowser'
|
||||
|
|
@ -6,17 +6,18 @@ import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar';
|
|||
|
||||
interface Props {
|
||||
data: any
|
||||
metric?: any
|
||||
}
|
||||
function SlowestDomains(props: Props) {
|
||||
const { data } = props;
|
||||
const firstAvg = data.chart[0] && data.chart[0].errorsCount;
|
||||
const { data, metric } = props;
|
||||
const firstAvg = metric.data.chart[0] && metric.data.chart[0].errorsCount;
|
||||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<div className="w-full" style={{ height: '240px' }}>
|
||||
{data.chart.map((item, i) =>
|
||||
{metric.data.chart.map((item, i) =>
|
||||
<Bar
|
||||
key={i}
|
||||
className="mb-2"
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ interface Props {
|
|||
optionsLoading: any
|
||||
fetchOptions: any
|
||||
options: any
|
||||
metric?: any
|
||||
}
|
||||
function TimeToRender(props: Props) {
|
||||
const { data, optionsLoading } = props;
|
||||
const { data, optionsLoading, metric } = props;
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const params = { density: 70 }
|
||||
|
||||
|
||||
const onSelect = (params) => {
|
||||
const _params = { density: 70 }
|
||||
// const _params = { density: 70 }
|
||||
console.log('params', params) // TODO reload the data with new params;
|
||||
// this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value })
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ function TimeToRender(props: Props) {
|
|||
return (
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ metric.data.chart.length === 0 }
|
||||
>
|
||||
<>
|
||||
<div className="flex items-center mb-3">
|
||||
|
|
@ -49,12 +49,12 @@ function TimeToRender(props: Props) {
|
|||
</div>
|
||||
<ResponsiveContainer height={ 200 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
data={ metric.data.chart }
|
||||
margin={ Styles.chartMargins }
|
||||
>
|
||||
{gradientDef}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(params.density/7)} />
|
||||
<XAxis {...Styles.xaxis} dataKey="time" interval={(metric.params.density/7)} />
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
// allowDecimals={false}
|
||||
|
|
@ -66,7 +66,7 @@ function TimeToRender(props: Props) {
|
|||
name="Avg"
|
||||
type="monotone"
|
||||
unit=" ms"
|
||||
dataKey="avgCpu"
|
||||
dataKey="value"
|
||||
stroke={Styles.colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function WidgetChart(props: Props) {
|
|||
if (isOverviewWidget) {
|
||||
return <CustomMetricOverviewChart data={data} />
|
||||
}
|
||||
return <WidgetPredefinedChart data={data} predefinedKey={metric.predefinedKey} />
|
||||
return <WidgetPredefinedChart metric={metric} data={data} predefinedKey={metric.predefinedKey} />
|
||||
}
|
||||
|
||||
if (metricType === 'timeseries') {
|
||||
|
|
|
|||
|
|
@ -22,64 +22,69 @@ import ResourceLoadingTime from 'App/components/Dashboard/Widgets/PredefinedWidg
|
|||
import BreakdownOfLoadedResources from 'App/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources';
|
||||
import MissingResources from 'App/components/Dashboard/Widgets/PredefinedWidgets/MissingResources';
|
||||
import ResourceLoadedVsResponseEnd from 'App/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd';
|
||||
import SessionsPerBrowser from 'App/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser';
|
||||
import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors';
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
predefinedKey: string
|
||||
metric?: any;
|
||||
}
|
||||
function WidgetPredefinedChart(props: Props) {
|
||||
const { data, predefinedKey } = props;
|
||||
const { data, predefinedKey, metric } = props;
|
||||
|
||||
const renderWidget = () => {
|
||||
switch (predefinedKey) {
|
||||
// ERRORS
|
||||
case 'errors_per_type':
|
||||
return <ErrorsByType data={data} />
|
||||
return <ErrorsByType data={data} metric={metric} />
|
||||
case 'errors_per_domains':
|
||||
return <ErrorsPerDomain data={data} />
|
||||
return <ErrorsPerDomain data={data} metric={metric} />
|
||||
case 'resources_by_party':
|
||||
return <ErrorsByOrigin data={data} />
|
||||
return <ErrorsByOrigin data={data} metric={metric} />
|
||||
case 'impacted_sessions_by_js_errors':
|
||||
return <SessionsAffectedByJSErrors data={data} />
|
||||
return <SessionsAffectedByJSErrors data={data} metric={metric} />
|
||||
case 'domains_errors_4xx':
|
||||
return <CallsErrors4xx data={data} />
|
||||
return <CallsErrors4xx data={data} metric={metric} />
|
||||
case 'domains_errors_5xx':
|
||||
return <CallsErrors5xx data={data} />
|
||||
return <CallsErrors5xx data={data} metric={metric} />
|
||||
case 'calls_errors':
|
||||
return <CallWithErrors data={data} metric={metric} />
|
||||
|
||||
// PERFORMANCE
|
||||
// case 'impacted_sessions_by_slow_pages':
|
||||
// case 'pages_response_time_distribution':
|
||||
// case 'speed_location':
|
||||
case 'cpu':
|
||||
return <CPULoad data={data} />
|
||||
return <CPULoad data={data} metric={metric} />
|
||||
case 'crashes':
|
||||
return <Crashes data={data} />
|
||||
return <Crashes data={data} metric={metric} />
|
||||
case 'pages_dom_buildtime':
|
||||
return <DomBuildingTime data={data} />
|
||||
return <DomBuildingTime data={data} metric={metric} />
|
||||
case 'fps':
|
||||
return <FPS data={data} />
|
||||
return <FPS data={data} metric={metric} />
|
||||
case 'memory_consumption':
|
||||
return <MemoryConsumption data={data} />
|
||||
return <MemoryConsumption data={data} metric={metric} />
|
||||
case 'pages_response_time':
|
||||
return <ResponseTime data={data} />
|
||||
return <ResponseTime data={data} metric={metric} />
|
||||
case 'resources_vs_visually_complete':
|
||||
return <ResourceLoadedVsVisuallyComplete data={data} />
|
||||
return <ResourceLoadedVsVisuallyComplete data={data} metric={metric} />
|
||||
case 'sessions_per_browser':
|
||||
return <SessionsImpactedBySlowRequests data={data} />
|
||||
return <SessionsPerBrowser data={data} metric={metric} />
|
||||
case 'slowest_domains':
|
||||
return <SlowestDomains data={data} />
|
||||
return <SlowestDomains data={data} metric={metric} />
|
||||
case 'time_to_render':
|
||||
return <TimeToRender data={data} />
|
||||
return <TimeToRender data={data} metric={metric} />
|
||||
|
||||
// Resources
|
||||
case 'resources_count_by_type':
|
||||
return <BreakdownOfLoadedResources data={data} />
|
||||
return <BreakdownOfLoadedResources data={data} metric={metric} />
|
||||
case 'missing_resources':
|
||||
return <MissingResources data={data} />
|
||||
return <MissingResources data={data} metric={metric} />
|
||||
case 'resource_type_vs_response_end':
|
||||
return <ResourceLoadedVsResponseEnd data={data} />
|
||||
return <ResourceLoadedVsResponseEnd data={data} metric={metric} />
|
||||
case 'resources_loading_time':
|
||||
return <ResourceLoadingTime data={data} />
|
||||
return <ResourceLoadingTime data={data} metric={metric} />
|
||||
// case 'slowest_resources':
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -420,27 +420,6 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
metric.setData(_data)
|
||||
resolve(_data);
|
||||
} else {
|
||||
// if (metric.predefinedKey === 'errors_per_domains') {
|
||||
// console.log('errors_per_domains', data)
|
||||
// data.chart = data
|
||||
// } else {
|
||||
// data.chart = getChartFormatter(this.period)(Array.isArray(data) ? data : data.chart)
|
||||
// }
|
||||
// data.namesMap = Array.isArray(data) ? data
|
||||
// .map(i => Object.keys(i))
|
||||
// .flat()
|
||||
// .filter(i => i !== 'time' && i !== 'timestamp')
|
||||
// .reduce((unique: any, item: any) => {
|
||||
// if (!unique.includes(item)) {
|
||||
// unique.push(item);
|
||||
// }
|
||||
// return unique;
|
||||
// }, []) : data.chart;
|
||||
// console.log('map', data.namesMap)
|
||||
// const _data = { ...data, namesMap: data.namesMap, chart: data.chart }
|
||||
// metric.setData(_data)
|
||||
// resolve(_data);
|
||||
|
||||
const _data = {
|
||||
...data,
|
||||
}
|
||||
|
|
@ -457,7 +436,7 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
return unique;
|
||||
}, [])
|
||||
} else {
|
||||
_data['chart'] = Array.isArray(data) ? data : []
|
||||
_data['chart'] = getChartFormatter(this.period)(Array.isArray(data) ? data : []);
|
||||
_data['namesMap'] = Array.isArray(data) ? data.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export interface IWidget {
|
|||
colSpan: number
|
||||
predefinedKey: string
|
||||
|
||||
params: any
|
||||
|
||||
udpateKey(key: string, value: any): void
|
||||
removeSeries(index: number): void
|
||||
addSeries(): void
|
||||
|
|
@ -57,6 +59,7 @@ export default class Widget implements IWidget {
|
|||
dashboards: any[] = []
|
||||
dashboardIds: any[] = []
|
||||
config: any = {}
|
||||
params: any = { density: 70 }
|
||||
|
||||
position: number = 0
|
||||
data: any = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue