change(ui): fix some logs, fix map rendering into png and saving it
This commit is contained in:
parent
f188073743
commit
315227b91d
14 changed files with 120 additions and 67 deletions
|
|
@ -5,34 +5,40 @@ import WebPlayer from 'App/components/Session/WebPlayer'
|
|||
import { connect } from 'react-redux'
|
||||
import { setCustomSession } from 'App/duck/sessions'
|
||||
|
||||
// TODO session type ???
|
||||
function ClickMapCard({ setCustomSession, visitedEvents }: any) {
|
||||
const { metricStore } = useStore()
|
||||
React.useEffect(() => {
|
||||
if (metricStore.instance.data.mobsUrl) {
|
||||
setCustomSession(metricStore.instance.data)
|
||||
}
|
||||
}, [metricStore.instance.data.mobsUrl])
|
||||
const { metricStore } = useStore();
|
||||
const onMarkerClick = (s: string) => console.log(s)
|
||||
|
||||
if (!metricStore.instance.data?.mobsUrl) return <div className="p-4">looking for session</div>
|
||||
console.log(visitedEvents, metricStore.instance.data.events)
|
||||
if (!visitedEvents || !visitedEvents.length) {
|
||||
return <div className="p-4">loading</div>
|
||||
}
|
||||
const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0]
|
||||
const jumpToEvent = metricStore.instance.data.events.find((evt: Record<string, any>) => {
|
||||
if (searchUrl) return evt.path.includes(searchUrl)
|
||||
return evt
|
||||
})
|
||||
const jumpTimestamp = (jumpToEvent.timestamp - metricStore.instance.data.startTs) + jumpToEvent.domBuildingTime
|
||||
console.log(jumpTimestamp, jumpToEvent, searchUrl)
|
||||
return (
|
||||
<div>
|
||||
<WebPlayer isClickmap customSession={metricStore.instance.data} customTimestamp={jumpTimestamp} />
|
||||
</div>
|
||||
)
|
||||
React.useEffect(() => {
|
||||
if (metricStore.instance.data.mobsUrl) {
|
||||
setCustomSession(metricStore.instance.data)
|
||||
}
|
||||
}, [metricStore.instance.data.mobsUrl])
|
||||
|
||||
if (!metricStore.instance.data?.mobsUrl) return <div className="p-2">No Data for selected period or URL.</div>
|
||||
if (!visitedEvents || !visitedEvents.length) {
|
||||
return <div className="p-2">Loading session</div>
|
||||
}
|
||||
|
||||
const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0]
|
||||
const jumpToEvent = metricStore.instance.data.events.find((evt: Record<string, any>) => {
|
||||
if (searchUrl) return evt.path.includes(searchUrl)
|
||||
return evt
|
||||
})
|
||||
const jumpTimestamp = (jumpToEvent.timestamp - metricStore.instance.data.startTs) + jumpToEvent.domBuildingTime
|
||||
return (
|
||||
<div id="clickmap-render">
|
||||
<WebPlayer
|
||||
isClickmap
|
||||
customSession={metricStore.instance.data}
|
||||
customTimestamp={jumpTimestamp}
|
||||
onMarkerClick={onMarkerClick}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({ visitedEvents: state.getIn(['sessions', 'visitedEvents']) }), {setCustomSession})(
|
||||
observer(ClickMapCard)
|
||||
)
|
||||
export default connect(
|
||||
(state: any) => ({ visitedEvents: state.getIn(['sessions', 'visitedEvents']) }),
|
||||
{ setCustomSession })
|
||||
(observer(ClickMapCard))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import {
|
||||
edit,
|
||||
|
|
@ -43,7 +43,6 @@ function FilterSeries(props: Props) {
|
|||
|
||||
const onUpdateFilter = (filterIndex: any, filter: any) => {
|
||||
series.filter.updateFilter(filterIndex, filter)
|
||||
console.log('hi', filterIndex, filter)
|
||||
observeChanges()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import CustomMetriLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart';
|
||||
import CustomMetricPercentage from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage';
|
||||
import CustomMetricTable from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable';
|
||||
|
|
@ -25,6 +25,7 @@ interface Props {
|
|||
metric: any;
|
||||
isWidget?: boolean;
|
||||
isTemplate?: boolean;
|
||||
isPreview?: boolean;
|
||||
}
|
||||
|
||||
function WidgetChart(props: Props) {
|
||||
|
|
@ -84,7 +85,7 @@ function WidgetChart(props: Props) {
|
|||
if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) {
|
||||
prevMetricRef.current = metric;
|
||||
return
|
||||
};
|
||||
}
|
||||
prevMetricRef.current = metric;
|
||||
const timestmaps = drillDownPeriod.toTimestamps();
|
||||
const payload = isWidget ? { ...params } : { ...metricParams, ...timestmaps, ...metric.toJson() };
|
||||
|
|
@ -179,6 +180,14 @@ function WidgetChart(props: Props) {
|
|||
}
|
||||
}
|
||||
if (metricType === CLICKMAP) {
|
||||
console.log(props.isPreview)
|
||||
if (!props.isPreview) {
|
||||
return (
|
||||
<div>
|
||||
<img src={metric.thumbnail} alt="clickmap thumbnail" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ClickMapCard />
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import MetricTypeDropdown from './components/MetricTypeDropdown';
|
|||
import MetricSubtypeDropdown from './components/MetricSubtypeDropdown';
|
||||
import { TIMESERIES, TABLE, CLICKMAP } from 'App/constants/card';
|
||||
import { clickmapFilter } from 'App/types/filter/newFilter';
|
||||
import { toJS } from 'mobx';
|
||||
import Period, { LAST_30_DAYS } from 'Types/app/period';
|
||||
import { renderClickmapThumbnail } from './renderMap'
|
||||
|
||||
interface Props {
|
||||
history: any;
|
||||
|
|
@ -21,12 +20,6 @@ interface Props {
|
|||
onDelete: () => void;
|
||||
}
|
||||
|
||||
const metricIcons = {
|
||||
timeseries: 'graph-up',
|
||||
table: 'table',
|
||||
funnel: 'funnel',
|
||||
};
|
||||
|
||||
function WidgetForm(props: Props) {
|
||||
const {
|
||||
history,
|
||||
|
|
@ -88,8 +81,15 @@ function WidgetForm(props: Props) {
|
|||
metricStore.merge(obj);
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
const onSave = async () => {
|
||||
const wasCreating = !metric.exists();
|
||||
if (isClickmap) {
|
||||
try {
|
||||
metric.thumbnail = await renderClickmapThumbnail()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
metricStore.save(metric, dashboardId).then((metric: any) => {
|
||||
if (wasCreating) {
|
||||
if (parseInt(dashboardId) > 0) {
|
||||
|
|
@ -115,7 +115,6 @@ function WidgetForm(props: Props) {
|
|||
}
|
||||
};
|
||||
|
||||
console.log(toJS(metric));
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="form-group">
|
||||
|
|
@ -194,7 +193,7 @@ function WidgetForm(props: Props) {
|
|||
|
||||
{metric.series.length > 0 &&
|
||||
metric.series
|
||||
.slice(0, isTable || isFunnel || isClickmap? 1 : metric.series.length)
|
||||
.slice(0, isTable || isFunnel || isClickmap ? 1 : metric.series.length)
|
||||
.map((series: any, index: number) => (
|
||||
<div className="mb-2" key={series.name}>
|
||||
<FilterSeries
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
export const renderClickmapThumbnail = () => {
|
||||
// @ts-ignore
|
||||
return import('html2canvas').then(({ default: html2canvas }) => {
|
||||
// @ts-ignore
|
||||
window.html2canvas = html2canvas;
|
||||
const element = document.querySelector<HTMLIFrameElement>('#clickmap-render * iframe').contentDocument.body
|
||||
console.log(element)
|
||||
if (element) {
|
||||
const dimensions = element.getBoundingClientRect()
|
||||
return html2canvas(
|
||||
element,
|
||||
{
|
||||
scale: 1,
|
||||
// allowTaint: true,
|
||||
useCORS: true,
|
||||
foreignObjectRendering: true,
|
||||
height: dimensions.height > 900 ? 900 : dimensions.height,
|
||||
width: dimensions.width > 1200 ? 1200 : dimensions.width,
|
||||
x: 0,
|
||||
y: 0,
|
||||
ignoreElements: (e) => e.id.includes('render-ignore'),
|
||||
}
|
||||
).then((canvas) => {
|
||||
return canvas.toDataURL('img/png');
|
||||
}).catch(console.log);
|
||||
} else {
|
||||
Promise.reject("can't find clickmap container")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -162,7 +162,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
|
|||
|
||||
<LazyLoad offset={!isTemplate ? 100 : 600}>
|
||||
<div className="px-4" onClick={onChartClick}>
|
||||
<WidgetChart metric={widget} isTemplate={isTemplate} isWidget={isWidget} />
|
||||
<WidgetChart isPreview={isPreview} metric={widget} isTemplate={isTemplate} isWidget={isWidget} />
|
||||
</div>
|
||||
</LazyLoad>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ function WebPlayer(props: any) {
|
|||
insightsFilters,
|
||||
insights,
|
||||
jumpTimestamp,
|
||||
onMarkerClick,
|
||||
} = props;
|
||||
const { notesStore } = useStore();
|
||||
const [activeTab, setActiveTab] = useState('');
|
||||
|
|
@ -72,11 +73,13 @@ function WebPlayer(props: any) {
|
|||
setShowNote(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
WebPlayerInst.setMarkerClick(onMarkerClick)
|
||||
}
|
||||
|
||||
const jumptTime = props.query.get('jumpto');
|
||||
if (jumptTime) {
|
||||
WebPlayerInst.jump(parseInt(jumptTime));
|
||||
const jumpToTime = props.query.get('jumpto');
|
||||
if (jumpToTime) {
|
||||
WebPlayerInst.jump(parseInt(jumpToTime));
|
||||
}
|
||||
|
||||
return () => WebPlayerInst.clean();
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ function FilterItem(props: Props) {
|
|||
});
|
||||
};
|
||||
|
||||
const onOperatorChange = (e: any, { name, value }: any) => {
|
||||
const onOperatorChange = (e: any, { value }: any) => {
|
||||
props.onUpdate({ ...filter, operator: value });
|
||||
};
|
||||
|
||||
const onSourceOperatorChange = (e: any, { name, value }: any) => {
|
||||
const onSourceOperatorChange = (e: any, { value }: any) => {
|
||||
props.onUpdate({ ...filter, sourceOperator: value });
|
||||
};
|
||||
|
||||
const onUpdateSubFilter = (subFilter: any, subFilterIndex: any) => {
|
||||
console.log(subFilter, subFilterIndex)
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
filters: filter.filters.map((i: any, index: any) => {
|
||||
|
|
@ -49,7 +48,6 @@ function FilterItem(props: Props) {
|
|||
});
|
||||
};
|
||||
|
||||
console.log('filterItem', filter)
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2">
|
||||
<div className="flex items-start w-full">
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ function FilterValue(props: Props) {
|
|||
}
|
||||
return _;
|
||||
});
|
||||
console.log(item ,{ ...filter, value: newValues });
|
||||
props.onUpdate({ ...filter, value: newValues });
|
||||
};
|
||||
|
||||
|
|
@ -50,9 +49,9 @@ function FilterValue(props: Props) {
|
|||
setDurationValues({ ...durationValues, ...newValues });
|
||||
};
|
||||
|
||||
const handleBlur = (e: any) => {
|
||||
const handleBlur = () => {
|
||||
if (filter.type === FilterType.DURATION) {
|
||||
const { maxDuration, minDuration, key } = filter;
|
||||
const { maxDuration, minDuration } = filter;
|
||||
if (maxDuration || minDuration) return;
|
||||
if (maxDuration !== durationValues.maxDuration || minDuration !== durationValues.minDuration) {
|
||||
props.onUpdate({ ...filter, value: [durationValues.minDuration, durationValues.maxDuration] });
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { makeAutoObservable, runInAction, observable, action } from "mobx"
|
||||
import { makeAutoObservable, runInAction } from "mobx"
|
||||
import FilterSeries from "./filterSeries";
|
||||
import { DateTime } from 'luxon';
|
||||
import { metricService, errorService } from "App/services";
|
||||
import Session from "App/mstore/types/session";
|
||||
import Funnelissue from 'App/mstore/types/funnelIssue';
|
||||
import { issueOptions } from 'App/constants/filterOptions';
|
||||
|
|
@ -30,9 +29,9 @@ export default class Widget {
|
|||
config: any = {}
|
||||
page: number = 1
|
||||
limit: number = 5
|
||||
thumbnail?: string
|
||||
params: any = { density: 70 }
|
||||
|
||||
|
||||
period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }) // temp value in detail view
|
||||
hasChanged: boolean = false
|
||||
|
||||
|
|
@ -85,7 +84,7 @@ export default class Widget {
|
|||
this.metricFormat = json.metricFormat
|
||||
this.viewType = json.viewType
|
||||
this.name = json.name
|
||||
this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : [],
|
||||
this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : []
|
||||
this.dashboards = json.dashboards || []
|
||||
this.owner = json.ownerEmail
|
||||
this.lastModified = json.editedAt || json.createdAt ? DateTime.fromMillis(json.editedAt || json.createdAt) : null
|
||||
|
|
@ -93,6 +92,7 @@ export default class Widget {
|
|||
this.position = json.config.position
|
||||
this.predefinedKey = json.predefinedKey
|
||||
this.category = json.category
|
||||
this.thumbnail = json.thumbnail
|
||||
|
||||
if (period) {
|
||||
this.period = period
|
||||
|
|
@ -130,6 +130,7 @@ export default class Widget {
|
|||
viewType: this.viewType,
|
||||
name: this.name,
|
||||
series: this.series.map((series: any) => series.toJson()),
|
||||
thumbnail: this.thumbnail,
|
||||
config: {
|
||||
...this.config,
|
||||
col: (this.metricType === 'funnel' || this.metricOf === FilterKey.ERRORS || this.metricOf === FilterKey.SESSIONS) ? 4 : 2
|
||||
|
|
@ -156,7 +157,7 @@ export default class Widget {
|
|||
}
|
||||
|
||||
fetchSessions(metricId: any, filter: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
metricService.fetchSessions(metricId, filter).then((response: any[]) => {
|
||||
resolve(response.map((cat: { sessions: any[]; }) => {
|
||||
return {
|
||||
|
|
@ -169,7 +170,7 @@ export default class Widget {
|
|||
}
|
||||
|
||||
fetchIssues(filter: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
metricService.fetchIssues(filter).then((response: any) => {
|
||||
const significantIssues = response.issues.significant ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) : []
|
||||
const insignificantIssues = response.issues.insignificant ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) : []
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ export default class WebPlayer extends Player {
|
|||
// this.updateMarketTargets() ??
|
||||
}
|
||||
|
||||
scaleFullPage =() => {
|
||||
scaleFullPage = () => {
|
||||
window.removeEventListener('resize', this.scale)
|
||||
window.addEventListener('resize', this.screen.scaleFullPage)
|
||||
return this.screen.scaleFullPage()
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +129,10 @@ export default class WebPlayer extends Player {
|
|||
this.targetMarker.injectTargets(...args)
|
||||
}
|
||||
|
||||
setMarkerClick = (...args: Parameters<TargetMarker['setOnMarkerClick']>) => {
|
||||
this.targetMarker.setOnMarkerClick(...args)
|
||||
}
|
||||
|
||||
|
||||
// TODO separate message receivers
|
||||
toggleTimetravel = async () => {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export default class TargetMarker {
|
|||
private clickMapOverlay: HTMLDivElement
|
||||
private clickContainers: HTMLDivElement[] = []
|
||||
private smallClicks: HTMLDivElement[] = []
|
||||
private onMarkerClick: (selector: string) => void
|
||||
static INITIAL_STATE: State = {
|
||||
markedTargets: null,
|
||||
activeTargetIndex: 0
|
||||
|
|
@ -145,14 +146,12 @@ export default class TargetMarker {
|
|||
this.clickMapOverlay?.remove()
|
||||
const overlay = document.createElement("div")
|
||||
const iframeSize = this.screen.iframeStylesRef
|
||||
console.log(iframeSize)
|
||||
const scaleRatio = this.screen.getScale()
|
||||
Object.assign(overlay.style, clickmapStyles.overlayStyle({ height: iframeSize.height, width: iframeSize.width, scale: scaleRatio }))
|
||||
|
||||
this.clickMapOverlay = overlay
|
||||
selections.forEach((s, i) => {
|
||||
const el = this.screen.getElementBySelector(s.selector);
|
||||
console.log(el, s.selector)
|
||||
if (!el) return;
|
||||
|
||||
const bubbleContainer = document.createElement("div")
|
||||
|
|
@ -183,6 +182,7 @@ export default class TargetMarker {
|
|||
|
||||
border.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
this.onMarkerClick?.(s.selector)
|
||||
this.clickContainers.forEach(container => {
|
||||
if (container.id === containerId) {
|
||||
container.style.visibility = "visible"
|
||||
|
|
@ -226,4 +226,8 @@ export default class TargetMarker {
|
|||
}
|
||||
}
|
||||
|
||||
setOnMarkerClick(cb: (selector: string) => void) {
|
||||
this.onMarkerClick = cb
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export const clickmapStyles = {
|
|||
background: 'rgba(0,0,0, 0.15)',
|
||||
zIndex: 9 * 10e3,
|
||||
transformOrigin: 'left top',
|
||||
// pointerEvents: 'none',
|
||||
}),
|
||||
totalClicks: {
|
||||
fontSize: '16px',
|
||||
|
|
|
|||
|
|
@ -36,15 +36,14 @@ export default class MetricService {
|
|||
|
||||
/**
|
||||
* Save a metric.
|
||||
* @param metric
|
||||
* @param metric
|
||||
* @returns
|
||||
*/
|
||||
saveMetric(metric: Widget, dashboardId?: string): Promise<any> {
|
||||
saveMetric(metric: Widget): Promise<any> {
|
||||
const data = metric.toJson()
|
||||
const isCreating = !data[Widget.ID_KEY];
|
||||
const method = isCreating ? 'post' : 'put';
|
||||
const url = isCreating ? '/cards' : '/cards/' + data[Widget.ID_KEY];
|
||||
return this.client[method](url, data)
|
||||
return this.client.post(url, data)
|
||||
.then(fetchErrorCheck)
|
||||
.then((response: { data: any; }) => response.data || {})
|
||||
}
|
||||
|
|
@ -80,6 +79,7 @@ export default class MetricService {
|
|||
|
||||
/**
|
||||
* Fetch sessions from the server.
|
||||
* @param metricId {String}
|
||||
* @param filter
|
||||
* @returns
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue