From f53ad1bbc4e61e44fd6994b2b4529f4e4bbe335b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 17 Jun 2022 15:38:16 +0200 Subject: [PATCH] feat(ui) - sessions - widget - pagination --- .../CustomMetricTableErrors.tsx | 44 ++++++++++-- .../CustomMetricTableSessions.tsx | 34 ++++----- .../DashboardWidgetGrid.tsx | 6 +- .../Errors/ErrorLabel/ErrorLabel.tsx | 23 ++++++ .../components/Errors/ErrorLabel/index.ts | 1 + .../Errors/ErrorListItem/ErrorListItem.tsx | 71 +++++++++++++++++-- .../components/Errors/ErrorName/ErrorName.tsx | 15 ++++ .../components/Errors/ErrorName/index.ts | 1 + .../components/WidgetChart/WidgetChart.tsx | 21 ++++-- .../components/WidgetView/WidgetView.tsx | 4 +- .../Errors/List/ListItem/ListItem.js | 3 +- .../Funnels/FunnelWidget/FunnelBar.tsx | 2 +- frontend/app/mstore/dashboardStore.ts | 8 ++- frontend/app/mstore/types/error.ts | 10 ++- frontend/app/mstore/types/funnel.ts | 4 +- frontend/app/mstore/types/widget.ts | 13 ++-- frontend/app/services/MetricService.ts | 2 +- 17 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/Errors/ErrorLabel/ErrorLabel.tsx create mode 100644 frontend/app/components/Dashboard/components/Errors/ErrorLabel/index.ts create mode 100644 frontend/app/components/Dashboard/components/Errors/ErrorName/ErrorName.tsx create mode 100644 frontend/app/components/Dashboard/components/Errors/ErrorName/index.ts diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index 3806d55f5..91b95a163 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -1,15 +1,49 @@ import React from 'react'; +import { Pagination } from 'UI'; +import ErrorListItem from '../../../components/Errors/ErrorListItem'; - +const PER_PAGE = 5; interface Props { - + metric: any; + isTemplate?: boolean; + isEdit?: boolean; } -function CustomMetricTableErrors(props) { +function CustomMetricTableErrors(props: Props) { + const { metric, isEdit = false } = props; + return (
- + {metric.data.errors && metric.data.errors.map((error: any, index: any) => ( + + ))} + + {isEdit && ( +
+ metric.updateKey('page', page)} + limit={metric.limit} + debounceRequest={500} + /> +
+ )} + + {!isEdit && ( + + )}
); } -export default CustomMetricTableErrors; \ No newline at end of file +export default CustomMetricTableErrors; + +const ViewMore = ({ total, limit }: any) => total > limit && ( +
+
+
+ All {total} errors +
+
+
+); \ 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 8e2a56004..0e5ff0587 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,51 +1,45 @@ import { useObserver } from 'mobx-react-lite'; import React from 'react'; import SessionItem from 'Shared/SessionItem'; -import { Pagination } from 'UI'; -import { useStore } from 'App/mstore'; +import { Pagination, NoContent } from 'UI'; -const PER_PAGE = 10; interface Props { - data: any - metric?: any + metric: any; isTemplate?: boolean; isEdit?: boolean; } function CustomMetricTableSessions(props: Props) { - const { data = { sessions: [], total: 0 }, isEdit = false } = props; - const currentPage = 1; - const { metricStore } = useStore(); - const metric: any = useObserver(() => metricStore.instance); + const { isEdit = false, metric } = props; - return ( -
- {data.sessions && data.sessions.map((session: any, index: any) => ( - + return useObserver(() => ( + + {metric.data.sessions && metric.data.sessions.map((session: any, index: any) => ( + ))} {isEdit && (
metric.updateKey('page', page)} - limit={PER_PAGE} + limit={metric.data.total} debounceRequest={500} />
)} {!isEdit && ( - + )} -
- ); + + )); } export default CustomMetricTableSessions; -const ViewMore = ({ total }: any) => total > PER_PAGE && ( +const ViewMore = ({ total, limit }: any) => total > limit && (
diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index b60fa2de2..cf80137dd 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -10,7 +10,7 @@ interface Props { onEditHandler: () => void; id?: string; } -function DashboardWidgetGrid(props) { +function DashboardWidgetGrid(props: Props) { const { dashboardId, siteId } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); @@ -31,12 +31,12 @@ function DashboardWidgetGrid(props) { } >
- {list && list.map((item, index) => ( + {list && list.map((item: any, index: any) => ( dashboard.swapWidgetPosition(dragIndex, hoverIndex)} + moveListItem={(dragIndex: any, hoverIndex: any) => dashboard.swapWidgetPosition(dragIndex, hoverIndex)} dashboardId={dashboardId} siteId={siteId} isWidget={true} diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorLabel/ErrorLabel.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorLabel/ErrorLabel.tsx new file mode 100644 index 000000000..1899ccbb9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/Errors/ErrorLabel/ErrorLabel.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import cn from "classnames"; + +interface Props { + className?: string; + topValue: string; + topValueSize?: string; + bottomValue: string; + topMuted?: boolean; + bottomMuted?: boolean; +} +function ErrorLabel({ className, topValue, topValueSize = 'text-base', bottomValue, topMuted = false, bottomMuted = false }: Props) { + return ( +
+
{ topValue }
+
{ bottomValue }
+
+ ) +} + +ErrorLabel.displayName = "ErrorLabel"; + +export default ErrorLabel; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorLabel/index.ts b/frontend/app/components/Dashboard/components/Errors/ErrorLabel/index.ts new file mode 100644 index 000000000..716e5986c --- /dev/null +++ b/frontend/app/components/Dashboard/components/Errors/ErrorLabel/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorLabel' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index 6f230b516..95ce3f021 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -1,14 +1,77 @@ import React from 'react'; +import cn from 'classnames'; +import moment from 'moment'; +import { error as errorRoute } from 'App/routes'; +import { IGNORED, RESOLVED } from 'Types/errorInfo'; +import { Link, Label } from 'UI'; +import ErrorName from '../ErrorName'; +import ErrorLabel from '../ErrorLabel'; +import { BarChart, Bar, YAxis, Tooltip, XAxis } from 'recharts'; +import { Styles } from '../../../Widgets/common'; +import { diffFromNowString } from 'App/date'; interface Props { error: any; + className?: string; } function ErrorListItem(props: Props) { + const { error, className = '' } = props; return ( -
- Errors List Item -
+
+
+
+ +
+ { error.message } +
+
+
+ + + + } /> + + + + + +
); } -export default ErrorListItem; \ No newline at end of file +export default ErrorListItem; + +const CustomTooltip = ({ active, payload, label }: any) => { + if (active) { + const p = payload[0].payload; + return ( +
+

{`${p.timestamp ? moment(p.timestamp).format('l') : ''}`}

+

Sessions: {p.count}

+
+ ); + } + + return null; + }; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorName/ErrorName.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorName/ErrorName.tsx new file mode 100644 index 000000000..646b5c68f --- /dev/null +++ b/frontend/app/components/Dashboard/components/Errors/ErrorName/ErrorName.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import cn from "classnames"; + +function ErrorText({ className, icon, name, message, bold, lineThrough = false }: any) { + return ( +
+ { name } + { message } +
+ ); +} + +ErrorText.displayName = "ErrorText"; + +export default ErrorText; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorName/index.ts b/frontend/app/components/Dashboard/components/Errors/ErrorName/index.ts new file mode 100644 index 000000000..548c10f38 --- /dev/null +++ b/frontend/app/components/Dashboard/components/Errors/ErrorName/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorName'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 5df1873fe..0d9e3283a 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -12,11 +12,13 @@ import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMe import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { debounce } from 'App/utils'; import useIsMounted from 'App/hooks/useIsMounted' +import { FilterKey } from 'Types/filter/filterType'; import FunnelWidget from 'App/components/Funnels/FunnelWidget'; import ErrorsWidget from '../Errors/ErrorsWidget'; import SessionWidget from '../Sessions/SessionWidget'; -import CustomMetricTableSessions from '../../Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; +import CustomMetricTableSessions from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; +import CustomMetricTableErrors from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors'; interface Props { metric: any; isWidget?: boolean; @@ -130,12 +132,19 @@ function WidgetChart(props: Props) { } if (metricType === 'table') { - if (metricOf === 'SESSIONS') { + if (metricOf === FilterKey.SESSIONS) { return ( + ) + } + if (metricOf === FilterKey.ERRORS) { + return ( + @@ -165,7 +174,7 @@ function WidgetChart(props: Props) { return
Unknown
; } return useObserver(() => ( - + {renderChart()} )); diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 338204f8a..e9acace0b 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -54,11 +54,11 @@ function WidgetView(props: Props) { className={cn( "px-6 py-4 flex justify-between items-center", { - 'cursor-pointer hover:bg-active-blue hover:shadow-border-blue': !expanded, + 'cursor-pointer hover:bg-active-blue hover:shadow-border-blue rounded': !expanded, } )} onClick={openEdit} - > + >

{ if (active) { - const p = payload[0].payload; + const p = payload[0].payload; return (

{`${moment(p.timestamp).format('l')}`}

diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index b846ed94b..4d945b483 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -7,7 +7,7 @@ interface Props { } function FunnelBar(props: Props) { const { filter } = props; - const completedPercentage = calculatePercentage(filter.sessionsCount, filter.dropDueToIssues); + // const completedPercentage = calculatePercentage(filter.sessionsCount, filter.dropDueToIssues); return (
diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 98e0c5e78..ba0e9ee83 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -8,6 +8,8 @@ import { getChartFormatter } from 'Types/dashboard/helper'; import Filter, { IFilter } from "./types/filter"; import Funnel from "./types/funnel"; import Session from "./types/session"; +import Error from "./types/error"; +import { FilterKey } from 'Types/filter/filterType'; export interface IDashboardSotre { dashboards: IDashboard[] @@ -132,7 +134,7 @@ export default class DashboardStore implements IDashboardSotre { fetchMetricChartData: action }) - const drillDownPeriod = Period({ rangeName: LAST_24_HOURS }).toTimestamps(); + const drillDownPeriod = Period({ rangeName: LAST_30_DAYS }).toTimestamps(); this.drillDownFilter.updateKey('startTimestamp', drillDownPeriod.startTimestamp) this.drillDownFilter.updateKey('endTimestamp', drillDownPeriod.endTimestamp) } @@ -459,8 +461,10 @@ export default class DashboardStore implements IDashboardSotre { } // TODO refactor to widget class - if (metric.metricOf === 'SESSIONS') { + if (metric.metricOf === FilterKey.SESSIONS) { _data['sessions'] = data.sessions.map((s: any) => new Session().fromJson(s)) + } else if (metric.metricOf === FilterKey.ERRORS) { + _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)) } else { if (data.hasOwnProperty('chart')) { _data['chart'] = getChartFormatter(this.period)(data.chart) diff --git a/frontend/app/mstore/types/error.ts b/frontend/app/mstore/types/error.ts index 7542aca4b..2f8b8fbec 100644 --- a/frontend/app/mstore/types/error.ts +++ b/frontend/app/mstore/types/error.ts @@ -2,7 +2,6 @@ export default class Error { sessionId: string = '' messageId: string = '' - timestamp: string = '' errorId: string = '' projectId: string = '' source: string = '' @@ -11,6 +10,12 @@ export default class Error { time: string = '' function: string = '?' stack0InfoString: string = '' + + chart: any = [] + sessions: number = 0 + users: number = 0 + lastOccurrence: string = '' + timestamp: string = '' constructor() { } @@ -26,7 +31,10 @@ export default class Error { this.message = json.message this.time = json.time this.function = json.function + this.chart = json.chart this.stack0InfoString = getStck0InfoString(json.stack || []) + this.sessions = json.sessions + this.users = json.users return this } } diff --git a/frontend/app/mstore/types/funnel.ts b/frontend/app/mstore/types/funnel.ts index 253fdb076..52b702d55 100644 --- a/frontend/app/mstore/types/funnel.ts +++ b/frontend/app/mstore/types/funnel.ts @@ -31,9 +31,9 @@ export default class Funnel implements IFunnel { const firstStage = json.stages[0] const lastStage = json.stages[json.stages.length - 1] this.lostConversions = firstStage.sessionsCount - lastStage.sessionsCount - this.lostConversionsPercentage = this.lostConversions / firstStage.sessionsCount * 100 + this.lostConversionsPercentage = Math.round(this.lostConversions / firstStage.sessionsCount * 100) this.totalConversions = lastStage.sessionsCount - this.totalConversionsPercentage = this.totalConversions / firstStage.sessionsCount * 100 + this.totalConversionsPercentage = Math.round(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, 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/widget.ts b/frontend/app/mstore/types/widget.ts index de01ad834..d84a18efc 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -5,6 +5,7 @@ import { metricService } from "App/services"; import Session from "App/mstore/types/session"; import Funnelissue from 'App/mstore/types/funnelIssue'; import { issueOptions } from 'App/constants/filterOptions'; +import { FilterKey } from 'Types/filter/filterType'; export interface IWidget { metricId: any @@ -58,7 +59,7 @@ export default class Widget implements IWidget { widgetId: any = undefined name: string = "New Metric" // metricType: string = "timeseries" - metricType: string = "table" + metricType: string = "timeseries" metricOf: string = "sessionCount" metricValue: string = "" viewType: string = "lineChart" @@ -79,6 +80,8 @@ export default class Widget implements IWidget { position: number = 0 data: any = { + sessions: [], + total: 0, chart: [], namesMap: {}, avg: 0, @@ -93,7 +96,7 @@ export default class Widget implements IWidget { constructor() { makeAutoObservable(this, { sessionsLoading: observable, - data: observable.ref, + data: observable, metricId: observable, widgetId: observable, name: observable, @@ -183,7 +186,7 @@ export default class Widget implements IWidget { series: this.series.map((series: any) => series.toJson()), config: { ...this.config, - col: this.metricType === 'funnel' ? 4 : this.config.col + col: this.metricType === 'funnel' || this.metricOf === FilterKey.ERRORS ? 4 : this.config.col }, } } @@ -204,7 +207,7 @@ export default class Widget implements IWidget { setData(data: any) { runInAction(() => { - Object.assign(this.data, data) + this.data = data; }) } @@ -236,8 +239,6 @@ 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.sessions.map((s: any) => new Session().fromJson(s)), diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index cfab5ae39..2696eec3b 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -89,7 +89,7 @@ export default class MetricService implements IMetricService { getMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise { const path = isWidget ? `/metrics/${metric.metricId}/chart` : `/metrics/try`; return this.client.post(path, data) - .then((response: { json: () => any; }) => response.json()) + .then(fetchErrorCheck) .then((response: { data: any; }) => response.data || {}); }