change(ui): add pdf gen
This commit is contained in:
parent
7e8a106edf
commit
f2c9250cc8
12 changed files with 172 additions and 50 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
line-height: 0.5!important;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,4 +15,10 @@ input.no-focus:focus {
|
|||
|
||||
.widget-wrapper {
|
||||
@apply rounded border bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
img {
|
||||
@apply inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue