change(ui): fix some logs, fix map rendering into png and saving it

This commit is contained in:
sylenien 2022-12-20 17:14:51 +01:00
parent f188073743
commit 315227b91d
14 changed files with 120 additions and 67 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

@ -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)) : []

View file

@ -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 () => {

View file

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

View file

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

View file

@ -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
*/