feat(ui) - custom metrics - wip

This commit is contained in:
Shekar Siri 2022-03-01 13:29:20 +01:00
parent 0791744de7
commit 82e572b9d1
21 changed files with 266 additions and 72 deletions

View file

@ -0,0 +1,56 @@
import React from 'react'
import { Styles } from '../../common';
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
import { LineChart, Line, Legend } from 'recharts';
interface Props {
data: any;
params: any;
seriesMap: any;
colors: any;
}
function CustomMetriLineChart(props: Props) {
const { data, params, seriesMap, colors } = props;
return (
<ResponsiveContainer height={ 240 } width="100%">
<LineChart
data={ data }
margin={Styles.chartMargins}
// syncId={ showSync ? "domainsErrors_4xx" : 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"
}}
/>
<Legend />
<Tooltip {...Styles.tooltip} />
{ seriesMap.map((key, index) => (
<Line
key={key}
name={key}
type="monotone"
dataKey={key}
stroke={colors[index]}
fillOpacity={ 1 }
strokeWidth={ 2 }
strokeOpacity={ 0.6 }
// fill="url(#colorCount)"
dot={false}
/>
))}
</LineChart>
</ResponsiveContainer>
)
}
export default CustomMetriLineChart

View file

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

View file

@ -0,0 +1,16 @@
import React from 'react'
interface Props {
data: any;
}
function CustomMetriPercentage(props: Props) {
const { data } = 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>
)
}
export default CustomMetriPercentage;

View file

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

View file

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

View file

@ -0,0 +1,16 @@
import React from 'react'
interface Props {
data: any;
}
function CustomMetricPieChart(props: Props) {
const { data } = 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>
)
}
export default CustomMetricPieChart;

View file

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

View file

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

View file

@ -0,0 +1,44 @@
import React from 'react'
import { Table } from '../../common';
import { List } from 'immutable';
const cols = [
{
key: 'name',
title: 'Resource',
toText: name => name,
width: '70%',
},
{
key: 'sessions',
title: 'Sessions',
toText: sessions => sessions,
width: '30%',
},
];
interface Props {
data: any;
}
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 },
])
return (
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
<Table
small
cols={ cols }
rows={ rows }
rowClass="group"
/>
</div>
)
}
export default CustomMetriTable;

