From 6a1e72e1d5ef4fd0fb09f7c085e17b504d10913f Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 12 May 2022 15:15:56 +0200 Subject: [PATCH] feat(ui) - funnels - issues list --- .../components/FunnelIssues/FunnelIssues.tsx | 5 +- .../FunnelIssueGraph/FunnelIssueGraph.tsx | 53 +++++++++++++ .../components/FunnelIssueGraph/index.ts | 1 + .../FunnelIssuesList/FunnelIssuesList.tsx | 6 +- .../FunnelIssuesListItem.tsx | 79 ++++++++++++++++++- .../app/components/shared/Select/Select.tsx | 1 + frontend/app/mstore/funnelStore.ts | 6 +- frontend/app/mstore/metricStore.ts | 2 +- frontend/app/mstore/types/funnelIssue.ts | 48 ++++++++++- 9 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx index 7391cf2b7..e779d4f49 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx @@ -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 + diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx new file mode 100644 index 000000000..4a69dc280 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx @@ -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 ( +
+ +
+
{issue.unaffectedSessions}
+
+ } + content={ `Unaffected sessions` } + size="tiny" + inverted + position="top center" + /> + +
+
{issue.affectedSessions}
+ {/*
{issue.affectedSessionsPer}
*/} +
+ } + content={ `Affected sessions` } + size="tiny" + inverted + position="top center" + /> + +
+
{issue.lostConversions}
+
+ } + content={ `Conversion lost` } + size="tiny" + inverted + position="top center" + /> +
+ ); +} + +export default FunnelIssueGraph; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts new file mode 100644 index 000000000..11aed8599 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssueGraph'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx index 8a6f05c6d..839ca35dd 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx @@ -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 (
- {issues.map((issue, index) => ( -
+ {filteredIssues.map((issue, index) => ( +
))} diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx index 6e5bb65c0..4adfbeb31 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx @@ -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 ( -
- list item -
+
null}> + {/* {inDetails && ( + + )} */} +
+
+
+ +
+
+ + {inDetails && ( +
+
{issue.title}
+
+ +
+
+ )} + + {!inDetails && ( +
+
{issue.title}
+
+ +
+
+ )} + +
+
{issue.affectedUsers}
+
Affected Users
+
+ +
+
{issue.conversionImpact}%
+
Conversion Impact
+
+ +
+
{issue.lostConversions}
+
Lost Conversions
+
+
+ {inDetails && ( +
+ +
+ + + +
+
+ )} +
); } -export default FunnelIssuesListItem; \ No newline at end of file +export default FunnelIssuesListItem; + +const Info = ({ label = '', color = 'red'}) => { + return ( +
+
+
+
{ label }
+
+
+ ) + } \ No newline at end of file diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx index 6cc11cbb4..f7e834561 100644 --- a/frontend/app/components/shared/Select/Select.tsx +++ b/frontend/app/components/shared/Select/Select.tsx @@ -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) => ({ diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index c339de92f..ad1020a1a 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -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 { }) }) } -} \ No newline at end of file +} + + +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"}] \ No newline at end of file diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index eb9147620..0f4163711 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -192,7 +192,7 @@ export default class MetricStore implements IMetricStore { } const sampleJson = { - // metricId: 1, + metricId: 1, name: "Funnel Sample", metricType: 'funnel', series: [ diff --git a/frontend/app/mstore/types/funnelIssue.ts b/frontend/app/mstore/types/funnelIssue.ts index c5bafbfaf..5f07ee123 100644 --- a/frontend/app/mstore/types/funnelIssue.ts +++ b/frontend/app/mstore/types/funnelIssue.ts @@ -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 } -} \ No newline at end of file +} + + +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' + } + } \ No newline at end of file