feat(ui) - sessions - widget - pagination

This commit is contained in:
Shekar Siri 2022-06-16 19:27:01 +02:00
parent 951ffa0320
commit 10c064c99c
14 changed files with 103 additions and 54 deletions

View file

@ -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 (
<div>
@ -25,7 +29,7 @@ function CustomMetricTableSessions(props: Props) {
<Pagination
page={currentPage}
totalPages={Math.ceil(data.total / PER_PAGE)}
onPageChange={(page: any) => this.props.updateCurrentPage(page)}
onPageChange={(page: any) => metric.updateKey('page', page)}
limit={PER_PAGE}
debounceRequest={500}
/>

View file

@ -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 <CustomMetricTableSessions
metric={metric}
data={data}
// onClick={onChartClick}
isTemplate={isTemplate}
return (
<CustomMetricTableSessions
metric={_metric}
data={data}
// onClick={onChartClick}
isTemplate={isTemplate}
isEdit={!isWidget && !isTemplate}
/>
)
}
if (viewType === 'table') {
return <CustomMetricTable
metric={metric} data={data[0]}
onClick={onChartClick}
isTemplate={isTemplate}
/>;
return (
<CustomMetricTable
metric={metric} data={data[0]}
onClick={onChartClick}
isTemplate={isTemplate}
/>
)
} else if (viewType === 'pieChart') {
return (
<CustomMetricPieChart

View file

@ -69,15 +69,16 @@ function WidgetForm(props: Props) {
const onSave = () => {
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 () => {

View file

@ -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"
/>
</>
)}

View file

@ -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) {
</div>
<WidgetPreview className="mt-8" />
{ widget.metricOf !== 'SESSIONS' && widget.metricOf !== 'ERRORS' && (
{ widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{ (widget.metricType === 'table' || widget.metricType === 'timeseries') && <WidgetSessions className="mt-8" /> }
{ widget.metricType === 'funnel' && <FunnelIssues /> }

View file

@ -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;
}

View file

@ -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,

View file

@ -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 (
<div className={ cn(styles.wrapper, {
[styles.primary] : primary,
[styles.small] : size === 'small' || small,
[styles.extraSmall] : size === 'extraSmall' || extraSmall,
[styles.icons] : icons === true,
}, className) }
<Popup
content={disabledMessage}
disabled={!disabled}
>
{ list.map(item => (
<div
key={ item.name }
className={ cn(styles.item, 'w-full', { 'opacity-25 cursor-default' : item.disabled }) }
data-active={ this.props.value && this.props.value.value === item.value }
onClick={ () => !item.disabled && this.setActiveItem(item) }
>
{ item.icon && <Icon name={ item.icon } size={(size === "extraSmall" || icons) ? 14 : 20} marginRight={ item.name ? "6" : "" } /> }
<div className="leading-none">{ item.name }</div>
</div>
))
}
</div>
<div className={ cn(styles.wrapper, {
[styles.primary] : primary,
[styles.small] : size === 'small' || small,
[styles.extraSmall] : size === 'extraSmall' || extraSmall,
[styles.icons] : icons === true,
[styles.disabled] : disabled,
}, className) }
>
{ list.map(item => (
<div
key={ item.name }
className={ cn(styles.item, 'w-full', { 'opacity-25 cursor-default' : item.disabled }) }
data-active={ this.props.value && this.props.value.value === item.value }
onClick={ () => !item.disabled && this.setActiveItem(item) }
>
{ item.icon && <Icon name={ item.icon } size={(size === "extraSmall" || icons) ? 14 : 20} marginRight={ item.name ? "6" : "" } /> }
<div className="leading-none">{ item.name }</div>
</div>
))
}
</div>
</Popup>
);
}
}

View file

@ -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;
}

View file

@ -434,8 +434,15 @@ export default class DashboardStore implements IDashboardSotre {
fetchMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise<any> {
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) }

View file

@ -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
}

View file

@ -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 || {})
}
/**

View file

@ -93,5 +93,5 @@ export enum FilterKey {
GRAPHQL_RESPONSE_BODY = "GRAPHQL_RESPONSE_BODY",
SESSIONS = 'SESSIONS',
ERRORS = 'ERRORS'
ERRORS = 'js_exception'
}

View file

@ -308,4 +308,11 @@ export const exportCSVFile = (headers, items, fileTitle) => {
document.body.removeChild(link);
}
}
}
}
export const fetchErrorCheck = (response: any) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
}