From 10c064c99cdffcfe3f50c3f716237fc56fe8d8b3 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 16 Jun 2022 19:27:01 +0200 Subject: [PATCH] feat(ui) - sessions - widget - pagination --- .../CustomMetricTableSessions.tsx | 8 +++- .../components/WidgetChart/WidgetChart.tsx | 27 ++++++----- .../components/WidgetForm/WidgetForm.tsx | 17 +++---- .../WidgetPreview/WidgetPreview.tsx | 8 +++- .../components/WidgetView/WidgetView.tsx | 3 +- .../Session_/Player/Controls/Controls.js | 1 - .../SelectDateRange/SelectDateRange.tsx | 1 + .../ui/SegmentSelection/SegmentSelection.js | 48 +++++++++++-------- .../segmentSelection.module.css | 7 +++ frontend/app/mstore/dashboardStore.ts | 9 +++- frontend/app/mstore/types/widget.ts | 12 +++-- frontend/app/services/MetricService.ts | 5 +- frontend/app/types/filter/filterType.ts | 2 +- frontend/app/utils.ts | 9 +++- 14 files changed, 103 insertions(+), 54 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index 45cbd198a..8e2a56004 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,6 +1,8 @@ +import { useObserver } from 'mobx-react-lite'; import React from 'react'; import SessionItem from 'Shared/SessionItem'; import { Pagination } from 'UI'; +import { useStore } from 'App/mstore'; const PER_PAGE = 10; interface Props { @@ -11,8 +13,10 @@ interface Props { } function CustomMetricTableSessions(props: Props) { - const { data = { sessions: [], total: 0 }, isEdit = false, metric = {}, isTemplate } = props; + const { data = { sessions: [], total: 0 }, isEdit = false } = props; const currentPage = 1; + const { metricStore } = useStore(); + const metric: any = useObserver(() => metricStore.instance); return (
@@ -25,7 +29,7 @@ function CustomMetricTableSessions(props: Props) { this.props.updateCurrentPage(page)} + onPageChange={(page: any) => metric.updateKey('page', page)} limit={PER_PAGE} debounceRequest={500} /> diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 83894d299..5df1873fe 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -84,7 +84,7 @@ function WidgetChart(props: Props) { prevMetricRef.current = metric; const payload = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() }; debounceRequest(metric, payload, isWidget); - }, [period, depsString]); + }, [period, depsString, _metric.page]); const renderChart = () => { const { metricType, viewType, metricOf } = metric; @@ -131,19 +131,24 @@ function WidgetChart(props: Props) { if (metricType === 'table') { if (metricOf === 'SESSIONS') { - return + ) } if (viewType === 'table') { - return ; + return ( + + ) } else if (viewType === 'pieChart') { return ( { const wasCreating = !metric.exists() - metricStore.save(metric, dashboardId).then((metric: any) => { - if (wasCreating) { - if (parseInt(dashboardId) > 0) { - history.replace(withSiteId(dashboardMetricDetails(parseInt(dashboardId), metric.metricId), siteId)); - } else { - history.replace(withSiteId(metricDetails(metric.metricId), siteId)); + metricStore.save(metric, dashboardId) + .then((metric: any) => { + if (wasCreating) { + if (parseInt(dashboardId) > 0) { + history.replace(withSiteId(dashboardMetricDetails(parseInt(dashboardId), metric.metricId), siteId)); + } else { + history.replace(withSiteId(metricDetails(metric.metricId), siteId)); + } } - } - }); + }); } const onDelete = async () => { diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 679da3944..75e6eb270 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -5,6 +5,7 @@ import { useStore } from 'App/mstore'; import { SegmentSelection } from 'UI'; import { useObserver } from 'mobx-react-lite'; import SelectDateRange from 'Shared/SelectDateRange'; +import { FilterKey } from 'Types/filter/filterType'; interface Props { className?: string; @@ -16,6 +17,7 @@ function WidgetPreview(props: Props) { const metric: any = useObserver(() => metricStore.instance); const isTimeSeries = metric.metricType === 'timeseries'; const isTable = metric.metricType === 'table'; + const disableVisualization = useObserver(() => metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS); const chagneViewType = (e, { name, value }: any) => { metric.update({ [ name ]: value }); @@ -55,9 +57,11 @@ function WidgetPreview(props: Props) { onSelect={ chagneViewType } value={{ value: metric.viewType }} list={[ - { value: 'table', name: 'Table', icon: 'table' }, - { value: 'pieChart', name: 'Chart', icon: 'pie-chart-fill' }, + { value: 'table', name: 'Table', icon: 'table' }, + { value: 'pieChart', name: 'Chart', icon: 'pie-chart-fill' }, ]} + disabled={disableVisualization} + disabledMessage="Chart view is not supported" /> )} diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 27d3b292c..338204f8a 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -10,6 +10,7 @@ import WidgetName from '../WidgetName'; import { withSiteId } from 'App/routes'; import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues'; import Breadcrumb from 'Shared/Breadcrumb'; +import { FilterKey } from 'Types/filter/filterType'; interface Props { history: any; match: any @@ -80,7 +81,7 @@ function WidgetView(props: Props) {
- { widget.metricOf !== 'SESSIONS' && widget.metricOf !== 'ERRORS' && ( + { widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( <> { (widget.metricType === 'table' || widget.metricType === 'timeseries') && } { widget.metricType === 'funnel' && } diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 48f738247..d42cff222 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -164,7 +164,6 @@ export default class Controls extends React.Component { } onKeyDown = (e) => { - console.log(e.key, e.target) if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { return; } diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 13573b4d5..107c87337 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -5,6 +5,7 @@ import Period, { LAST_7_DAYS } from 'Types/app/period'; import { components } from 'react-select'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import cn from 'classnames'; interface Props { period: any, diff --git a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js index feebd694a..dde4d6b7c 100644 --- a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js +++ b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon } from 'UI'; +import { Icon, Popup } from 'UI'; import cn from 'classnames'; import styles from './segmentSelection.module.css'; @@ -9,29 +9,35 @@ class SegmentSelection extends React.Component { } render() { - const { className, list, small = false, extraSmall = false, primary = false, size = "normal", icons = false } = this.props; + const { className, list, small = false, extraSmall = false, primary = false, size = "normal", icons = false, disabled = false, disabledMessage = 'Not Allowed' } = this.props; return ( -
- { list.map(item => ( -
!item.disabled && this.setActiveItem(item) } - > - { item.icon && } -
{ item.name }
-
- )) - } -
+
+ { list.map(item => ( +
!item.disabled && this.setActiveItem(item) } + > + { item.icon && } +
{ item.name }
+
+ )) + } +
+ ); } } diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css index 907f81e37..082e675c9 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css @@ -5,6 +5,7 @@ border: solid thin $gray-light; border-radius: 3px; overflow: hidden; + user-select: none; & .item { color: $gray-medium; @@ -79,4 +80,10 @@ .icons .item { padding: 4px !important; font-size: 12px; +} + +.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; } \ No newline at end of file diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 6ea803c82..98e0c5e78 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -434,8 +434,15 @@ export default class DashboardStore implements IDashboardSotre { fetchMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise { const period = this.period.toTimestamps() + const params = { ...period, ...data, key: metric.predefinedKey } + + if (metric.page && metric.limit) { + params['page'] = metric.page + params['limit'] = metric.limit + } + return new Promise((resolve, reject) => { - return metricService.getMetricChartData(metric, { ...period, ...data, key: metric.predefinedKey, page: 1, limit: 10 }, isWidget) + return metricService.getMetricChartData(metric, params, isWidget) .then((data: any) => { if (metric.metricType === 'predefined' && metric.viewType === 'overview') { const _data = { ...data, chart: getChartFormatter(this.period)(data.chart) } diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index b343b529f..de01ad834 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -33,10 +33,13 @@ export interface IWidget { dashboardId: any colSpan: number predefinedKey: string + + page: number + limit: number params: any - udpateKey(key: string, value: any): void + updateKey(key: string, value: any): void removeSeries(index: number): void addSeries(): void fromJson(json: any): void @@ -68,6 +71,8 @@ export default class Widget implements IWidget { dashboards: any[] = [] dashboardIds: any[] = [] config: any = {} + page: number = 1 + limit: number = 5 params: any = { density: 70 } sessionsLoading: boolean = false @@ -100,6 +105,7 @@ export default class Widget implements IWidget { dashboardId: observable, colSpan: observable, series: observable, + page: observable, addSeries: action, removeSeries: action, @@ -107,14 +113,14 @@ export default class Widget implements IWidget { toJson: action, validate: action, update: action, - udpateKey: action, + updateKey: action, }) const filterSeries = new FilterSeries() this.series.push(filterSeries) } - udpateKey(key: string, value: any) { + updateKey(key: string, value: any) { this[key] = value } diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index 3bc4d4ef9..cfab5ae39 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -1,6 +1,7 @@ import Widget, { IWidget } from "App/mstore/types/widget"; import APIClient from 'App/api_client'; import { IFilter } from "App/mstore/types/filter"; +import { fetchErrorCheck } from "App/utils"; export interface IMetricService { initClient(client?: APIClient): void; @@ -59,8 +60,8 @@ export default class MetricService implements IMetricService { const method = isCreating ? 'post' : 'put'; const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; return this.client[method](url, data) - .then((response: { json: () => any; }) => response.json()) - .then((response: { data: any; }) => response.data || {}); + .then(fetchErrorCheck) + .then((response: { data: any; }) => response.data || {}) } /** diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 4194b759b..772bea55e 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -93,5 +93,5 @@ export enum FilterKey { GRAPHQL_RESPONSE_BODY = "GRAPHQL_RESPONSE_BODY", SESSIONS = 'SESSIONS', - ERRORS = 'ERRORS' + ERRORS = 'js_exception' } \ No newline at end of file diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 39b9cd3f7..4a2b57a56 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -308,4 +308,11 @@ export const exportCSVFile = (headers, items, fileTitle) => { document.body.removeChild(link); } } -} \ No newline at end of file +} + +export const fetchErrorCheck = (response: any) => { + if (!response.ok) { + throw Error(response.statusText); + } + return response.json(); +}