From 7874dcbe0bf5c067a2ce8d53f3bb85dae8242564 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 14 Jun 2022 12:47:43 +0200 Subject: [PATCH] feat(ui) - issues and errors widgets --- .../CustomMetricTableErrors.tsx | 15 +++++ .../CustomMetricTableErrors/index.ts | 1 + .../CustomMetricTableSessions.tsx | 8 ++- .../components/WidgetChart/WidgetChart.tsx | 11 +++- .../Funnels/FunnelWidget/FunnelBar.tsx | 17 +++--- .../Funnels/FunnelWidget/FunnelWidget.tsx | 6 +- .../SelectDateRange/SelectDateRange.tsx | 6 +- frontend/app/components/ui/Label/Label.js | 2 +- frontend/app/constants/filterOptions.js | 1 + frontend/app/mstore/dashboardStore.ts | 60 ++++++++++--------- frontend/app/mstore/types/funnel.ts | 13 +++- frontend/app/mstore/types/funnelStage.ts | 8 ++- frontend/app/mstore/types/widget.ts | 4 +- frontend/app/services/MetricService.ts | 4 +- frontend/app/types/filter/filterType.ts | 3 +- 15 files changed, 107 insertions(+), 52 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/index.ts diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx new file mode 100644 index 000000000..3806d55f5 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + + +interface Props { + +} +function CustomMetricTableErrors(props) { + return ( +
+ +
+ ); +} + +export default CustomMetricTableErrors; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/index.ts new file mode 100644 index 000000000..78590d267 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricTableErrors'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index 4bd4cb983..e04c5c1a3 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,14 +1,20 @@ import React from 'react'; +import SessionItem from 'Shared/SessionItem'; interface Props { data: any metric?: any isTemplate?: boolean; } + function CustomMetricTableSessions(props: Props) { + const { data = { sessions: [] }, metric = {}, isTemplate } = props; + console.log('data', data) return (
- + {data.sessions && data.sessions.map((session: any, index: any) => ( + + ))}
); } diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 62ab68a1d..83894d299 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -16,6 +16,7 @@ import useIsMounted from 'App/hooks/useIsMounted' import FunnelWidget from 'App/components/Funnels/FunnelWidget'; import ErrorsWidget from '../Errors/ErrorsWidget'; import SessionWidget from '../Sessions/SessionWidget'; +import CustomMetricTableSessions from '../../Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; interface Props { metric: any; isWidget?: boolean; @@ -86,7 +87,7 @@ function WidgetChart(props: Props) { }, [period, depsString]); const renderChart = () => { - const { metricType, viewType } = metric; + const { metricType, viewType, metricOf } = metric; if (metricType === 'sessions') { return @@ -129,6 +130,14 @@ function WidgetChart(props: Props) { } if (metricType === 'table') { + if (metricOf === 'SESSIONS') { + return + } if (viewType === 'table') { return
-
{completedPercentage}%
+
{filter.completedPercentage}%
@@ -41,8 +41,8 @@ function FunnelBar(props: Props) {
- {filter.dropDueToIssues} - Dropped off + {filter.droppedCount} + Dropped
@@ -53,8 +53,9 @@ export default FunnelBar; const calculatePercentage = (completed: number, dropped: number) => { const total = completed + dropped; - if (total === 0) { - return 0; - } - return Math.round((completed / total) * 100); + if (dropped === 0) return 100; + if (total === 0) return 0; + + return Math.round((completed / dropped) * 100); + } \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 0146a9c1f..4653a6687 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -35,15 +35,15 @@ function FunnelWidget(props: Props) { Lost conversions
{funnel.lostConversions} - (12%) + ({funnel.lostConversionsPercentage}%)
Total conversions
- 20 - (12%) + {funnel.totalConversions} + ({funnel.totalConversionsPercentage}%)
diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 8f43c2095..d349b9354 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -54,10 +54,10 @@ function SelectDateRange(props: Props) { setIsCustom(false)} > -
(
{ children }
diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index dbed5d32c..3a7345b61 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -80,6 +80,7 @@ export const metricOf = [ { text: 'Session Count', label: 'Session Count', value: 'sessionCount', type: 'timeseries' }, { text: 'Users', label: 'Users', value: FilterKey.USERID, type: 'table' }, { text: 'Sessions', label: 'Sessions', value: FilterKey.SESSIONS, type: 'table' }, + { text: 'JS Errors', label: 'JS Errors', value: FilterKey.ERRORS, type: 'table' }, { text: 'Issues', label: 'Issues', value: FilterKey.ISSUE, type: 'table' }, { text: 'Browsers', label: 'Browsers', value: FilterKey.USER_BROWSER, type: 'table' }, { text: 'Devices', label: 'Devices', value: FilterKey.USER_DEVICE, type: 'table' }, diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 1a0aa181a..3598fdc8a 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -3,10 +3,11 @@ import Dashboard, { IDashboard } from "./types/dashboard" import Widget, { IWidget } from "./types/widget"; import { dashboardService, metricService } from "App/services"; import { toast } from 'react-toastify'; -import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; +import Period, { LAST_24_HOURS, LAST_30_DAYS } from 'Types/app/period'; import { getChartFormatter } from 'Types/dashboard/helper'; import Filter, { IFilter } from "./types/filter"; import Funnel from "./types/funnel"; +import Session from "./types/session"; export interface IDashboardSotre { dashboards: IDashboard[] @@ -79,7 +80,7 @@ export default class DashboardStore implements IDashboardSotre { currentWidget: Widget = new Widget() widgetCategories: any[] = [] widgets: Widget[] = [] - period: Period = Period({ rangeName: LAST_24_HOURS }) + period: Period = Period({ rangeName: LAST_30_DAYS }) drillDownFilter: Filter = new Filter() startTimestamp: number = 0 endTimestamp: number = 0 @@ -434,9 +435,8 @@ export default class DashboardStore implements IDashboardSotre { fetchMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise { const period = this.period.toTimestamps() return new Promise((resolve, reject) => { - // this.isLoading = true return metricService.getMetricChartData(metric, { ...period, ...data, key: metric.predefinedKey }, isWidget) - .then(data => { + .then((data: any) => { if (metric.metricType === 'predefined' && metric.viewType === 'overview') { const _data = { ...data, chart: getChartFormatter(this.period)(data.chart) } metric.setData(_data) @@ -450,35 +450,41 @@ export default class DashboardStore implements IDashboardSotre { const _data = { ...data, } - if (data.hasOwnProperty('chart')) { - _data['chart'] = getChartFormatter(this.period)(data.chart) - _data['namesMap'] = data.chart - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []) + + // TODO refactor to widget class + if (metric.metricOf === 'SESSIONS') { + _data['sessions'] = data.sessions.map((s: any) => new Session().fromJson(s)) } else { - _data['chart'] = getChartFormatter(this.period)(Array.isArray(data) ? data : []); - _data['namesMap'] = Array.isArray(data) ? data.map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []) : [] + if (data.hasOwnProperty('chart')) { + _data['chart'] = getChartFormatter(this.period)(data.chart) + _data['namesMap'] = data.chart + .map(i => Object.keys(i)) + .flat() + .filter(i => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []) + } else { + _data['chart'] = getChartFormatter(this.period)(Array.isArray(data) ? data : []); + _data['namesMap'] = Array.isArray(data) ? data.map(i => Object.keys(i)) + .flat() + .filter(i => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []) : [] + } } metric.setData(_data) resolve(_data); } - }).catch((err) => { + }).catch((err: any) => { reject(err) }) }) diff --git a/frontend/app/mstore/types/funnel.ts b/frontend/app/mstore/types/funnel.ts index 0085816c8..253fdb076 100644 --- a/frontend/app/mstore/types/funnel.ts +++ b/frontend/app/mstore/types/funnel.ts @@ -2,8 +2,11 @@ import FunnelStage from './funnelStage' export interface IFunnel { affectedUsers: number; + totalConversions: number; + totalConversionsPercentage: number; conversionImpact: number lostConversions: number + lostConversionsPercentage: number isPublic: boolean fromJSON: (json: any) => void toJSON: () => any @@ -12,8 +15,11 @@ export interface IFunnel { export default class Funnel implements IFunnel { affectedUsers: number = 0 + totalConversions: number = 0 conversionImpact: number = 0 lostConversions: number = 0 + lostConversionsPercentage: number = 0 + totalConversionsPercentage: number = 0 isPublic: boolean = false stages: FunnelStage[] = [] @@ -24,9 +30,12 @@ export default class Funnel implements IFunnel { if (json.stages.length >= 1) { const firstStage = json.stages[0] const lastStage = json.stages[json.stages.length - 1] - this.lostConversions = json.totalDropDueToIssues + this.lostConversions = firstStage.sessionsCount - lastStage.sessionsCount + this.lostConversionsPercentage = this.lostConversions / firstStage.sessionsCount * 100 + this.totalConversions = lastStage.sessionsCount + this.totalConversionsPercentage = this.totalConversions / firstStage.sessionsCount * 100 this.conversionImpact = this.lostConversions ? Math.round((this.lostConversions / firstStage.sessionsCount) * 100) : 0; - this.stages = json.stages ? json.stages.map((stage: any) => new FunnelStage().fromJSON(stage)) : [] + this.stages = json.stages ? json.stages.map((stage: any, index: number) => new FunnelStage().fromJSON(stage, firstStage.sessionsCount, index > 0 ? json.stages[index - 1].sessionsCount : stage.sessionsCount)) : [] this.affectedUsers = firstStage.usersCount ? firstStage.usersCount - lastStage.usersCount : 0; } diff --git a/frontend/app/mstore/types/funnelStage.ts b/frontend/app/mstore/types/funnelStage.ts index d4b975518..40d79a344 100644 --- a/frontend/app/mstore/types/funnelStage.ts +++ b/frontend/app/mstore/types/funnelStage.ts @@ -9,7 +9,9 @@ export default class FunnelStage { type: string = ''; value: string[] = []; label: string = ''; - isActive: boolean = false; + isActive: boolean = true; + completedPercentage: number = 0; + droppedCount: number = 0; constructor() { makeAutoObservable(this, { @@ -18,7 +20,7 @@ export default class FunnelStage { }) } - fromJSON(json: any) { + fromJSON(json: any, total: number = 0, previousSessionCount: number = 0) { this.dropDueToIssues = json.dropDueToIssues; this.dropPct = json.dropPct; this.operator = json.operator; @@ -27,6 +29,8 @@ export default class FunnelStage { this.value = json.value; this.type = json.type; this.label = filterLabelMap[json.type] || json.type; + this.completedPercentage = total ? Math.round((this.sessionsCount / total) * 100) : 0; + this.droppedCount = previousSessionCount - this.sessionsCount; return this; } diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index dac811619..b343b529f 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -230,9 +230,11 @@ export default class Widget implements IWidget { fetchIssue(funnelId: any, issueId: any, params: any): Promise { return new Promise((resolve, reject) => { metricService.fetchIssue(funnelId, issueId, params).then((response: any) => { + response = response[0] + console.log('response', response) resolve({ issue: new Funnelissue().fromJSON(response.issue), - sessions: response.sessions.map((s: any) => new Session().fromJson(s)), + sessions: response.sessions.sessions.map((s: any) => new Session().fromJson(s)), }) }).catch((error: any) => { reject(error) diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index e01d9291b..3bc4d4ef9 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -109,8 +109,8 @@ export default class MetricService implements IMetricService { .then((response: { data: any; }) => response.data || {}); } - fetchIssue(funnelId: string, issueId: string, params: any): Promise { - return this.client.post(`/funnels/${funnelId}/issues/${issueId}/sessions`, params) + fetchIssue(metricId: string, issueId: string, params: any): Promise { + return this.client.post(`/custom_metrics/${metricId}/issues/${issueId}/sessions`, params) .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || {}); } diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index f91f15dd5..4194b759b 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -92,5 +92,6 @@ export enum FilterKey { GRAPHQL_REQUEST_BODY = "GRAPHQL_REQUEST_BODY", GRAPHQL_RESPONSE_BODY = "GRAPHQL_RESPONSE_BODY", - SESSIONS = 'SESSIONS' + SESSIONS = 'SESSIONS', + ERRORS = 'ERRORS' } \ No newline at end of file