feat(ui) - funnels - issues

This commit is contained in:
Shekar Siri 2022-05-11 16:13:01 +02:00
parent 34425b8b02
commit bec68eb375
9 changed files with 116 additions and 4 deletions

View file

@ -0,0 +1,36 @@
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { NoContent, Loader } from 'UI';
import FunnelIssuesSort from '../FunnelIssuesSort';
function FunnelIssues(props) {
const { funnelStore } = useStore();
const funnel = useObserver(() => funnelStore.instance);
const issues = useObserver(() => funnelStore.issues);
const loading = useObserver(() => funnelStore.isLoadingIssues);
useEffect(() => {
funnelStore.fetchIssues(funnel?.funnelId);
}, []);
return (
<div className="my-8">
<div className="flex justify-between">
<h1 className="font-medium text-2xl">Most significant issues <span className="font-normal">identified in this funnel</span></h1>
<FunnelIssuesSort />
</div>
<Loader loading={loading}>
<NoContent
show={issues.length === 0}
title="No issues found."
animatedIcon="empty-state"
>
Issues
</NoContent>
</Loader>
</div>
);
}
export default FunnelIssues;

View file

@ -0,0 +1,23 @@
import React from 'react';
import Select from 'Shared/Select';
const sortOptions = [
{ value: 'afectedUsers-desc', label: 'Affected Users (High)' },
{ value: 'afectedUsers-asc', label: 'Affected Users (Low)' },
{ value: 'conversionImpact-desc', label: 'Conversion Impact (High)' },
{ value: 'conversionImpact-asc', label: 'Conversion Impact (Low)' },
{ value: 'lostConversions-desc', label: 'Lost Conversions (High)' },
{ value: 'lostConversions-asc', label: 'Lost Conversions (Low)' },
]
function FunnelIssuesSort(props) {
return (
<div>
<Select
plain
options={sortOptions}
/>
</div>
);
}
export default FunnelIssuesSort;

View file

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

View file

@ -7,6 +7,7 @@ import { Icon, BackLink, Loader } from 'UI';
import { useObserver } from 'mobx-react-lite';
import { withSiteId } from 'App/routes';
import WidgetName from '../WidgetName';
import FunnelIssues from '../FunnelIssues/FunnelIssues';
interface Props {
history: any;
match: any
@ -17,6 +18,7 @@ function WidgetView(props: Props) {
const { metricStore } = useStore();
const widget = useObserver(() => metricStore.instance);
const loading = useObserver(() => metricStore.isLoading);
const isFunnel = widget.metricType === 'funnel';
const [expanded, setExpanded] = useState(!metricId || metricId === 'create');
React.useEffect(() => {
@ -63,7 +65,8 @@ function WidgetView(props: Props) {
</div>
<WidgetPreview className="mt-8" />
<WidgetSessions className="mt-8" />
{ !isFunnel && <WidgetSessions className="mt-8" /> }
{ isFunnel && <FunnelIssues /> }
</div>
</Loader>
));

View file

@ -25,6 +25,7 @@ export default function({ plain = false, options, isSearchable = false, defaultV
}
if (plain) {
obj['border'] = '1px solid transparent'
obj['backgroundColor'] = 'transparent'
}
return obj;
},

View file

@ -1,6 +1,7 @@
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
import { funnelService } from "App/services"
import Funnel, { IFunnel } from "./types/funnel";
import FunnelIssue from './types/funnelIssue';
import Period, { LAST_7_DAYS } from 'Types/app/period';
export default class FunnelStore {
@ -13,6 +14,9 @@ export default class FunnelStore {
page: number = 1
pageSize: number = 10
issues: any[] = []
isLoadingIssues: boolean = false
constructor() {
makeAutoObservable(this, {
@ -96,4 +100,20 @@ export default class FunnelStore {
)
})
}
fetchIssues(funnelId?: string): Promise<any> {
this.isLoadingIssues = true
return new Promise((resolve, reject) => {
funnelService.fetchIssues(funnelId, this.period)
.then(response => {
this.issues = response.map(i => new FunnelIssue().fromJSON(i))
resolve(this.issues)
}).catch(error => {
reject(error)
}
).finally(() => {
this.isLoadingIssues = false
})
})
}
}

View file

@ -0,0 +1,27 @@
export default class FunnelIssue {
issueId: string = ''
title: string = ''
type: string = ''
affectedSessions: number = 0
affectedUsers: number = 0
unaffectedSessions: number = 0
contextString: string = ''
conversionImpact: number = 0
lostConversions: number = 0
constructor() {
}
fromJSON(json: any) {
this.issueId = json.issueId
this.title = json.title
this.type = 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
return this
}
}

View file

@ -9,7 +9,7 @@ export interface IFunnelService {
delete(funnelId: string): Promise<any>
fetchInsights(funnelId: string, payload: any): Promise<any>
fetchIssues(funnelId: string, payload: any): Promise<any>
fetchIssues(funnelId?: string, payload?: any): Promise<any>
fetchIssue(funnelId: string, issueId: string): Promise<any>
}
@ -50,8 +50,9 @@ export default class FunnelService implements IFunnelService {
.then(response => response.json())
}
fetchIssues(funnelId: string, payload: any): Promise<any> {
return this.client.post(`/funnels/${funnelId}/issues`, payload)
fetchIssues(funnelId?: string, payload?: any): Promise<any> {
const path = funnelId ? `/funnels/${funnelId}/issues` : '/funnels/issues';
return this.client.post(path, payload)
.then(response => response.json())
}