change(ui): add pdf gen

This commit is contained in:
sylenien 2022-10-26 10:42:16 +02:00 committed by Delirium
parent 7e8a106edf
commit f2c9250cc8
12 changed files with 172 additions and 50 deletions

View file

@ -2,14 +2,15 @@ import React from 'react';
import { connect } from 'react-redux';
import { countries } from 'App/constants';
import { useStore } from 'App/mstore';
import { Button } from 'UI';
import { session as sessionRoute } from 'App/routes';
import { ReportDefaults, EnvData } from './types'
import Session from './components/Session'
import MetaInfo from './components/MetaInfo'
import Title from './components/Title'
import Comments from './components/Comments'
import Steps from './components/Steps'
import { mapEvents } from './utils'
import { ReportDefaults, EnvData } from './types';
import Session from './components/Session';
import MetaInfo from './components/MetaInfo';
import Title from './components/Title';
import Comments from './components/Comments';
import Steps from './components/Steps';
import { mapEvents } from './utils';
interface Props {
hideModal: () => void;
@ -23,11 +24,14 @@ interface Props {
exceptionsList: Record<string, any>[];
eventsList: Record<string, any>[];
endTime: number;
}
};
}
function BugReportModal({ hideModal, session, width, height, account, xrayProps }: Props) {
const { bugReportStore } = useStore()
const reportRef = React.createRef<HTMLDivElement>();
const [isRendering, setRendering] = React.useState(false);
const { bugReportStore } = useStore();
const {
userBrowser,
userDevice,
@ -43,7 +47,7 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps
events,
} = session;
console.log(session.toJS())
console.log(session.toJS());
const envObject: EnvData = {
Device: `${userDevice}${userDeviceType !== userDevice ? ` ${userDeviceType}` : ''}`,
@ -54,9 +58,13 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps
Country: countries[userCountry],
};
if (revId) {
Object.assign(envObject, { Rev: revId })
Object.assign(envObject, { Rev: revId });
}
const sessionUrl = `${window.location.origin}/${window.location.pathname.split('/')[1]}${sessionRoute(sessionId)}`
const sessionUrl = `${window.location.origin}/${
window.location.pathname.split('/')[1]
}${sessionRoute(sessionId)}`;
const defaults: ReportDefaults = {
author: account.name,
env: envObject,
@ -65,32 +73,132 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps
user: userDisplayName,
id: sessionId,
url: sessionUrl,
}
}
bugReportStore.updateReportDefaults(defaults)
bugReportStore.setDefaultSteps(mapEvents(events))
},
};
React.useEffect(() => {
return () => bugReportStore.clearStore()
}, [])
bugReportStore.updateReportDefaults(defaults);
bugReportStore.setDefaultSteps(mapEvents(events));
return () => bugReportStore.clearStore();
}, []);
const onGen = () => {
// @ts-ignore
import('html2canvas').then(({ default: html2canvas }) => {
// @ts-ignore
window.html2canvas = html2canvas;
// @ts-ignore
import('jspdf').then(({ jsPDF }) => {
setRendering(true);
const doc = new jsPDF('p', 'mm', 'a4');
const now = new Date().toISOString();
doc.addMetadata('Author', account.name);
doc.addMetadata('Title', 'OpenReplay Bug Report');
doc.addMetadata('Subject', 'OpenReplay Bug Report');
doc.addMetadata('Keywords', 'OpenReplay Bug Report');
doc.addMetadata('Creator', 'OpenReplay');
doc.addMetadata('Producer', 'OpenReplay');
doc.addMetadata('CreationDate', now);
// DO NOT DELETE UNUSED RENDER FUNCTION
// REQUIRED FOR FUTURE USAGE AND AS AN EXAMPLE OF THE FUNCTIONALITY
function buildPng() {
html2canvas(reportRef.current, {
scale: 2,
ignoreElements: (e) => e.id.includes('pdf-ignore'),
}).then((canvas) => {
const imgData = canvas.toDataURL('img/png');
var imgWidth = 210;
var pageHeight = 295;
var imgHeight = (canvas.height * imgWidth) / canvas.width;
var heightLeft = imgHeight;
var position = 0;
doc.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight + 10;
doc.addPage();
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.link(5, 295 - Math.abs(heightLeft) - 25, 200, 30, { url: sessionUrl });
doc.save('Bug Report: ' + sessionId + '.pdf');
setRendering(false);
});
}
function buildText() {
doc
.html(reportRef.current, {
x: 0,
y: 0,
width: 210,
windowWidth: reportRef.current.getBoundingClientRect().width,
autoPaging: 'text',
html2canvas: {
ignoreElements: (e) => e.id.includes('pdf-ignore') || e instanceof SVGElement,
},
})
.save('html.pdf')
.then(() => {
setRendering(false);
})
.catch((e) => {
console.error(e);
setRendering(false);
});
}
// buildText();
buildPng();
});
});
};
return (
<div
className="flex flex-col p-4 gap-4 bg-white overflow-y-scroll"
className="bg-white overflow-y-scroll"
style={{ maxWidth: '70vw', width: 620, height: '100vh' }}
>
<Title userName={account.name} />
<MetaInfo envObject={envObject} metadata={metadata} />
<Steps xrayProps={xrayProps} />
<Session user={userDisplayName} sessionId={sessionId} sessionUrl={sessionUrl} />
<Comments />
<div className="flex flex-col p-4 gap-4 bg-white h-auto relative" ref={reportRef}>
<Title userName={account.name} />
<MetaInfo envObject={envObject} metadata={metadata} />
<Steps xrayProps={xrayProps} />
<Comments />
<Session user={userDisplayName} sessionId={sessionId} sessionUrl={sessionUrl} />
<div id="pdf-ignore" className="flex items-center gap-2 mt-4">
<Button icon="file-pdf" variant="primary" onClick={onGen} loading={isRendering}>
Download Bug Report
</Button>
<Button variant="text-primary" onClick={hideModal}>
Close
</Button>
</div>
</div>
{isRendering ? (
<div
className="fixed min-h-screen flex text-xl items-center justify-center top-0 right-0 cursor-wait"
style={{ background: 'rgba(0,0,0, 0.2)', zIndex: 9999, width: 620, maxWidth: '70vw' }}
id="pdf-ignore"
>
<div>Rendering PDF Report</div>
</div>
) : null}
</div>
);
}
// @ts-ignore
const WithUIState = connect((state) => ({ session: state.getIn(['sessions', 'current']), account: state.getIn(['user', 'account']), }))(
BugReportModal
);
const WithUIState = connect((state) => ({
// @ts-ignore
session: state.getIn(['sessions', 'current']),
// @ts-ignore
account: state.getIn(['user', 'account']),
}))(BugReportModal);
export default WithUIState
export default WithUIState;

View file

@ -24,7 +24,7 @@ function Comments() {
: 'Expected results, additional steps or any other useful information for debugging.';
return (
<div className="w-full">
<div className="w-full" id={commentsEnabled ? '' : 'pdf-ignore'}>
<div className="flex items-center gap-2">
<SectionTitle>Comments</SectionTitle>
<div className="text-disabled-text mb-2">(Optional)</div>

View file

@ -25,8 +25,10 @@ export default function MetaInfo({
<div key={envTag} className="flex items-center">
<div className="py-1 px-2">{envTag}</div>
<div className="py-1 px-2 text-gray-medium bg-light-blue-bg rounded">
<span className="text-base">
{/* @ts-ignore */}
{envObject[envTag]}
</span>
</div>
</div>
))}

View file

@ -11,7 +11,7 @@ export default function Session({ user, sessionId, sessionUrl }: { user: string,
<div className="flex flex-col">
<div className="text-lg">{user}</div>
<div className="text-disabled-text">
{sessionUrl}
<a href={sessionUrl}>{sessionUrl}</a>
</div>
</div>
<PlayLink newTab isAssist={false} viewed={false} sessionId={sessionId} />

View file

@ -57,9 +57,9 @@ function Steps({ xrayProps }: Props) {
<div className="mt-4 mb-2 text-gray-dark flex items-center gap-4">
STEPS
{timePointer > 0 ? <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} /> : null}
<div id="pdf-ignore">{timePointer > 0 ? <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} /> : null}</div>
</div>
<div className="text-blue cursor-pointer" onClick={handleStepsSelection}>
<div className="text-blue cursor-pointer" id="pdf-ignore" onClick={handleStepsSelection}>
{!shouldShowEventReset ? (
<span>Add {timePointer > 0 ? '' : 'All'} Steps</span>
) : (

View file

@ -78,7 +78,7 @@ function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, set
) : null}
</div>
{!shouldShowPointerReset ? (
<div className="flex items-center gap-2 px-2 py-1 rounded bg-light-blue-bg">
<div className="flex items-center gap-2 px-2 py-1 rounded bg-light-blue-bg" id="pdf-ignore">
<Icon name="info-circle" size={16} />
<div>
Click anywhere on <span className="font-semibold">X-RAY</span> to drilldown and add
@ -86,7 +86,7 @@ function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, set
</div>
</div>
) : (
<div className="text-blue py-1 cursor-pointer" onClick={clearEventSelection}>
<div className="text-blue py-1 cursor-pointer" onClick={clearEventSelection} id="pdf-ignore">
Clear Selection
</div>
)}
@ -95,11 +95,12 @@ function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, set
className="relative cursor-pointer"
onClick={pickEventRadius}
ref={xrayContainer}
style={{ background: timePointer > 0 ? 'rgba(57, 78, 255, 0.07)' : undefined }}
>
<div id="pdf-ignore" style={{ pointerEvents: 'none', background: timePointer > 0 ? 'rgb(57, 78, 255)' : undefined, opacity: '0.07', position: 'absolute', top:0, left:0, width:'100%', height: '100%' }} />
{timePointer > 0 ? (
<div
className="absolute h-full bg-white"
// id="pdf-ignore"
style={{
zIndex: INDEXES.BUG_REPORT_PICKER,
width: 41,
@ -111,15 +112,16 @@ function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, set
style={{
height: '100%',
width: 0,
border: '1px dashed rgba(0,0,0, 0.5)',
borderLeft: '2px dashed rgba(0,0,0, 0.5)',
left: 20,
position: 'absolute',
zIndex: INDEXES.BUG_REPORT + 1,
}}
/>
</div>
) : null}
{Object.keys(resources).map((feature) => (
<div key={feature} className="border-b-2 last:border-none z-20">
<div key={feature} className="border-b-2 last:border-none relative z-20">
<EventRow
title={feature}
// @ts-ignore

View file

@ -0,0 +1,3 @@
body {
line-height: 0.5!important;
}

View file

@ -58,7 +58,9 @@ const TimelinePointer = React.memo((props: Props) => {
position="top"
>
<div onClick={createEventClickHandler(item, NETWORK)} className="cursor-pointer">
<div className="h-4 w-4 rounded-full bg-red" />
<div className="h-4 w-4 rounded-full bg-red text-white font-bold flex items-center justify-center text-sm">
<span>!</span>
</div>
</div>
</Popup>
);
@ -138,12 +140,9 @@ const TimelinePointer = React.memo((props: Props) => {
position="top"
>
<div onClick={createEventClickHandler(item, 'ERRORS')} className="cursor-pointer">
<Icon
className="rounded-full bg-white"
name="funnel/exclamation-circle-fill"
color="red"
size="16"
/>
<div className="h-4 w-4 rounded-full bg-red text-white font-bold flex items-center justify-center text-sm">
<span>!</span>
</div>
</div>
</Popup>
);

View file

@ -15,4 +15,10 @@ input.no-focus:focus {
.widget-wrapper {
@apply rounded border bg-white;
}
}
@layer base {
img {
@apply inline-block;
}
}

View file

@ -270,10 +270,10 @@ export const positionOfTheNumber = (min, max, value, length) => {
};
export const convertElementToImage = async (el) => {
const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el);
const image = await htmlToImage.toJpeg(el, {
// const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el);
const image = await htmlToImage.toPng(el, {
pixelRatio: 2,
fontEmbedCss,
// fontEmbedCss,
filter: function (node) {
return node.id !== 'no-print';
},

View file

@ -25,6 +25,7 @@
"copy-to-clipboard": "^3.3.1",
"deep-diff": "^1.0.2",
"html-to-image": "^1.9.0",
"html2canvas": "^1.4.1",
"immutable": "^4.0.0-rc.12",
"jsbi": "^4.1.0",
"jshint": "^2.11.1",

View file

@ -113,3 +113,4 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
| clickhouse | Apache2 | Infrastructure |
| redis | BSD3 | Infrastructure |
| yq | MIT | Infrastructure |
| html2canvas | MIT | JavaScript |