feat(ui) - custom metrics - wip

This commit is contained in:
Shekar Siri 2022-03-04 11:32:09 +01:00
parent ed56d206be
commit 984b29f451
15 changed files with 227 additions and 90 deletions

View file

@ -19,6 +19,7 @@ function CustomMetriLineChart(props: Props) {
margin={Styles.chartMargins}
// syncId={ showSync ? "domainsErrors_4xx" : undefined }
onClick={onClick}
isAnimationActive={ false }
>
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
<XAxis

View file

@ -7,11 +7,11 @@ interface Props {
onClick?: (event, index) => void;
}
function CustomMetriPercentage(props: Props) {
const { data } = props;
const { data = {} } = props;
return (
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
<div className="text-6xl">{data.count}</div>
<div className="text-lg mt-6">{`${data.previousCount} ( ${data.countProgress}% ) from previous hour`}</div>
<div className="text-lg mt-6">{`${data.previousCount} ( ${data.countProgress}% ) from previous period.`}</div>
</div>
)
}

View file

@ -1,7 +1,36 @@
import React from 'react'
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
import { LineChart, Line, Legend, PieChart, Pie } from 'recharts';
import { LineChart, Line, Legend, PieChart, Pie, Cell } from 'recharts';
import { Styles } from '../../common';
function renderCustomizedLabel({
cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) {
const RADIAN = Math.PI / 180;
const diffAngle = endAngle - startAngle;
const delta = ((360-diffAngle)/15)-1;
const radius = innerRadius + (outerRadius - innerRadius);
const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN);
const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN);
return (
<text x={x} y={y} fill={color} textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fontSize={12} fontWeight="normal">
{value}
</text>
);
};
function renderCustomizedLabelLine(props){
let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props;
const RADIAN = Math.PI / 180;
const diffAngle = endAngle - startAngle;
const radius = 10 + innerRadius + (outerRadius - innerRadius);
let path='';
for(let i=0;i<((360-diffAngle)/15);i++){
path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} `
}
return (
<polyline points={path} stroke={color} fill="none" />
);
}
interface Props {
data: any;
params: any;
@ -9,35 +38,114 @@ interface Props {
colors: any;
onClick?: (event, index) => void;
}
function CustomMetricPieChart(props: Props) {
const { data, params, colors, onClick = () => null } = props;
const data01 = [
{ "name": "Group A", "value": 400 },
{ "name": "Group B", "value": 300 },
{ "name": "Group C", "value": 300 },
{ "name": "Group D", "value": 200 },
{ "name": "Group E", "value": 278 },
{ "name": "Group F", "value": 189 }
];
const { data = { values: [] }, params, colors, onClick = () => null } = props;
return (
// <div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
// <div className="text-6xl">0%</div>
// <div className="text-lg mt-6">0 ( 0.0% ) from previous hour</div>
// </div>
<ResponsiveContainer height={ 240 } width="100%">
<PieChart width={730} height={250} onClick={onClick}>
<PieChart>
<Pie
data={data01}
dataKey="value"
isAnimationActive={ false }
data={data.values}
dataKey="sessionCount"
nameKey="name"
cx="50%"
cy="50%"
innerRadius={40}
// innerRadius={40}
outerRadius={70}
fill={colors[0]}
// fill={colors[0]}
activeIndex={1}
label
/>
labelLine={({
cx,
cy,
midAngle,
innerRadius,
outerRadius,
value,
index
}) => {
const RADIAN = Math.PI / 180;
let radius1 = 15 + innerRadius + (outerRadius - innerRadius);
let radius2 = innerRadius + (outerRadius - innerRadius);
let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN);
let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN);
let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN);
let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN);
const percentage = value * 100 / data.values.reduce((a, b) => a + b.sessionCount, 0);
if (percentage<3){
return null;
}
return(
<line x1={x1} y1={y1} x2={x2} y2={y2} stroke="#3EAAAF" strokeWidth={1} />
)
}}
label={({
cx,
cy,
midAngle,
innerRadius,
outerRadius,
value,
index
}) => {
const RADIAN = Math.PI / 180;
let radius = 20 + innerRadius + (outerRadius - innerRadius);
let x = cx + radius * Math.cos(-midAngle * RADIAN);
let y = cy + radius * Math.sin(-midAngle * RADIAN);
const percentage = (value / data.values.reduce((a, b) => a + b.sessionCount, 0)) * 100;
if (percentage<3){
return null;
}
return (
<text
x={x}
y={y}
fontWeight="300"
fontSize="12px"
// fontFamily="'Source Sans Pro', 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'"
textAnchor={x > cx ? "start" : "end"}
dominantBaseline="central"
fill='#3EAAAF'
>
{data.values[index].name} - ({value})
</text>
);
}}
// label={({
// cx,
// cy,
// midAngle,
// innerRadius,
// outerRadius,
// value,
// index
// }) => {
// const RADIAN = Math.PI / 180;
// const radius = 30 + innerRadius + (outerRadius - innerRadius);
// const x = cx + radius * Math.cos(-midAngle * RADIAN);
// const y = cy + radius * Math.sin(-midAngle * RADIAN);
// return (
// <text
// x={x}
// y={y}
// fill="#3EAAAF"
// textAnchor={x > cx ? "start" : "end"}
// dominantBaseline="top"
// fontSize={10}
// >
// {data.values[index].name} ({value})
// </text>
// );
// }}
>
{data.values.map((entry, index) => (
<Cell key={`cell-${index}`} fill={Styles.colorsPie[index % Styles.colorsPie.length]} />
))}
</Pie>
<Tooltip {...Styles.tooltip} />
</PieChart>
</ResponsiveContainer>

View file

@ -6,11 +6,11 @@ const cols = [
{
key: 'name',
title: 'Resource',
toText: name => name,
toText: name => name || 'Unidentified',
width: '70%',
},
{
key: 'sessions',
key: 'sessionCount',
title: 'Sessions',
toText: sessions => sessions,
width: '30%',
@ -19,18 +19,13 @@ const cols = [
interface Props {
data: any;
onClick?: (event, index) => void;
}
function CustomMetriTable(props: Props) {
const { data } = props;
const rows = List([
{ name: 'one', sessions: 2 },
{ name: 'two', sessions: 3 },
{ name: 'three', sessions: 4 },
{ name: 'four', sessions: 1 },
{ name: 'five', sessions: 6 },
])
const { data = { values: [] }, onClick = () => null } = props;
const rows = List(data.values);
return (
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
<div className="" style={{ height: '240px'}}>
<Table
small
cols={ cols }

View file

@ -12,6 +12,7 @@ import { setShowAlerts } from 'Duck/dashboard';
import CustomMetriLineChart from '../CustomMetriLineChart';
import CustomMetricPieChart from '../CustomMetricPieChart';
import CustomMetricPercentage from '../CustomMetricPercentage';
import CustomMetricTable from '../CustomMetricTable';
const customParams = rangeName => {
const params = { density: 70 }
@ -101,7 +102,7 @@ function CustomMetricWidget(props: Props) {
return (
<div className={stl.wrapper}>
<div className="flex items-center mb-10 p-2">
<div className="flex items-center p-2">
<div className="font-medium">{metric.name}</div>
<div className="ml-auto flex items-center">
<WidgetIcon className="cursor-pointer mr-6" icon="bell-plus" tooltip="Set Alert" onClick={props.onAlertClick} />
@ -109,7 +110,7 @@ function CustomMetricWidget(props: Props) {
<WidgetIcon className="cursor-pointer" icon="close" tooltip="Hide Metric" onClick={() => updateActiveState(metric.metricId, false)} />
</div>
</div>
<div>
<div className="px-3">
<Loader loading={ loading } size="small">
<NoContent
size="small"
@ -129,7 +130,7 @@ function CustomMetricWidget(props: Props) {
{metric.viewType === 'pieChart' && (
<CustomMetricPieChart
data={ data }
data={ data[0] }
params={ params }
colors={ colors }
onClick={ clickHandler }
@ -138,12 +139,21 @@ function CustomMetricWidget(props: Props) {
{metric.viewType === 'progress' && (
<CustomMetricPercentage
data={ data }
data={ data[0] }
params={ params }
colors={ colors }
onClick={ clickHandler }
/>
)}
{metric.viewType === 'table' && (
<CustomMetricTable
data={ data[0] }
// params={ params }
// colors={ colors }
onClick={ clickHandler }
/>
)}
</>
</ResponsiveContainer>
</NoContent>

View file

@ -1,6 +1,13 @@
.wrapper {
background-color: white;
background-color: $gray-light;
/* border: solid thin $gray-medium; */
border-radius: 3px;
padding: 10px;
padding: 20px;
}
.innerWapper {
border-radius: 3px;
width: 70%;
margin: 0 auto;
background-color: white;
}

View file

@ -106,8 +106,8 @@ function CustomMetricWidget(props: Props) {
onSelect={ chagneViewType }
value={{ value: metric.viewType }}
list={ [
{ value: 'lineChart', icon: 'graph-up-arrow' },
{ value: 'progress', icon: 'hash' },
{ value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' },
{ value: 'progress', name: 'Progress', icon: 'hash' },
]}
/>
)}
@ -121,8 +121,8 @@ function CustomMetricWidget(props: Props) {
onSelect={ chagneViewType }
value={{ value: metric.viewType }}
list={[
{ value: 'table', icon: 'table' },
{ value: 'pieChart', icon: 'graph-up-arrow' },
{ value: 'table', name: 'Table', icon: 'table' },
{ value: 'pieChart', name: 'Chart', icon: 'graph-up-arrow' },
]}
/>
)}
@ -139,45 +139,50 @@ function CustomMetricWidget(props: Props) {
</div>
</div>
<div className={stl.wrapper}>
<div>
<div className={stl.innerWapper}>
<Loader loading={ loading } size="small">
<NoContent
size="small"
show={ data.length === 0 }
>
<div className="p-4 font-medium">
{metric.name}
</div>
<div className="px-4 pb-4">
{ isTimeSeries && (
<>
{ metric.viewType === 'progress' && (
<CustomMetricPercentage
data={data[0]}
colors={colors}
params={params}
/>
)}
{ metric.viewType === 'lineChart' && (
<CustomMetriLineChart
data={data}
seriesMap={seriesMap}
colors={colors}
params={params}
/>
)}
</>
)}
{ isTable && (
<div className="p-3">
{ metric.viewType === 'table' ? (
<CustomMetricTable data={data} />
) : (
<CustomMetricPieChart
data={data}
<>
{ metric.viewType === 'progress' && (
<CustomMetricPercentage
data={data[0]}
colors={colors}
params={params}
/>
)}
</div>
)}
)}
{ metric.viewType === 'lineChart' && (
<CustomMetriLineChart
data={data}
seriesMap={seriesMap}
colors={colors}
params={params}
/>
)}
</>
)}
{ isTable && (
<>
{ metric.viewType === 'table' ? (
<CustomMetricTable data={data[0]} />
) : (
<CustomMetricPieChart
data={data[0]}
colors={colors}
params={params}
/>
)}
</>
)}
</div>
</NoContent>
</Loader>
</div>

View file

@ -5,6 +5,7 @@ const colorsx = ['#256669', '#38999e', '#3eaaaf', '#51b3b7', '#78c4c7', '#9fd5d7
const compareColors = ['#394EFF', '#4D5FFF', '#808DFF', '#B3BBFF', '#E5E8FF'];
const compareColorsx = ["#222F99", "#2E3ECC", "#394EFF", "#6171FF", "#8895FF", "#B0B8FF", "#D7DCFF"].reverse();
const customMetricColors = ['#3EAAAF', '#394EFF', '#666666'];
const colorsPie = colors.concat(["#DDDDDD"]);
const countView = count => {
const isMoreThanK = count >= 1000;
@ -14,6 +15,7 @@ const countView = count => {
export default {
customMetricColors,
colors,
colorsPie,
colorsx,
compareColors,
compareColorsx,

View file

@ -45,14 +45,14 @@ export default class Table extends React.PureComponent {
)) }
</div>
{ rows.size > (small ? 3 : 5) && !showAll &&
<div className="w-full flex justify-center mt-3">
<div className="w-full flex justify-center">
<Button
onClick={ this.onLoadMoreClick }
plain
small
className="text-center"
>
{ 'Load More' }
{ rows.size + ' More' }
</Button>
</div>
}

View file

@ -25,6 +25,10 @@ function CustomMetricForm(props: Props) {
// const metricOfOptions = metricOf.filter(i => i.key === metric.metricType);
const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries');
const tableOptions = metricOf.filter(i => i.key === 'table');
const isTable = metric.metricType === 'table';
const isTimeSeries = metric.metricType === 'timeseries';
const _issueOptions = [{ text: 'All', value: '' }].concat(issueOptions);
const addSeries = () => {
props.addSeries();
@ -44,7 +48,7 @@ function CustomMetricForm(props: Props) {
if (name === 'metricOf') {
if (value === 'ISSUES') {
props.editMetric({ metricValue: [issueOptions[0].value] }, false);
props.editMetric({ metricValue: [''] }, false);
}
}
@ -140,7 +144,7 @@ function CustomMetricForm(props: Props) {
<span className="mx-3">issue type</span>
<DropdownPlain
name="metricValue"
options={issueOptions}
options={_issueOptions}
value={ metric.metricValue[0] }
onChange={ writeOption }
/>
@ -165,9 +169,10 @@ function CustomMetricForm(props: Props) {
<div className="form-group">
<label className="font-medium">Chart Series</label>
{metric.series && metric.series.size > 0 && metric.series.map((series: any, index: number) => (
{metric.series && metric.series.size > 0 && metric.series.take(isTable ? 1 : metric.series.size).map((series: any, index: number) => (
<div className="mb-2">
<FilterSeries
hideHeader={ isTable }
seriesIndex={index}
series={series}
onRemoveSeries={() => removeSeries(index)}
@ -177,9 +182,11 @@ function CustomMetricForm(props: Props) {
))}
</div>
<div className={cn("flex justify-end -my-4", {'disabled' : metric.series.size > 2})}>
<IconButton hover type="button" onClick={addSeries} primaryText label="SERIES" icon="plus" />
</div>
{ isTimeSeries && (
<div className={cn("flex justify-end -my-4", {'disabled' : metric.series.size > 2})}>
<IconButton hover type="button" onClick={addSeries} primaryText label="SERIES" icon="plus" />
</div>
)}
<div className="my-8" />

View file

@ -25,10 +25,12 @@ interface Props {
editSeriesFilterFilter: typeof editSeriesFilterFilter;
editSeriesFilter: typeof editSeriesFilter;
removeSeriesFilterFilter: typeof removeSeriesFilterFilter;
hideHeader?: boolean;
emptyMessage?: any;
}
function FilterSeries(props: Props) {
const { canDelete } = props;
const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props;
const [expanded, setExpanded] = useState(true)
const { series, seriesIndex } = props;
@ -51,7 +53,7 @@ function FilterSeries(props: Props) {
return (
<div className="border rounded bg-white">
<div className="border-b px-5 h-12 flex items-center relative">
<div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}>
<div className="mr-auto">
<SeriesName name={series.name} onUpdate={(name) => props.updateSeries(seriesIndex, { name }) } />
</div>
@ -78,7 +80,7 @@ function FilterSeries(props: Props) {
onChangeEventsOrder={onChangeEventsOrder}
/>
): (
<div className="color-gray-medium">Add user event or filter to define the series by clicking Add Step.</div>
<div className="color-gray-medium">{emptyMessage}</div>
)}
</div>
<div className="px-6 border-t h-12 flex items-center -mx-4">

View file

@ -28,8 +28,8 @@ class SegmentSelection extends React.Component {
data-active={ this.props.value && this.props.value.value === item.value }
onClick={ () => !item.disabled && this.setActiveItem(item) }
>
{ item.icon && <Icon name={ item.icon } size={(size === "extraSmall" || icons) ? 14 : 20} marginRight={ item.name ? "10" : "" } /> }
<div>{ item.name }</div>
{ item.icon && <Icon name={ item.icon } size={(size === "extraSmall" || icons) ? 14 : 20} marginRight={ item.name ? "6" : "" } /> }
<div className="leading-none">{ item.name }</div>
</div>
}
disabled={!item.disabled}

View file

@ -72,7 +72,7 @@
}
.extraSmall .item {
padding: 0 4px !important;
padding: 2px 4px !important;
font-size: 12px;
}

View file

@ -31,7 +31,7 @@ export default Record({
metricOf: 'USERID',
metricValue: ['sessionCount'],
metricFormat: 'sessionCount',
viewType: 'table',
viewType: 'pieChart',
series: List(),
isPublic: true,
startDate: '',

View file

@ -44,7 +44,7 @@ export const filtersMap = {
[FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true },
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: filterOptions.issueOptions },
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions },
}
export const liveFiltersMap = {