From 315227b91da8982b09062ffcc4090f0144279adc Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 20 Dec 2022 17:14:51 +0100 Subject: [PATCH] change(ui): fix some logs, fix map rendering into png and saving it --- .../ClickMapCard/ClickMapCard.tsx | 60 ++++++++++--------- .../components/FilterSeries/FilterSeries.tsx | 3 +- .../components/WidgetChart/WidgetChart.tsx | 13 +++- .../components/WidgetForm/WidgetForm.tsx | 21 ++++--- .../components/WidgetForm/renderMap.ts | 30 ++++++++++ .../WidgetWrapper/WidgetWrapper.tsx | 2 +- frontend/app/components/Session/WebPlayer.tsx | 9 ++- .../shared/Filters/FilterItem/FilterItem.tsx | 6 +- .../Filters/FilterValue/FilterValue.tsx | 5 +- frontend/app/mstore/types/widget.ts | 13 ++-- frontend/app/player/web/WebPlayer.ts | 8 ++- .../app/player/web/addons/TargetMarker.ts | 8 ++- .../app/player/web/addons/clickmapStyles.ts | 1 - frontend/app/services/MetricService.ts | 8 +-- 14 files changed, 120 insertions(+), 67 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/WidgetForm/renderMap.ts diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx index bf61ec0c8..0018e319a 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -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
looking for session
- console.log(visitedEvents, metricStore.instance.data.events) - if (!visitedEvents || !visitedEvents.length) { - return
loading
- } - const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0] - const jumpToEvent = metricStore.instance.data.events.find((evt: Record) => { - 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 ( -
- -
- ) + React.useEffect(() => { + if (metricStore.instance.data.mobsUrl) { + setCustomSession(metricStore.instance.data) + } + }, [metricStore.instance.data.mobsUrl]) + + if (!metricStore.instance.data?.mobsUrl) return
No Data for selected period or URL.
+ if (!visitedEvents || !visitedEvents.length) { + return
Loading session
+ } + + const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0] + const jumpToEvent = metricStore.instance.data.events.find((evt: Record) => { + if (searchUrl) return evt.path.includes(searchUrl) + return evt + }) + const jumpTimestamp = (jumpToEvent.timestamp - metricStore.instance.data.startTs) + jumpToEvent.domBuildingTime + return ( +
+ +
+ ) } -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)) diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index 2496d0377..ed974e30d 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -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() } diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 37a0e9297..cb459d7ee 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -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 ( +
+ clickmap thumbnail +
+ ) + } return ( ) diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index cf77dc872..4460f69a2 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -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 (
@@ -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) => (
{ + // @ts-ignore + return import('html2canvas').then(({ default: html2canvas }) => { + // @ts-ignore + window.html2canvas = html2canvas; + const element = document.querySelector('#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") + } + }) +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index 4aac81dd6..5e81386c6 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -162,7 +162,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
- +
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index be2297b3a..b380803d3 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -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(); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 6cebb0d94..fae8d9f1d 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -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 (
diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 4a13b2c4a..8984899ea 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -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] }); diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 69d9e556e..753691d3d 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -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 = 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 { - 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 { - 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)) : [] diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index b712b8c69..1fe878077 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -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) => { + this.targetMarker.setOnMarkerClick(...args) + } + // TODO separate message receivers toggleTimetravel = async () => { diff --git a/frontend/app/player/web/addons/TargetMarker.ts b/frontend/app/player/web/addons/TargetMarker.ts index 0066243b5..e20b4c6c4 100644 --- a/frontend/app/player/web/addons/TargetMarker.ts +++ b/frontend/app/player/web/addons/TargetMarker.ts @@ -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 + } + } diff --git a/frontend/app/player/web/addons/clickmapStyles.ts b/frontend/app/player/web/addons/clickmapStyles.ts index d69c5f7fa..522a2b285 100644 --- a/frontend/app/player/web/addons/clickmapStyles.ts +++ b/frontend/app/player/web/addons/clickmapStyles.ts @@ -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', diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index 4bf9dd231..7be2ee9d3 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -36,15 +36,14 @@ export default class MetricService { /** * Save a metric. - * @param metric + * @param metric * @returns */ - saveMetric(metric: Widget, dashboardId?: string): Promise { + saveMetric(metric: Widget): Promise { 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 */