feat(ui) - sessions - widget - pagination
This commit is contained in:
parent
951ffa0320
commit
10c064c99c
14 changed files with 103 additions and 54 deletions
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 /> }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || {})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -93,5 +93,5 @@ export enum FilterKey {
|
|||
GRAPHQL_RESPONSE_BODY = "GRAPHQL_RESPONSE_BODY",
|
||||
|
||||
SESSIONS = 'SESSIONS',
|
||||
ERRORS = 'ERRORS'
|
||||
ERRORS = 'js_exception'
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue