feat(ui) - funnels - graph

This commit is contained in:
Shekar Siri 2022-05-10 17:17:15 +02:00
parent 89db14bdbf
commit 3bb5d9fabd
12 changed files with 198 additions and 2 deletions

View file

@ -11,6 +11,7 @@ import WidgetPredefinedChart from '../WidgetPredefinedChart';
import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart';
import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper';
import { debounce } from 'App/utils';
import FunnelWidget from 'App/components/Funnels/FunnelWidget';
interface Props {
metric: any;
isWidget?: boolean
@ -81,6 +82,10 @@ function WidgetChart(props: Props) {
const renderChart = () => {
const { metricType, viewType } = metric;
if (metricType === 'funnel') {
return <FunnelWidget metric={metric} />
}
if (metricType === 'predefined') {
if (isOverviewWidget) {
return <CustomMetricOverviewChart data={data} />

View file

@ -2,7 +2,7 @@ import React from 'react';
import cn from 'classnames';
import WidgetWrapper from '../WidgetWrapper';
import { useStore } from 'App/mstore';
import { Loader, NoContent, SegmentSelection, Icon } from 'UI';
import { SegmentSelection } from 'UI';
import DateRange from 'Shared/DateRange';
import { useObserver } from 'mobx-react-lite';

View file

@ -0,0 +1,51 @@
import React from 'react';
import FunnelStepText from './FunnelStepText';
import { Icon } from 'UI';
interface Props {
completed: number;
dropped: number;
filter: any;
}``
function FunnelBar(props: Props) {
const { completed, dropped, filter } = props;
return (
<div className="w-full mb-4">
<FunnelStepText filter={filter} />
<div style={{
height: '25px',
// marginBottom: '10px',
width: '100%',
backgroundColor: '#f5f5f5',
position: 'relative',
borderRadius: '3px',
overflow: 'hidden',
}}>
<div style={{
width: `${completed * 100 / (completed + dropped)}px`,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
// height: '10px',
backgroundColor: '#00b5ad',
}} />
</div>
<div className="flex justify-between py-2">
<div className="flex items-center">
<Icon name="arrow-right-short" size="20" color="green" />
<span className="mx-1 font-medium">{13}</span>
<span>completed</span>
</div>
<div className="flex items-center">
<Icon name="caret-down-fill" color="red" size={18} />
<span className="font-medium mx-1 color-red">20</span>
<span>Dropped off</span>
</div>
</div>
</div>
);
}
export default FunnelBar;

View file

@ -0,0 +1,23 @@
import React from 'react';
interface Props {
filter: any;
}
function FunnelStepText(props: Props) {
const { filter } = props;
const total = filter.value.length;
return (
<div className="mb-2 color-gray-medium">
<span>{filter.label}</span>
<span className="mx-1">{filter.operator}</span>
{filter.value.map((value, index) => (
<>
<span key={index} className="font-medium color-gray-darkest">{value}</span>
{index < total - 1 && <span className="mx-1 color-gray-medium">or</span>}
</>
))}
</div>
);
}
export default FunnelStepText;

View file

@ -0,0 +1,18 @@
.step {
/* display: flex; */
position: relative;
&:before {
content: '';
border-left: 2px solid $gray-lightest;
position: absolute;
top: 16px;
bottom: 9px;
left: 10px;
/* width: 1px; */
height: 100%;
z-index: 0;
}
&:last-child:before {
display: none;
}
}

View file

@ -0,0 +1,52 @@
import React from 'react';
import Funnel from 'App/mstore/types/funnel';
import Widget from 'App/mstore/types/widget';
import Funnelbar from './FunnelBar'
import cn from 'classnames';
import stl from './FunnelWidget.css';
import { Icon } from 'UI';
interface Props {
metric: Widget;
}
function FunnelWidget(props: Props) {
const { metric } = props;
return (
<>
<div className="w-full">
{metric.series[0].filter.filters.filter(f => f.isEvent).map((filter, index) => (
<div className={cn("flex items-start mb-4", stl.step)}>
<div className="z-10 w-6 h-6 border mr-4 text-sm rounded-full bg-gray-lightest flex items-center justify-center leading-3">
{index + 1}
</div>
<Funnelbar key={index} completed={90} dropped={10} filter={filter}/>
<div className="self-end flex items-center justify-center ml-4" style={{ marginBottom: '50px'}}>
<Icon name="eye-slash" size="22" />
</div>
</div>
))}
</div>
<div className="flex items-center">
<div className="flex items-center">
<span className="text-xl mr-2">Total conversions</span>
<div className="rounded px-2 py-1 bg-tealx-lightest color-tealx">
<span className="text-xl mr-2 font-medium">20</span>
<span className="text-sm">(12%)</span>
</div>
</div>
<div className="mx-3" />
<div className="flex items-center">
<span className="text-xl mr-2">Lost conversions</span>
<div className="rounded px-2 py-1 bg-red-lightest color-red">
<span className="text-xl mr-2 font-medium">20</span>
<span className="text-sm">(12%)</span>
</div>
</div>
</div>
</>
);
}
export default FunnelWidget;

View file

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

View file

@ -62,6 +62,8 @@ export const customOperators = [
export const metricTypes = [
{ text: 'Timeseries', value: 'timeseries' },
{ text: 'Table', value: 'table' },
{ text: 'Funnel', value: 'funnel' },
{ text: 'Error', value: 'error' },
];
export const tableColumnName = {

View file

@ -90,7 +90,10 @@ export default class MetricStore implements IMetricStore {
// State Actions
init(metric?: IWidget|null) {
this.instance.update(metric || new Widget())
const _metric = new Widget().fromJson(sampleJson)
this.instance.update(metric || _metric)
// this.instance.update(metric || new Widget())
}
updateKey(key: string, value: any) {
@ -186,4 +189,35 @@ export default class MetricStore implements IMetricStore {
this.isSaving = false
})
}
}
const sampleJson = {
metricId: 1,
name: "Funnel Sample",
metricType: 'funnel',
series: [
{
name: 'Series 1',
filter: {
filters: [
{
type: 'LOCATION',
operator: 'is',
value: ['/sessions', '/errors', '/users'],
percent: 100,
completed: 60,
dropped: 0,
},
{
type: 'LOCATION',
operator: 'is',
value: ['/sessions', '/errors', '/users'],
percent: 80,
completed: 40,
dropped: 20,
}
]
}
}
],
}

View file

@ -27,6 +27,12 @@ export default class FilterItem {
merge: action
})
if (Array.isArray(data.filters)) {
data.filters = data.filters.map(function (i) {
return new FilterItem(i);
});
}
this.merge(data)
}

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 273 B

View file

@ -27,6 +27,7 @@ module.exports = {
"green-dark": "#2C9848",
red: "#cc0000",
red2: "#F5A623",
"red-lightest": 'rgba(204, 0, 0, 0.1)',
blue: "#366CD9",
blue2: "#0076FF",
"active-blue": "#F6F7FF",