feat(ui) - funnels - graph
This commit is contained in:
parent
89db14bdbf
commit
3bb5d9fabd
12 changed files with 198 additions and 2 deletions
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
51
frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
Normal file
51
frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
1
frontend/app/components/Funnels/FunnelWidget/index.ts
Normal file
1
frontend/app/components/Funnels/FunnelWidget/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './FunnelWidget';
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
3
frontend/app/svg/icons/arrow-right-short.svg
Normal file
3
frontend/app/svg/icons/arrow-right-short.svg
Normal 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 |
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue