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