feat(ui) - funnels - issues list

This commit is contained in:
Shekar Siri 2022-05-12 15:15:56 +02:00
parent 8584cf74cb
commit 6a1e72e1d5
9 changed files with 190 additions and 11 deletions

View file

@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import { NoContent, Loader } from 'UI';
import FunnelIssuesDropdown from '../FunnelIssuesDropdown';
import FunnelIssuesSort from '../FunnelIssuesSort';
import FunnelIssuesList from './components/FunnelIssuesList';
function FunnelIssues(props) {
const { funnelStore } = useStore();
@ -12,7 +13,7 @@ function FunnelIssues(props) {
const loading = useObserver(() => funnelStore.isLoadingIssues);
useEffect(() => {
funnelStore.fetchIssues(funnel?.funnelId);
// funnelStore.fetchIssues(funnel?.funnelId);
}, []);
return (
@ -32,7 +33,7 @@ function FunnelIssues(props) {
title="No issues found."
animatedIcon="empty-state"
>
Issues
<FunnelIssuesList />
</NoContent>
</Loader>
</div>

View file

@ -0,0 +1,53 @@
import React from 'react';
import { Popup } from 'UI';
const MIN_WIDTH = '20px';
interface Props {
issue: any
}
function FunnelIssueGraph(props: Props) {
const { issue } = props;
return (
<div className="flex rounded-sm" style={{ width: '600px' }}>
<Popup
trigger={
<div style={{ width: issue.unaffectedSessionsPer + '%', minWidth: MIN_WIDTH }} className="relative">
<div className="w-full relative rounded-tl-sm rounded-bl-sm" style={{ height: '18px', backgroundColor: 'rgba(217, 219, 238, 0.7)' }} />
<div className="absolute ml-2 font-bold top-0 bottom-0 text-sm">{issue.unaffectedSessions}</div>
</div>
}
content={ `Unaffected sessions` }
size="tiny"
inverted
position="top center"
/>
<Popup
trigger={
<div style={{ width: issue.affectedSessionsPer + '%', minWidth: MIN_WIDTH}} className="border-l relative">
<div className="w-full relative" style={{ height: '18px', backgroundColor: 'rgba(238, 238, 238, 0.7)' }} />
<div className="absolute ml-2 font-bold top-0 bottom-0 text-sm">{issue.affectedSessions}</div>
{/* <div className="absolute left-0 ml-1 text-xs">{issue.affectedSessionsPer}</div> */}
</div>
}
content={ `Affected sessions` }
size="tiny"
inverted
position="top center"
/>
<Popup
trigger={
<div style={{ width: issue.lostConversionsPer + '%', minWidth: MIN_WIDTH}} className="border-l relative">
<div className="w-full relative rounded-tr-sm rounded-br-sm" style={{ height: '18px', backgroundColor: 'rgba(204, 0, 0, 0.26)' }} />
<div className="absolute ml-2 font-bold top-0 bottom-0 text-sm color-red">{issue.lostConversions}</div>
</div>
}
content={ `Conversion lost` }
size="tiny"
inverted
position="top center"
/>
</div>
);
}
export default FunnelIssueGraph;

View file

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

View file

@ -5,12 +5,14 @@ import FunnelIssuesListItem from '../FunnelIssuesListItem';
function FunnelIssuesList(props) {
const { funnelStore } = useStore();
const issuesFilter = useObserver(() => funnelStore.issuesFilter.map((issue: any) => issue.value));
const issues = useObserver(() => funnelStore.issues);
const filteredIssues = useObserver(() => issuesFilter.length > 0 ? issues.filter((issue: any) => issuesFilter.includes(issue.type)) : issues);
return (
<div>
{issues.map((issue, index) => (
<div key={index}>
{filteredIssues.map((issue, index) => (
<div key={index} className="mb-4">
<FunnelIssuesListItem issue={issue} />
</div>
))}

View file

@ -1,14 +1,85 @@
import React from 'react';
import cn from 'classnames';
import { Icon, TextEllipsis } from 'UI';
import FunnelIssueGraph from '../FunnelIssueGraph';
interface Props {
issue: any;
inDetails?: boolean;
}
function FunnelIssuesListItem(props) {
const { issue, inDetails = false } = props;
const onClick = () => {}
return (
<div>
list item
</div>
<div className={cn('flex flex-col bg-white w-full rounded border relative hover:bg-active-blue', { 'cursor-pointer bg-hover' : !inDetails })} onClick={!inDetails ? onClick : () => null}>
{/* {inDetails && (
<BackLink onClick={onBack} className="absolute" style={{ left: '-50px', top: '8px' }} />
)} */}
<div className="flex items-center px-6 py-4 relative">
<div className="mr-3">
<div
className="flex items-center justify-center flex-shrink-0 mr-3 relative"
>
<Icon name={issue.icon.icon} style={{ fill: issue.icon.color }} size="24" className="z-10 inset-0" />
</div>
</div>
{inDetails && (
<div className="flex-1 overflow-hidden">
<div className="text-lg font-medium mb-2 capitalize">{issue.title}</div>
<div className="text-xl whitespace-nowrap">
<TextEllipsis text={issue.contextString} />
</div>
</div>
)}
{!inDetails && (
<div className="flex-1 overflow-hidden">
<div className="text-xl mb-2 capitalize">{issue.title}</div>
<div className="text-sm color-gray-medium whitespace-nowrap leading-none">
<TextEllipsis text={issue.contextString} />
</div>
</div>
)}
<div className="text-center text-sm ml-10 flex-shrink-0">
<div className="text-xl mb-2">{issue.affectedUsers}</div>
<div className="color-gray-medium leading-none">Affected Users</div>
</div>
<div className="text-center text-sm ml-10 flex-shrink-0">
<div className="text-xl mb-2 color-red">{issue.conversionImpact}<span className="text-sm ml-1">%</span></div>
<div className="color-gray-medium leading-none">Conversion Impact</div>
</div>
<div className="text-center text-sm ml-10 flex-shrink-0">
<div className="text-xl mb-2">{issue.lostConversions}</div>
<div className="color-gray-medium leading-none">Lost Conversions</div>
</div>
</div>
{inDetails && (
<div className="flex items-center px-6 py-4 justify-between border-t">
<FunnelIssueGraph issue={issue} />
<div className="flex items-center">
<Info label="Unaffected sessions" color="rgba(217, 219, 238, 0.7)" />
<Info label="Affected sessions" color="rgba(238, 238, 238, 0.7)" />
<Info label="Conversion Lost" color="rgba(204, 0, 0, 0.26)" />
</div>
</div>
)}
</div>
);
}
export default FunnelIssuesListItem;
export default FunnelIssuesListItem;
const Info = ({ label = '', color = 'red'}) => {
return (
<div className="flex items-center ml-4">
<div className="flex text-sm items-center color-gray-medium">
<div className={ cn("w-2 h-2 rounded-full mr-2") } style={{ backgroundColor: color }} />
<div>{ label }</div>
</div>
</div>
)
}

View file

@ -41,6 +41,7 @@ export default function({ styles= {}, alignRight = false, plain = false, options
position: 'absolute',
minWidth: 'fit-content',
overflow: 'hidden',
zIndex: 100,
...(alignRight && { right: 0 })
}),
menuList: (provided, state) => ({

View file

@ -27,6 +27,7 @@ export default class FunnelStore {
saveFunnel: action,
deleteFunnel: action
})
this.issues = sampleIssues.map(i => new FunnelIssue().fromJSON(i));
}
updateKey(key: string, value: any) {
@ -117,4 +118,7 @@ export default class FunnelStore {
})
})
}
}
}
const sampleIssues = [{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/assist-story-4.svg","issueId":"9157d556854f17cc25df3510bf7a980fd4d"},{"type":"click_rage","title":"Click Rage","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info 0:40 78:02 Pause Back Skip to Issue 1x Skip Inactivity Network Fetch 6 Redux 2 Consol","issueId":"91502c2c13f69c09a68503c61b0fd4461ca"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-slack.svg","issueId":"915b1b3a25c5f1127ec762235be7f896c3a"},{"type":"dead_click","title":"Dead Click","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"py-1 px-2 bg-white border border-gray-light rounded w-16","issueId":"9159e30220bb6a6a31afcaa1979a0c7d69c"},{"type":"dead_click","title":"Dead Click","affectedSessions":4,"unaffectedSessions":56,"lostConversions":2,"affectedUsers":2,"conversionImpact":61,"contextString":"OpenReplay App New Project SESSIONS ASSIST ERRORS DASHBOARDS Billing Details Announcements There are","issueId":"91515f9118ed803291f87133e2cb49a16ea"},{"type":"dead_click","title":"Dead Click","affectedSessions":2,"unaffectedSessions":58,"lostConversions":0,"affectedUsers":1,"conversionImpact":20,"contextString":"Type to search","issueId":"915832d68d21f03f83af1bfc758a1dda50b"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-linkedin.svg","issueId":"91506bb929c2cb3679f8b01c228d8a0b5c8"},{"type":"dead_click","title":"Dead Click","affectedSessions":3,"unaffectedSessions":57,"lostConversions":0,"affectedUsers":2,"conversionImpact":31,"contextString":"Search sessions using any captured event (click, input, page, error...)","issueId":"9157be39a537e81243a2ff44ad74867941f"},{"type":"cpu","title":"High CPU","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"","issueId":"915a68d6bb4448b5822836dbc797bafadf9"},{"type":"dead_click","title":"Dead Click","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info Report Issue This session's issues 10:35 83:19 Play Back Skip to Issue 4x Skip Inacti","issueId":"915b43e81f8da042f70ea47bd9ad14a3bb8"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-git.svg","issueId":"915f7f277daa7a695d5bf9e233c43af7f02"},{"type":"click_rage","title":"Click Rage","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info 0:35 78:02 Pause Back Skip to Issue 1x Skip Inactivity Network Fetch 6 Redux 2 Consol","issueId":"915788d9976c9f80c6f599e3e5816f2c7be"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-twitter.svg","issueId":"915ab993784a0432c39b4a4e9248dfe6acd"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/logo-open-replay.svg","issueId":"915ac8719c95392adb8b79d2d5eae1063b9"}]

View file

@ -192,7 +192,7 @@ export default class MetricStore implements IMetricStore {
}
const sampleJson = {
// metricId: 1,
metricId: 1,
name: "Funnel Sample",
metricType: 'funnel',
series: [

View file

@ -8,6 +8,10 @@ export default class FunnelIssue {
contextString: string = ''
conversionImpact: number = 0
lostConversions: number = 0
lostConversionsPer: number = 0
affectedSessionsPer: number = 0
unaffectedSessionsPer: number = 0
icon: any = {}
constructor() {
}
@ -16,12 +20,54 @@ export default class FunnelIssue {
this.issueId = json.issueId
this.title = json.title
this.type = json.type
this.icon = getIconDetails(json.type)
this.affectedSessions = json.affectedSessions
this.affectedUsers = json.affectedUsers
this.unaffectedSessions = json.unaffectedSessions
this.contextString = json.contextString
this.conversionImpact = json.conversionImpact
this.lostConversions = json.lostConversions
const total = json.lostConversions + json.affectedSessions + json.unaffectedSessions;
this.lostConversionsPer = json.lostConversions * 100 / total;
this.affectedSessionsPer = json.affectedSessions * 100 / total;
this.unaffectedSessionsPer = json.unaffectedSessions * 100 / total;
return this
}
}
}
const getIconDetails = (type) => {
switch(type) {
case 'click_rage':
return { icon: 'funnel/emoji-angry-fill', color: '#CC0000' };
case 'dead_click':
return { icon: 'funnel/emoji-dizzy-fill', color: '#9C001F' };
case 'excessive_scrolling':
return { icon: 'funnel/mouse', color: '#D3545F' };
case 'bad_request':
return { icon: 'funnel/patch-exclamation-fill', color: '#D70072' };
case 'missing_resource':
return { icon: 'funnel/image-fill', color: '#B89C50' };
case 'memory':
return { icon: 'funnel/cpu-fill', color: '#8A5A83' };
case 'cpu':
return { icon: 'funnel/hdd-fill', color: '#8A5A83' };
case 'slow_resource':
return { icon: 'funnel/hourglass-top', color: '#8B006D' };
case 'slow_page_load':
return { icon: 'funnel/hourglass-top', color: '#8B006D' };
case 'custom_event_error':
case 'custom':
return { icon: 'funnel/exclamation-circle-fill', color: '#BF6C00' };
case 'crash':
return { icon: 'funnel/file-x', color: '#BF2D00' };
case 'js_exception':
return { icon: 'funnel/exclamation-circle', color: '#BF2D00' };
}
return {
icon: 'info',
color: 'red'
}
}