View file

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

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Loader, NoContent, Icon } from 'UI';
import { Loader, NoContent, SegmentSelection, Icon } from 'UI';
import { Styles } from '../../common';
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts';
import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
@ -9,6 +9,9 @@ import { getChartFormatter } from 'Types/dashboard/helper';
import { remove } from 'Duck/customMetrics';
import DateRange from 'Shared/DateRange';
import { edit } from 'Duck/customMetrics';
import CustomMetriLineChart from '../CustomMetriLineChart';
import CustomMetriPercentage from '../CustomMetriPercentage';
import CustomMetricTable from '../CustomMetricTable';
import APIClient from 'App/api_client';
@ -83,11 +86,28 @@ function CustomMetricWidget(props: Props) {
props.edit({ ...changedDates, rangeName: changedDates.rangeValue });
}
const chagneViewType = (e, { name, value }) => {
props.edit({ [ name ]: value });
}
return (
<div className="mb-10">
<div className="flex items-center mb-4">
<div className="mr-auto font-medium">Preview</div>
<div>
<div className="flex items-center">
<SegmentSelection
name="viewType"
className="my-3"
size="extraSmall"
onSelect={ chagneViewType }
value={{ value: metric.viewType }}
list={ [
{ value: 'chart', icon: 'graph-up-arrow' },
{ value: 'percent', icon: 'hash' },
]}
/>
<div className="mx-2" />
<span className="mr-1 color-gray-medium">Time Range</span>
<DateRange
rangeValue={metric.rangeName}
startDate={metric.startDate}
@ -105,44 +125,27 @@ function CustomMetricWidget(props: Props) {
size="small"
show={ data.length === 0 }
>
<ResponsiveContainer height={ 240 } width="100%">
<LineChart
data={ data }
margin={Styles.chartMargins}
syncId={ showSync ? "domainsErrors_4xx" : undefined }
>
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
<XAxis
{...Styles.xaxis}
dataKey="time"
interval={params.density/7}
{ metric.metricType === 'timeseries' && (
<>
{ metric.viewType === 'percent' && (
<CustomMetriPercentage data={data} />
)}
{ metric.viewType === 'chart' && (
<CustomMetriLineChart
data={data}
seriesMap={seriesMap}
colors={colors}
params={params}
/>
<YAxis
{...Styles.yaxis}
allowDecimals={false}
label={{
...Styles.axisLabelLeft,
value: "Number of Sessions"
}}
/>
<Legend />
<Tooltip {...Styles.tooltip} />
{ seriesMap.map((key, index) => (
<Line
key={key}
name={key}
type="monotone"
dataKey={key}
stroke={colors[index]}
fillOpacity={ 1 }
strokeWidth={ 2 }
strokeOpacity={ 0.6 }
// fill="url(#colorCount)"
dot={false}
/>
))}
</LineChart>
</ResponsiveContainer>
)}
</>
)}
{ metric.metricType === 'table' && (
<div className="p-3">
<CustomMetricTable data={data} />
</div>
)}
</NoContent>
</Loader>
</div>

View file

@ -16,7 +16,8 @@ export default class Table extends React.PureComponent {
rowProps,
rowClass = '',
small = false,
compare = false
compare = false,
maxHeight = 200,
} = this.props;
const { showAll } = this.state;
@ -30,7 +31,7 @@ export default class Table extends React.PureComponent {
<div key={ key } style={ { width } } className={ stl.header }>{ title }</div>)
}
</div>
<div className={ cn(stl.content, "thin-scrollbar") }>
<div className={ cn(stl.content, "thin-scrollbar") } style={{ maxHeight: maxHeight + 'px'}}>
{ rows.take(showAll ? 10 : (small ? 3 : 5)).map(row => (
<div className={ cn(rowClass, stl.row, { [stl.small]: small}) } key={ row.key }>
{ cols.map(({ cellClass = '', className = '', Component, key, toText = t => t, width }) => (
@ -42,7 +43,7 @@ export default class Table extends React.PureComponent {
)) }
</div>
)) }
</div>
{ rows.size > (small ? 3 : 5) && !showAll &&
<div className="w-full flex justify-center mt-3">
<Button
@ -56,7 +57,6 @@ export default class Table extends React.PureComponent {
</div>
}
</div>
</div>
);
}
}

View file

@ -24,6 +24,8 @@ interface Props {
function CustomMetricForm(props: Props) {
const { metric, loading } = 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 addSeries = () => {
props.addSeries();
@ -34,7 +36,17 @@ function CustomMetricForm(props: Props) {
}
const write = ({ target: { value, name } }) => props.editMetric({ [ name ]: value }, false);
const writeOption = (e, { value, name }) => props.editMetric({ [ name ]: value }, false);
const writeOption = (e, { value, name }) => {
props.editMetric({ [ name ]: value }, false);
if (name === 'metricType') {
if (value === 'timeseries') {
props.editMetric({ metricOf: timeseriesOptions[0].value }, false);
} else if (value === 'table') {
props.editMetric({ metricOf: tableOptions[0].value }, false);
}
}
};
const changeConditionTab = (e, { name, value }) => {
props.editMetric({[ 'viewType' ]: value });
@ -89,22 +101,44 @@ function CustomMetricForm(props: Props) {
value={ metric.metricType }
onChange={ writeOption }
/>
{metric.metricType === 'timeseries' && (
<>
<span className="mx-3">of</span>
<DropdownPlain
name="metricOf"
options={metricOfOptions}
options={timeseriesOptions}
value={ metric.metricOf }
onChange={ writeOption }
/>
</>
)}
{metric.metricType === 'table' && (
<>
<span className="mx-3">of</span>
<DropdownPlain
name="metricOf"
options={tableOptions}
value={ metric.metricOf }
onChange={ writeOption }
/>
</>
)}
{metric.metricType === 'table' && (
<>
<span className="mx-3">showing</span>
<DropdownPlain
name="viewType"
options={[
{ value: 'sessionCount', text: 'Session Count' },
]}
value={ metric.metricType }
value={ metric.viewType }
onChange={ writeOption }
/>
</>
)}
</div>
{/* <div className="flex items-center">
<span className="bg-white p-1 px-2 border rounded" style={{ height: '30px'}}>Timeseries</span>

View file

@ -4,6 +4,8 @@
border-radius: 3px;
color: $gray-darkest;
font-weight: 500;
background-color: white;
border: solid thin $gray-light;
&:hover {
background-color: $gray-light;
}

View file

@ -12,7 +12,7 @@ interface Props {
}
export default function DropdownPlain(props: Props) {
const { name = "sort", value, options, icon = "chevron-down", direction = "left" } = props;
const { name = "sort", value, options, icon = "chevron-down", direction = "right" } = props;
return (
<div>
<Dropdown
@ -22,7 +22,7 @@ export default function DropdownPlain(props: Props) {
direction={direction}
options={ options }
onChange={ props.onChange }
floating
// floating
scrolling
selectOnBlur={ false }
// defaultValue={ value }

View file

@ -15,7 +15,7 @@ class SegmentSelection extends React.Component {
<div className={ cn(styles.wrapper, {
[styles.primary] : primary,
[styles.small] : size === 'small' || small,
[styles.extraSmall] : extraSmall,
[styles.extraSmall] : size === 'extraSmall' || extraSmall,
}, className) }
>
{ list.map(item => (
@ -27,7 +27,7 @@ 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="20" marginRight="10" /> }
{ item.icon && <Icon name={ item.icon } size={size === "extraSmall" ? 12 : 20} marginRight={ item.name ? "10" : "" } /> }
<div>{ item.name }</div>
</div>
}

View file

@ -12,13 +12,13 @@
padding: 10px;
flex: 1;
text-align: center;
border-right: solid thin $teal;
cursor: pointer;
background-color: $gray-lightest;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
border-right: solid thin $gray-light;
& span svg {
fill: $gray-medium;
@ -53,6 +53,7 @@
& .item {
color: $teal;
background-color: white;
border-right: solid thin $teal;
&[data-active=true] {
background-color: $teal;
color: white;
@ -65,6 +66,6 @@
}
.extraSmall .item {
padding: 0 4px;
padding: 6px !important;
font-size: 12px;
}

View file

@ -61,7 +61,7 @@ export const metricTypes = [
export const metricOf = [
{ text: 'Session Count', value: 'sessionCount', key: 'timeseries' },
{ text: 'Users', value: 'users', key: 'table' },
{ text: 'Users', value: 'USERID', key: 'table' },
{ text: 'Rage Click', value: 'rageClick', key: 'table' },
{ text: 'Dead Click', value: 'deadClick', key: 'table' },
{ text: 'Browser', value: 'browser', key: 'table' },

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-graph-up-arrow" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M0 0h1v15h15v1H0V0Zm10 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V4.9l-3.613 4.417a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61L13.445 4H10.5a.5.5 0 0 1-.5-.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 402 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hash" viewBox="0 0 16 16">
<path d="M8.39 12.648a1.32 1.32 0 0 0-.015.18c0 .305.21.508.5.508.266 0 .492-.172.555-.477l.554-2.703h1.204c.421 0 .617-.234.617-.547 0-.312-.188-.53-.617-.53h-.985l.516-2.524h1.265c.43 0 .618-.227.618-.547 0-.313-.188-.524-.618-.524h-1.046l.476-2.304a1.06 1.06 0 0 0 .016-.164.51.51 0 0 0-.516-.516.54.54 0 0 0-.539.43l-.523 2.554H7.617l.477-2.304c.008-.04.015-.118.015-.164a.512.512 0 0 0-.523-.516.539.539 0 0 0-.531.43L6.53 5.484H5.414c-.43 0-.617.22-.617.532 0 .312.187.539.617.539h.906l-.515 2.523H4.609c-.421 0-.609.219-.609.531 0 .313.188.547.61.547h.976l-.516 2.492c-.008.04-.015.125-.015.18 0 .305.21.508.5.508.265 0 .492-.172.554-.477l.555-2.703h2.242l-.515 2.492zm-1-6.109h2.266l-.515 2.563H6.859l.532-2.563z"/>
</svg>

After

Width:  |  Height:  |  Size: 855 B

View file

@ -27,9 +27,9 @@ export const FilterSeries = Record({
export default Record({
metricId: undefined,
name: 'Series',
metricType: 'timeseries',
metricOf: 'sessionCount',
viewType: 'sessionCount',
metricType: 'table',
metricOf: 'USERID',
viewType: 'lineChart',
series: List(),
isPublic: true,
startDate: '',