diff --git a/frontend/app/components/Session_/BugReport/BugReportModal.tsx b/frontend/app/components/Session_/BugReport/BugReportModal.tsx deleted file mode 100644 index 62a0f5ac5..000000000 --- a/frontend/app/components/Session_/BugReport/BugReportModal.tsx +++ /dev/null @@ -1,224 +0,0 @@ -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, Activity } 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 { fetchList as fetchMembers } from 'Duck/member'; - -interface Props { - hideModal: () => void; - session: Record; - account: Record; - width: number; - height: number; - xrayProps: { - currentLocation: Record[]; - resourceList: Record[]; - exceptionsList: Record[]; - eventsList: Record[]; - endTime: number; - }; - fetchMembers: () => void - members: any; -} - -function BugReportModal({ hideModal, session, width, height, account, xrayProps, fetchMembers, members }: Props) { - const reportRef = React.createRef(); - const [isRendering, setRendering] = React.useState(false); - - const { bugReportStore } = useStore(); - const { - userBrowser, - userDevice, - userCountry, - userBrowserVersion, - userOs, - userOsVersion, - userDisplayName, - userDeviceType, - revId, - metadata, - sessionId, - events, - notes, - } = session; - - const envObject: EnvData = { - Device: `${userDevice}${userDeviceType !== userDevice ? ` ${userDeviceType}` : ''}`, - Resolution: `${width}x${height}`, - Browser: `${userBrowser} v${userBrowserVersion}`, - OS: `${userOs} v${userOsVersion}`, - // @ts-ignore - Country: countries[userCountry], - }; - if (revId) { - Object.assign(envObject, { Rev: revId }); - } - - const sessionUrl = `${window.location.origin}/${ - window.location.pathname.split('/')[1] - }${sessionRoute(sessionId)}`; - - const defaults: ReportDefaults = { - author: account.name, - env: envObject, - meta: metadata, - session: { - user: userDisplayName, - id: sessionId, - url: sessionUrl, - }, - }; - - React.useEffect(() => { - fetchMembers() - bugReportStore.updateReportDefaults(defaults); - bugReportStore.setDefaultSteps(mapEvents(events)); - }, []); - - const onClose = () => { - hideModal(); - 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'); - - let imgWidth = 200; - let pageHeight = 295; - let imgHeight = (canvas.height * imgWidth) / canvas.width; - let heightLeft = imgHeight - pageHeight; - let position = 0; - - - doc.addImage(imgData, 'PNG', 5, 5, imgWidth, imgHeight); - - doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, 2, 45, 5); - if (position === 0 && heightLeft === 0) doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, pageHeight - 5, 45, 5); - - while (heightLeft >= 0) { - position = heightLeft - imgHeight; - doc.addPage(); - doc.addImage(imgData, 'PNG', 5, position, imgWidth, imgHeight); - doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, pageHeight - 5, 45, 5); - 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(); - - const activity = { - network: xrayProps.resourceList, - console: xrayProps.exceptionsList, - clickRage: xrayProps.eventsList.filter((item: any) => item.type === 'CLICKRAGE'), - } - bugReportStore.composeReport(activity as unknown as Activity) - }); - }); - }; - - return ( -
-
- - <MetaInfo envObject={envObject} metadata={metadata} /> - <Steps xrayProps={xrayProps} notes={notes} members={members} /> - <Comments /> - <Session user={userDisplayName} 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={onClose}> - 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> - ); -} - -const WithUIState = connect((state) => ({ - // @ts-ignore - session: state.getIn(['sessions', 'current']), - // @ts-ignore - account: state.getIn(['user', 'account']), - // @ts-ignore - members: state.getIn(['members', 'list']), -}), { fetchMembers })(BugReportModal); - -export default WithUIState; diff --git a/frontend/app/components/Session_/BugReport/components/Comments.tsx b/frontend/app/components/Session_/BugReport/components/Comments.tsx deleted file mode 100644 index 1c50d1d68..000000000 --- a/frontend/app/components/Session_/BugReport/components/Comments.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { useStore } from 'App/mstore'; -import { observer } from 'mobx-react-lite'; -import cn from 'classnames'; -import SectionTitle from './SectionTitle'; - -function Comments() { - const { bugReportStore } = useStore(); - const inputRef = React.createRef<HTMLTextAreaElement>(); - - const toggleEdit = () => { - bugReportStore.toggleCommentEditing(true); - }; - - React.useEffect(() => { - if (inputRef.current && bugReportStore.isCommentEdit) { - inputRef.current?.focus(); - } - }, [bugReportStore.isCommentEdit]); - - const commentsEnabled = bugReportStore.comment.length > 0; - const commentStr = commentsEnabled - ? bugReportStore.comment - : 'Expected results, additional steps or any other useful information for debugging.'; - - return ( - <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> - </div> - {bugReportStore.isCommentEdit ? ( - <textarea - ref={inputRef} - name="reportComments" - placeholder="Comment..." - rows={3} - autoFocus - className="text-area fluid border -mx-2 px-2 py-1 w-full -mt-2" - value={bugReportStore.comment} - onChange={(e) => bugReportStore.setComment(e.target.value)} - onBlur={() => bugReportStore.toggleCommentEditing(false)} - onFocus={() => bugReportStore.toggleCommentEditing(true)} - /> - ) : ( - <div - onClick={toggleEdit} - className={cn( - !commentsEnabled - ? 'text-disabled-text border-dotted border-gray-medium' - : 'border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium', - 'pt-1 w-fit -mt-2', - 'cursor-pointer select-none border-b' - )} - > - {commentStr} - </div> - )} - </div> - ); -} - -export default observer(Comments); diff --git a/frontend/app/components/Session_/BugReport/components/MetaInfo.tsx b/frontend/app/components/Session_/BugReport/components/MetaInfo.tsx deleted file mode 100644 index 2eecf4ea7..000000000 --- a/frontend/app/components/Session_/BugReport/components/MetaInfo.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import SectionTitle from './SectionTitle'; - -interface EnvObj { - Device: string; - Resolution: string; - Browser: string; - OS: string; - Country: string; - Rev?: string; -} - -export default function MetaInfo({ - envObject, - metadata, -}: { - envObject: EnvObj; - metadata: Record<string, any>; -}) { - return ( - <div className="flex gap-8"> - <div className="flex flex-col gap-2"> - <SectionTitle>Environment</SectionTitle> - {Object.keys(envObject).map((envTag) => ( - <div key={envTag} className="flex items-center"> - <div className="py-1 px-2 font-medium">{envTag}</div> - <div className="rounded text-base bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip"> - {/* @ts-ignore */} - {envObject[envTag]} - </div> - </div> - ))} - </div> - - {Object.keys(metadata).length > 0 ? ( - <div className="flex flex-col gap-2"> - <SectionTitle>Metadata</SectionTitle> - {Object.keys(metadata).map((meta) => ( - <div key={meta} className="flex items-center rounded overflow-hidden bg-gray-lightest"> - <div className="bg-gray-light-shade py-1 px-2">{meta}</div> - <div className="py-1 px-2 text-gray-medium">{metadata[meta]}</div> - </div> - ))} - </div> - ) : null} - </div> - ); -} diff --git a/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx b/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx deleted file mode 100644 index 0330cdb80..000000000 --- a/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { useStore } from 'App/mstore'; -import { observer } from 'mobx-react-lite'; -import cn from 'classnames'; -import { Tooltip } from 'UI'; - -function ReportTitle() { - const { bugReportStore } = useStore(); - const inputRef = React.createRef<HTMLInputElement>(); - - const toggleEdit = () => { - bugReportStore.toggleTitleEdit(true); - }; - - React.useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (bugReportStore.isTitleEdit && e.key === 'Enter') { - inputRef.current?.blur(); - bugReportStore.toggleTitleEdit(false); - } - } - - document.addEventListener('keydown', handler, false) - - return () => document.removeEventListener('keydown', handler) - }) - - React.useEffect(() => { - if (inputRef.current && bugReportStore.isTitleEdit) { - inputRef.current?.focus(); - } - }, [bugReportStore.isTitleEdit]) - - return ( - <div> - {bugReportStore.isTitleEdit ? ( - <input - ref={inputRef} - name="reportTitle" - className="rounded fluid border-0 -mx-2 px-2 h-8 text-2xl" - value={bugReportStore.reportTitle} - onChange={(e) => bugReportStore.setTitle(e.target.value)} - onBlur={() => bugReportStore.toggleTitleEdit(false)} - onFocus={() => bugReportStore.toggleTitleEdit(true)} - /> - ) : ( - // @ts-ignore - <Tooltip delay={200} title="Double click to edit"> - <div - onDoubleClick={toggleEdit} - className={cn( - 'color-teal text-2xl h-8 flex items-center border-transparent', - 'cursor-pointer select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium' - )} - > - {bugReportStore.reportTitle} - </div> - </Tooltip> - )} - </div> - ); -} - -export default observer(ReportTitle); diff --git a/frontend/app/components/Session_/BugReport/components/SectionTitle.tsx b/frontend/app/components/Session_/BugReport/components/SectionTitle.tsx deleted file mode 100644 index 60306ca88..000000000 --- a/frontend/app/components/Session_/BugReport/components/SectionTitle.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -export default function SectionTitle({ children }: { children: React.ReactNode }) { - return <div className="text-xl font-semibold mb-2">{children}</div>; -} diff --git a/frontend/app/components/Session_/BugReport/components/Session.tsx b/frontend/app/components/Session_/BugReport/components/Session.tsx deleted file mode 100644 index 11dfffbfb..000000000 --- a/frontend/app/components/Session_/BugReport/components/Session.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import SectionTitle from './SectionTitle'; -import { Icon, Tooltip } from 'UI' - -export default function Session({ user, sessionUrl }: { user: string, sessionUrl: string }) { - - const onSessionClick = () => { - window.open(sessionUrl, '_blank').focus(); - } - - return ( - <div> - <SectionTitle>Session recording</SectionTitle> - {/* @ts-ignore */} - <Tooltip title="Play session in new tab"> - <div className="border hover:border-main hover:bg-active-blue cursor-pointer rounded flex items-center justify-between p-2" onClick={onSessionClick}> - <div className="flex flex-col"> - <div className="text-lg">{user}</div> - <div className="text-disabled-text"> - {sessionUrl} - </div> - </div> - <Icon name="play-fill" size={38} color="teal" /> - </div> - </Tooltip> - </div> - ); -} diff --git a/frontend/app/components/Session_/BugReport/components/Steps.tsx b/frontend/app/components/Session_/BugReport/components/Steps.tsx deleted file mode 100644 index 2f1517d24..000000000 --- a/frontend/app/components/Session_/BugReport/components/Steps.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { Button } from 'UI' -import { useStore } from 'App/mstore'; -import { observer } from 'mobx-react-lite'; -import { RADIUS } from '../utils'; -import SectionTitle from './SectionTitle'; -import XRay from './StepsComponents/XRay'; -import StepRenderer from './StepsComponents/StepRenderer'; -import StepRadius from './StepsComponents/StepRadius'; -import SubModal from './StepsComponents/SubModal'; -import { Note } from 'App/services/NotesService'; - -interface Props { - xrayProps: { - currentLocation: Record<string, any>[]; - resourceList: Record<string, any>[]; - exceptionsList: Record<string, any>[]; - eventsList: Record<string, any>[]; - endTime: number; - }; - notes: Note[]; - members: Record<string, any>[]; -} - -function Steps({ xrayProps, notes, members }: Props) { - const { bugReportStore } = useStore(); - const [stepPickRadius, setRadius] = React.useState(RADIUS); - const [timePointer, setPointer] = React.useState(0); - - const shouldShowEventReset = bugReportStore.chosenEventSteps.length > 0; - - const handleStepsSelection = () => { - if (shouldShowEventReset) { - return clearEventSelection(); - } - if (timePointer > 0) { - // temp ? - return bugReportStore.setSteps(bugReportStore.sessionEventSteps); - } else { - bugReportStore.setSteps(bugReportStore.sessionEventSteps); - } - }; - - const clearEventSelection = () => { - setPointer(0); - bugReportStore.resetSteps(); - }; - - React.useEffect(() => { - if (bugReportStore.sessionEventSteps.length < RADIUS && bugReportStore.sessionEventSteps.length > 0) { - setRadius(bugReportStore.sessionEventSteps.length); - } - }, [bugReportStore.sessionEventSteps]) - - return ( - <div> - <SectionTitle>Steps to reproduce</SectionTitle> - - <XRay - xrayProps={xrayProps} - timePointer={timePointer} - clearEventSelection={clearEventSelection} - setPointer={setPointer} - stepPickRadius={stepPickRadius} - /> - - <div className="flex items-center justify-between"> - <div className="mt-4 mb-2 text-gray-dark flex items-center gap-4"> - STEPS - <div id="pdf-ignore"> - {timePointer > 0 ? ( - <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} stepsNum={bugReportStore.sessionEventSteps.length}/> - ) : null} - </div> - </div> - <Button id="pdf-ignore" variant="text-primary" onClick={handleStepsSelection}> - {!shouldShowEventReset ? ( - <span>Add {timePointer > 0 ? '' : 'All'} Steps</span> - ) : ( - <span>Reset</span> - )} - </Button> - </div> - <StepRenderer - isDefault={bugReportStore.chosenEventSteps.length === 0} - steps={ - bugReportStore.chosenEventSteps.length === 0 - ? bugReportStore.sessionEventSteps - : bugReportStore.chosenEventSteps - } - /> - {bugReportStore.isSubStepModalOpen ? ( - <SubModal - members={members} - type={bugReportStore.subModalType} - notes={notes} - xrayProps={xrayProps} - /> - ) : null} - </div> - ); -} - -export default observer(Steps); diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx deleted file mode 100644 index f28771c47..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react'; -import { Icon, ItemMenu, Tooltip } from 'UI'; -import { observer } from 'mobx-react-lite'; -import { Step as IStep } from '../../types'; -const STEP_NAMES = { CLICKRAGE: 'Multiple click', CLICK: 'Clicked', LOCATION: 'Visited' }; -import { useStore } from 'App/mstore'; -import cn from 'classnames'; -import { ErrorComp, NetworkComp, NoteComp } from './SubModalItems'; -import { durationFromMs } from 'App/date'; - -const SUBSTEP = { - network: NetworkComp, - note: NoteComp, - error: ErrorComp, -}; - -function Step({ step, ind, isDefault }: { step: IStep; ind: number; isDefault?: boolean }) { - const { bugReportStore } = useStore(); - const [menuOpen, setMenu] = React.useState(false); - - const menuItems = [ - { - icon: 'quotes', - text: 'Notes', - onClick: () => bugReportStore.toggleSubStepModal(true, 'note', step.key), - }, - { - icon: 'info-circle', - text: `Errors`, - onClick: () => bugReportStore.toggleSubStepModal(true, 'error', step.key), - }, - { - icon: 'network', - text: 'Bad Network Requests', - onClick: () => bugReportStore.toggleSubStepModal(true, 'network', step.key), - }, - ]; - - return ( - <div className="flex flex-col w-full"> - <div - className={cn( - 'py-1 px-2 flex items-start gap-2 w-full rounded', - menuOpen - ? 'bg-figmaColors-secondary-outlined-hover-background' - : isDefault - ? '' - : 'hover:bg-figmaColors-secondary-outlined-hover-background group' - )} - > - <div className="rounded-3xl px-4 bg-gray-lightest relative z-10">{ind + 1}</div> - <div className="w-full"> - <div className="flex items-start w-full gap-2"> - <div className="px-1 text-disabled-text">{durationFromMs(step.time)}</div> - {/* @ts-ignore */} - <Icon name={step.icon} size={16} color="gray-darkest" className="relative z-10" /> - {/* @ts-ignore */} - <div className="font-semibold">{STEP_NAMES[step.type]}</div> - <div className="text-gray-medium">{step.details}</div> - <div - className={cn( - 'group-hover:flex items-center ml-auto gap-4', - menuOpen ? 'flex' : 'hidden' - )} - > - {/* @ts-ignore */} - <Tooltip title="Add Note, Error or bad Network Request" className="!flex items-center"> - <ItemMenu - label={ - <Icon - name="plus" - size={16} - className="cursor-pointer hover:fill-gray-darkest" - /> - } - items={menuItems} - flat - onToggle={(isOpen) => setMenu(isOpen)} - /> - </Tooltip> - {/* @ts-ignore */} - <Tooltip title="Delete Step" className="whitespace-nowrap"> - <div onClick={() => bugReportStore.removeStep(step)}> - <Icon name="trash" size={16} className="cursor-pointer hover:fill-gray-darkest" /> - </div> - </Tooltip> - </div> - </div> - {step.substeps?.length ? ( - <div className="flex flex-col gap-2 w-full mt-2 relative"> - {step.substeps.map((subStep) => { - const Component = SUBSTEP[subStep.type]; - return ( - <div className="relative" key={subStep.key}> - <div - className="rounded border py-1 px-2 w-full flex flex-col relative z-10" - style={{ background: subStep.type === 'note' ? '#FFFEF5' : 'white' }} - > - {/* @ts-ignore */} - <Component item={subStep} /> - </div> - <div - style={{ - borderBottom: '1px solid #DDDDDD', - borderLeft: '1px solid #DDDDDD', - borderBottomLeftRadius: 6, - position: 'absolute', - zIndex: 1, - left: -25, - bottom: 10, - height: '120%', - width: 50, - }} - /> - </div> - ); - })} - </div> - ) : null} - </div> - </div> - </div> - ); -} - -export default observer(Step); diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx deleted file mode 100644 index afa8b15c7..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Tooltip } from 'UI' - -interface Props { - pickRadius: number; - setRadius: (v: number) => void; - stepsNum: number; -} - -function StepRadius({ pickRadius, setRadius, stepsNum }: Props) { - return ( - <div className="w-full flex items-center gap-4"> - <div className="border-b border-dotted border-gray-medium cursor-help"> - {/* @ts-ignore */} - <Tooltip title={<span>Closest step to the selected timestamp ± {pickRadius}.</span>}> - <span>± {pickRadius}</span> - </Tooltip> - </div> - <div className="flex items-center gap-1"> - <div - className="rounded px-2 bg-light-blue-bg cursor-pointer hover:bg-teal-light" - onClick={() => pickRadius < Math.floor(stepsNum/2) ? setRadius(pickRadius + 1) : null} - > - +1 - </div> - <div - className="rounded px-2 bg-light-blue-bg cursor-pointer hover:bg-teal-light" - onClick={() => (pickRadius > 1 ? setRadius(pickRadius - 1) : null)} - > - -1 - </div> - </div> - </div> - ); -} - -export default StepRadius; diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRenderer.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRenderer.tsx deleted file mode 100644 index 569a794d0..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRenderer.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import Step from './EventStep'; -import { Step as IStep } from '../../types'; - -function StepRenderer(props: { steps: IStep[]; isDefault: boolean }) { - const stepAmount = props.steps.length; - const shouldSkip = stepAmount > 2; - if (props.isDefault && shouldSkip) { - return ( - <div className="flex flex-col gap-4 opacity-50"> - <Step step={props.steps[0]} ind={1} isDefault /> - <div className="ml-4"> + {stepAmount - 2} Steps</div> - <Step step={props.steps[stepAmount - 1]} ind={stepAmount} isDefault /> - </div> - ); - } - return ( - <div className="flex flex-col gap-4"> - {props.steps.map((step, ind) => ( - <React.Fragment key={step.key}> - <Step step={step} ind={ind} /> - </React.Fragment> - ))} - </div> - ); -} - -export default StepRenderer diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx deleted file mode 100644 index f557216bf..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react'; -import { Icon, Button } from 'UI'; -import { observer } from 'mobx-react-lite'; -import { Note } from 'App/services/NotesService'; -import { NoteItem, ErrorItem, NetworkReq, SubItem } from './SubModalItems'; -import { filterList, debounce } from 'App/utils'; -import { useStore } from 'App/mstore'; - -const Titles = { - note: 'Notes', - network: 'Fetch/XHR Errors', - error: 'Console Errors', -}; -const Icons = { - note: 'quotes' as const, - network: 'network' as const, - error: 'info-circle' as const -} -const Filters = { - note: 'note message or author', - network: 'url', - error: 'error name or message', -}; - -interface Props { - type: 'note' | 'network' | 'error'; - items: SubItem[]; -} - -let debounceUpdate: any = () => {}; - -const SUB_ITEMS = { - note: NoteItem, - error: ErrorItem, - network: NetworkReq, -}; - -function ModalContent(props: Props) { - const [searchStr, setSearch] = React.useState(''); - const list = - searchStr !== '' - ? filterList(props.items, searchStr, ['url', 'name', 'title', 'message']) - : props.items; - - React.useEffect(() => { - debounceUpdate = debounce((val: string) => setSearch(val), 250); - }, []); - - const SubItem = SUB_ITEMS[props.type]; - - return ( - <div className="flex flex-col p-4 bg-white gap-4 w-full"> - <div className="flex items-center gap-2"> - <div className="p-2 rounded-full bg-light-blue-bg"> - <Icon name={Icons[props.type]} size={18} /> - </div> - <div className="text-2xl font-semibold">{`Select ${Titles[props.type]}`}</div> - <div className="ml-auto"> - <input - onChange={(e) => debounceUpdate(e.target.value)} - className="bg-white p-2 border border-borderColor-gray-light-shade rounded" - placeholder={`Filter by ${Filters[props.type]}`} - style={{ width: 250 }} - /> - </div> - </div> - <div - className="flex flex-col rounded -mx-4 px-4 py-2 bg-white" - style={{ height: 'calc(100vh - 130px)', overflowY: 'scroll', maxWidth: '70vw', width: 620 }} - > - {list.length > 0 ? ( - list.map((item) => ( - <React.Fragment key={item.key}> - {/* @ts-ignore */} - <SubItem item={item} /> - </React.Fragment> - )) - ) : ( - <div className="text-2xl font-semibold text-center">No items to show.</div> - )} - </div> - - <ModalActionsObs /> - </div> - ); -} - -function ModalActions() { - const { bugReportStore } = useStore(); - - const removeModal = () => { - bugReportStore.toggleSubStepModal(false, bugReportStore.subModalType, undefined); - }; - const saveChoice = () => { - bugReportStore.saveSubItems(); - removeModal(); - }; - return ( - <div className="flex items-center gap-2"> - <Button - disabled={bugReportStore.pickedSubItems[bugReportStore.targetStep].size === 0} - variant="primary" - onClick={saveChoice} - > - Add Selected - </Button> - <Button variant="text-primary" onClick={removeModal}> - Cancel - </Button> - </div> - ); -} - -const ModalActionsObs = observer(ModalActions); - -interface ModalProps { - xrayProps: { - currentLocation: Record<string, any>[]; - resourceList: Record<string, any>[]; - exceptionsList: Record<string, any>[]; - eventsList: Record<string, any>[]; - endTime: number; - }; - type: 'note' | 'network' | 'error'; - notes: Note[]; - members: Record<string, any>[]; -} - -function SubModal(props: ModalProps) { - let items; - if (props.type === 'note') { - items = props.notes.map((note) => ({ - type: 'note' as const, - title: props.members.find((m) => m.id === note.userId)?.email || note.userId, - message: note.message, - time: 0, - key: note.noteId as unknown as string, - })); - } - if (props.type === 'error') { - items = props.xrayProps.exceptionsList.map((error) => ({ - type: 'error' as const, - time: error.time, - message: error.message, - name: error.name, - key: error.key, - })); - } - if (props.type === 'network') { - items = props.xrayProps.resourceList.map((fetch) => ({ - type: 'network' as const, - time: fetch.time, - url: fetch.url, - status: fetch.status, - success: fetch.success, - message: fetch.name, - key: fetch.key, - })); - } - - return ( - <div - className="bg-white fixed" - style={{ - maxWidth: '70vw', - overflow: 'hidden', - width: 620, - height: '100vh', - top: 0, - right: 0, - zIndex: 999, - }} - > - {/* @ts-ignore */} - <ModalContent type={props.type} items={items} /> - </div> - ); -} - -export default SubModal; diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx deleted file mode 100644 index bf68d0144..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react'; -import { Checkbox } from 'UI'; -import { observer } from 'mobx-react-lite'; -import { useStore } from 'App/mstore'; -import { durationFromMs } from 'App/date' -import cn from 'classnames' - -interface Item { - time: number; - message: string; - type: 'note' | 'network' | 'error'; - key: string; -} - -export interface INoteItem extends Item { - title: string; -} - -export interface IError extends Item { - name?: string; -} - -export interface INetworkReq extends Item { - url: string; - status: string; - success: boolean; -} - -export type SubItem = INoteItem | IError | INetworkReq; - -const safeStr = (ogStr: string) => { - if (!ogStr) return '' - return (ogStr.length > 80 ? ogStr.slice(0, 80) + '...' : ogStr) -} - -export const NetworkComp = ({ item }: { item: INetworkReq }) => ( - <div className="flex items-start flex-col z-10"> - <div className="flex items-center gap-2 text-disabled-text"> - <div>{durationFromMs(item.time)}</div> - <div>{safeStr(item.url)}</div> - </div> - <div className="flex items-center gap-2"> - <div className="rounded bg-active-blue px-2 whitespace-nowrap overflow-hidden text-clip font-mono">{item.status}</div> - <div className={item.success ? '' : 'text-red'}>{safeStr(item.message)}</div> - </div> - </div> -); - -export const NetworkReq = observer(({ item }: { item: INetworkReq }) => { - const { bugReportStore } = useStore(); - return ( - <SubModalItemContainer - isChecked={bugReportStore.isSubItemChecked(item)} - onChange={(isChecked) => bugReportStore.toggleSubItem(isChecked, item)} - > - <NetworkComp item={item} /> - </SubModalItemContainer> - ); -}); - -export const NoteComp = ({ item }: { item: INoteItem }) => ( - <div className="flex items-start flex-col z-10"> - <div>{item.message}</div> - <div className="text-disabled-text text-sm">{item.title}</div> - </div> -); - -export const NoteItem = observer(({ item }: { item: INoteItem }) => { - const { bugReportStore } = useStore(); - return ( - <SubModalItemContainer - isChecked={bugReportStore.isSubItemChecked(item)} - onChange={(isChecked) => bugReportStore.toggleSubItem(isChecked, item)} - isNote - > - <NoteComp item={item} /> - </SubModalItemContainer> - ); -}); - -export const ErrorComp = ({ item }: { item: IError }) => ( - <div className="flex items-start flex-col z-10"> - <div className="text-disabled-text">{durationFromMs(item.time)}</div> - {item.name ? <div className="text-red">{item.name}</div> : null} - <div className="text-secondary">{safeStr(item.message)}</div> - </div> -); - -export const ErrorItem = observer(({ item }: { item: IError }) => { - const { bugReportStore } = useStore(); - return ( - <SubModalItemContainer - isChecked={bugReportStore.isSubItemChecked(item)} - onChange={(isChecked) => bugReportStore.toggleSubItem(isChecked, item)} - > - <ErrorComp item={item} /> - </SubModalItemContainer> - ); -}); - -export function SubModalItemContainer({ - children, - isChecked, - onChange, - isNote, -}: { - children: React.ReactNode; - isChecked: boolean; - onChange: (arg: any) => void; - isNote?: boolean; -}) { - return ( - <div - className={cn("flex items-start p-2 gap-2 border-t last:border-b cursor-pointer", isNote ? 'note-hover-bg' : 'hover:bg-active-blue')} - // style={{ background: isNote ? '#FFFEF5' : undefined }} - onClick={() => onChange(!isChecked)} - > - <Checkbox - name="isIncluded" - type="checkbox" - checked={isChecked} - onChange={(e: any) => onChange(e.target.checked)} - className="pt-1" - /> - {children} - </div> - ); -} diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/XRay.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/XRay.tsx deleted file mode 100644 index 61c2fb9da..000000000 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/XRay.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React from 'react'; -import { Duration } from 'luxon'; -import { observer } from 'mobx-react-lite'; -import { Icon, Button } from 'UI'; -import { useStore } from 'App/mstore'; -import { INDEXES } from 'App/constants/zindex'; -import TimelinePointer from 'App/components/Session_/OverviewPanel/components/TimelinePointer'; -import EventRow from 'App/components/Session_/OverviewPanel/components/EventRow'; -import { selectEventSteps } from '../../utils'; - -interface IXRay { - xrayProps: { - currentLocation: Record<string, any>[]; - resourceList: Record<string, any>[]; - exceptionsList: Record<string, any>[]; - eventsList: Record<string, any>[]; - endTime: number; - }; - timePointer: number; - stepPickRadius: number; - clearEventSelection: () => void; - setPointer: (time: number) => void; -} - -function XRay({ xrayProps, timePointer, stepPickRadius, clearEventSelection, setPointer }: IXRay) { - const [selectedTime, setTime] = React.useState(0); - const xrayContainer = React.useRef<HTMLDivElement>(); - const { bugReportStore } = useStore(); - - const { resourceList, exceptionsList, eventsList, endTime } = xrayProps; - - const resources = { - NETWORK: resourceList, - ERRORS: exceptionsList, - CLICKRAGE: eventsList.filter((item: any) => item.type === 'CLICKRAGE'), - }; - - const pickEventRadius = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { - e.stopPropagation(); - - const pos = e.clientX - xrayContainer.current?.getBoundingClientRect().left; - const percent = pos / xrayContainer.current?.getBoundingClientRect().width; - const targetTime = percent * endTime; - const selectedSteps = selectEventSteps( - bugReportStore.sessionEventSteps, - targetTime, - stepPickRadius - ); - - setTime(targetTime); - setPointer(e.clientX - xrayContainer.current?.getBoundingClientRect().left); - bugReportStore.setSteps(selectedSteps); - }; - - React.useEffect(() => { - if (timePointer > 0 && selectedTime > 0 && bugReportStore.chosenEventSteps) { - const selectedSteps = selectEventSteps( - bugReportStore.sessionEventSteps, - selectedTime, - stepPickRadius - ); - - bugReportStore.setSteps(selectedSteps); - } - }, [stepPickRadius]); - - const shouldShowPointerReset = timePointer > 0; - - return ( - <> - <div className="flex items-center justify-between my-2"> - <div className=" text-gray-dark py-2"> - X-RAY - {timePointer > 0 ? ( - <span className="text-disabled-text ml-2"> - {Duration.fromMillis(selectedTime).toFormat('hh:mm:ss')} - </span> - ) : null} - </div> - {!shouldShowPointerReset ? ( - <div - className="flex items-center gap-2 rounded bg-active-blue px-2 py-1 whitespace-nowrap overflow-hidden text-clip group" - id="pdf-ignore" - > - <Icon name="info-circle" size={16} /> - <div>Click anywhere in the graph below to drilldown and add steps</div> - </div> - ) : ( - <Button id="pdf-ignore" variant="text-primary" onClick={clearEventSelection}> - Clear Selection - </Button> - )} - </div> - <div - className="relative cursor-pointer group-hover:border-dotted hover:border-dotted group-hover:border-gray-dark hover:border-gray-dark border border-transparent" - onClick={pickEventRadius} - ref={xrayContainer} - > - <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, - left: timePointer - 20, - pointerEvents: 'none', - }} - > - <div - style={{ - height: '100%', - width: 0, - 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 relative z-20"> - <EventRow - title={feature} - // @ts-ignore - list={resources[feature]} - zIndex={INDEXES.BUG_REPORT} - noMargin - renderElement={(pointer: any) => ( - <TimelinePointer noClick pointer={pointer} type={feature} /> - )} - endTime={endTime} - /> - </div> - ))} - </div> - </> - ); -} - -export default observer(XRay); diff --git a/frontend/app/components/Session_/BugReport/components/Title.tsx b/frontend/app/components/Session_/BugReport/components/Title.tsx deleted file mode 100644 index b0ea26caf..000000000 --- a/frontend/app/components/Session_/BugReport/components/Title.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import Select from 'Shared/Select'; -import { Icon } from 'UI'; -import ReportTitle from './ReportTitle'; -import { useStore } from 'App/mstore'; -import { observer } from 'mobx-react-lite'; -import { SeverityLevels } from 'App/mstore/bugReportStore'; - -const selectOptions = [ - { - label: ( - <div className="flex items-center gap-1 cursor-pointer w-full"> - <Icon name="arrow-up-short" color="red" size="24" /> - HIGH - </div> - ), - value: SeverityLevels.High, - }, - { - label: ( - <div className="flex items-center gap-1 cursor-pointer w-full"> - <Icon name="dash" size="24" color="yellow2" /> - MEDIUM - </div> - ), - value: SeverityLevels.Medium, - }, - { - label: ( - <div className="flex items-center gap-1 cursor-pointer w-full"> - <Icon name="arrow-down-short" color="teal" size="24" /> - LOW - </div> - ), - value: SeverityLevels.Low, - }, -]; - -function Title({ userName }: { userName: string }) { - const { bugReportStore } = useStore(); - - return ( - <div className="flex items-center py-2 px-3 justify-between bg-gray-lightest rounded"> - <div className="flex flex-col gap-2"> - <ReportTitle /> - <div className="text-gray-medium">By {userName}</div> - </div> - <div className="flex items-center gap-2"> - <div className="font-semibold">Severity</div> - <Select - plain - controlStyle={{ minWidth: 115 }} - defaultValue={SeverityLevels.High} - options={selectOptions} - onChange={({ value }) => bugReportStore.setSeverity(value.value)} - /> - </div> - </div> - ); -} - -export default observer(Title); diff --git a/frontend/app/components/Session_/BugReport/overload.module.css b/frontend/app/components/Session_/BugReport/overload.module.css deleted file mode 100644 index 4fc49cd3d..000000000 --- a/frontend/app/components/Session_/BugReport/overload.module.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - line-height: 0.5!important; -} diff --git a/frontend/app/components/Session_/BugReport/types.ts b/frontend/app/components/Session_/BugReport/types.ts deleted file mode 100644 index eb2b2a9d7..000000000 --- a/frontend/app/components/Session_/BugReport/types.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { SeverityLevels } from 'App/mstore/bugReportStore'; -import { SubItem } from './components/StepsComponents/SubModalItems'; - -export interface ReportDefaults { - author: string; - env: EnvData; - meta: { - [key: string]: string; - }; - session: { - user: string; - url: string; - id: string; - }; -} - -export interface BugReportPdf extends ReportDefaults { - title: string; - comment?: string; - severity: SeverityLevels; - steps: Step[]; - activity: Activity -} - -export interface Activity { - network: NetworkEvent[]; - console: Exception[]; - clickRage: ClickRage[]; -}; - -interface Event { - time: number; - key: string; -} - -interface NetworkEvent extends Event { - decodedBodySize: number | null; - duration: number | null; - encodedBodySize: number | null; - headerSize: number | null; - index?: number; - method: string; - name: string; - payload: string; - response: string; - responseBodySize: number; - score: number; - status: string; - success: boolean; - timewidth: number; - timings: Record<string, any>; - ttfb: number; - type: string; - url: string; -} - -interface Exception extends Event { - errorId: string; - function: string; - key: string; - message: string; - messageId: number; - name: string; - projectId: number; - sessionId: number; - source: string; - timestamp: number; -} - -interface ClickRage extends Event { - type: 'CLICKRAGE'; - label: string - targetContent: string, - target: { - key: string, - path: string, - label: string | null - }, - count: number -} - - -export interface EnvData { - Browser: string; - OS: string; - Country: string; - Device: string; - Resolution: string; -} - -export interface Step { - key: string; - type: string; - time: number; - details: string; - icon: string; - substeps?: SubItem[]; -} - -export interface Note { - author: string; - message: string; - step: 'note'; -} - -export interface Error { - timestamp: string; - error: string; - step: 'error'; -} - -export interface Request { - url: string; - status: number; - type: 'GET' | 'POST' | 'PUT' | 'DELETE'; - time: number; - name: string; - step: 'request'; -} diff --git a/frontend/app/components/Session_/BugReport/utils.ts b/frontend/app/components/Session_/BugReport/utils.ts deleted file mode 100644 index 053ece364..000000000 --- a/frontend/app/components/Session_/BugReport/utils.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Step } from './types' - -const TYPES = { CLICKRAGE: 'CLICKRAGE', CLICK: 'CLICK', LOCATION: 'LOCATION' } -export const RADIUS = 3 - -export function mapEvents(events: Record<string,any>[]): Step[] { - const steps: Step[] = [] - events.forEach(event => { - if (event.type === TYPES.LOCATION) { - const step = { - key: event.key, - type: TYPES.LOCATION, - icon: 'event/location', - details: event.url, - time: event.time, - } - steps.push(step) - } - if (event.type === TYPES.CLICK) { - const step = { - key: event.key, - type: TYPES.CLICK, - icon: 'puzzle-piece', - details: event.label, - time: event.time, - } - steps.push(step) - } - if (event.type === TYPES.CLICKRAGE) { - const step = { - key: event.key, - type: TYPES.CLICKRAGE, - icon: 'event/clickrage', - details: event.label, - time: event.time, - } - steps.push(step) - } - }) - - return steps -} - -export function getClosestEventStep(time: number, arr: Step[]) { - let mid; - let low = 0; - let high = arr.length - 1; - while (high - low > 1) { - mid = Math.floor ((low + high) / 2); - if (arr[mid].time < time) { - low = mid; - } else { - high = mid; - } - } - if (time - arr[low].time <= arr[high].time - time) { - return { targetStep: arr[low], index: low } ; - } - return { targetStep: arr[high], index: high } ; -} - -export const selectEventSteps = (steps: Step[], targetTime: number, radius: number) => { - const { targetStep, index } = getClosestEventStep(targetTime, steps) - - const stepsBeforeEvent = steps.slice(Math.max(index - radius, 0), index) - const stepsAfterEvent = steps.slice(index + 1, index + 1 + radius) - - return [...stepsBeforeEvent, targetStep, ...stepsAfterEvent] -} diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 3c190cf75..2a8914514 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -7,8 +7,6 @@ import Bookmark from 'Shared/Bookmark'; import SharePopup from '../shared/SharePopup/SharePopup'; import Issues from './Issues/Issues'; import NotePopup from './components/NotePopup'; -import { useModal } from 'App/components/Modal'; -import BugReportModal from './BugReport/BugReportModal'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { connect } from 'react-redux'; @@ -16,7 +14,7 @@ import SessionTabs from 'Components/Session/Player/SharedComponents/SessionTabs' import { IFRAME } from 'App/constants/storageKeys'; import cn from 'classnames'; import { Switch, Button as AntButton, Popover } from 'antd'; -import { BugOutlined, ShareAltOutlined } from '@ant-design/icons'; +import { ShareAltOutlined } from '@ant-design/icons'; const localhostWarn = (project) => project + '_localhost_warn'; const disableDevtools = 'or_devtools_uxt_toggle'; @@ -25,8 +23,8 @@ function SubHeader(props) { const localhostWarnKey = localhostWarn(props.siteId); const defaultLocalhostWarn = localStorage.getItem(localhostWarnKey) !== '1'; const [showWarningModal, setWarning] = React.useState(defaultLocalhostWarn); - const { player, store } = React.useContext(PlayerContext); - const { width, height, endTime, location: currentLocation = 'loading...' } = store.get(); + const { store } = React.useContext(PlayerContext); + const { location: currentLocation = 'loading...' } = store.get(); const hasIframe = localStorage.getItem(IFRAME) === 'true'; const { uxtestingStore } = useStore(); @@ -39,40 +37,11 @@ function SubHeader(props) { return integrations.some((i) => i.token); }, [props.integrations]); - const { showModal, hideModal } = useModal(); - const location = currentLocation && currentLocation.length > 70 ? `${currentLocation.slice(0, 25)}...${currentLocation.slice(-40)}` : currentLocation; - const showReportModal = () => { - const { tabStates, currentTab } = store.get(); - const resourceList = tabStates[currentTab]?.resourceList || []; - const exceptionsList = tabStates[currentTab]?.exceptionsList || []; - const eventsList = tabStates[currentTab]?.eventList || []; - const graphqlList = tabStates[currentTab]?.graphqlList || []; - const fetchList = tabStates[currentTab]?.fetchList || []; - - const mappedResourceList = resourceList - .filter((r) => r.isRed || r.isYellow) - .concat(fetchList.filter((i) => parseInt(i.status) >= 400)) - .concat(graphqlList.filter((i) => parseInt(i.status) >= 400)); - - player.pause(); - const xrayProps = { - currentLocation: currentLocation, - resourceList: mappedResourceList, - exceptionsList: exceptionsList, - eventsList: eventsList, - endTime: endTime, - }; - showModal( - <BugReportModal width={width} height={height} xrayProps={xrayProps} hideModal={hideModal} />, - { right: true, width: 620 } - ); - }; - const showWarning = location && /(localhost)|(127.0.0.1)|(0.0.0.0)/.test(location) && showWarningModal; const closeWarning = () => { @@ -128,15 +97,6 @@ function SubHeader(props) { style={{ width: 'max-content' }} > <KeyboardHelp /> - <Popover content={'Create Bug Report'}> - <AntButton - size={'small'} - className={'flex items-center justify-center'} - onClick={showReportModal} - > - <BugOutlined /> - </AntButton> - </Popover> <Bookmark sessionId={props.sessionId} /> <NotePopup /> {enabledIntegration && <Issues sessionId={props.sessionId} />} diff --git a/frontend/app/mstore/bugReportStore.ts b/frontend/app/mstore/bugReportStore.ts deleted file mode 100644 index abb3f5e84..000000000 --- a/frontend/app/mstore/bugReportStore.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { makeAutoObservable } from 'mobx'; -import { BugReportPdf, ReportDefaults, Step, Activity } from 'Components/Session_/BugReport/types'; -import { SubItem } from 'App/components/Session_/BugReport/components/StepsComponents/SubModalItems'; - -export enum SeverityLevels { - Low, - Medium, - High, -} - -export default class BugReportStore { - reportTitle = 'Untitled Report'; - comment = ''; - severity = SeverityLevels.High; - - isCommentEdit = false; - isTitleEdit = false; - isSubStepModalOpen = false; - - bugReport: Partial<BugReportPdf>; - sessionEventSteps: Step[] = []; - chosenEventSteps: Step[] = []; - subModalType: 'note' | 'network' | 'error'; - targetStep: string - pickedSubItems: Record<string, Map<string, SubItem>> = {} - - constructor() { - makeAutoObservable(this); - } - - clearStore() { - this.reportTitle = 'Untitled Report'; - this.comment = ''; - this.severity = SeverityLevels.High; - - this.isCommentEdit = false; - this.isTitleEdit = false; - - this.bugReport = undefined; - this.sessionEventSteps = []; - this.chosenEventSteps = []; - this.subModalType = undefined; - this.isSubStepModalOpen = false; - this.targetStep = undefined; - this.pickedSubItems = {}; - } - - toggleTitleEdit(isEdit: boolean) { - this.isTitleEdit = isEdit; - } - - setTitle(title: string) { - if (title.length < 40) { - this.reportTitle = title; - this.bugReport = Object.assign(this.bugReport, { title: this.reportTitle }); - } - } - - setSeverity(severity: SeverityLevels) { - this.severity = severity; - - this.bugReport = Object.assign(this.bugReport, { severity: this.severity }); - } - - toggleCommentEditing(isEdit: boolean) { - this.isCommentEdit = isEdit; - } - - setComment(comment: string) { - this.comment = comment; - - this.bugReport = Object.assign(this.bugReport, { - comment: this.comment.length > 0 ? this.comment : undefined, - }); - } - - updateReportDefaults(defaults: ReportDefaults) { - this.bugReport = Object.assign(this.bugReport || {}, defaults); - } - - composeReport(activity: Activity) { - const reportObj = { - title: this.reportTitle, - comment: this.comment, - severity: this.severity, - steps: this.chosenEventSteps, - activity - } - this.bugReport = Object.assign(this.bugReport, reportObj) - - return this.bugReport - } - - setDefaultSteps(steps: Step[]) { - this.sessionEventSteps = steps; - } - - setSteps(steps: Step[]) { - this.chosenEventSteps = steps.map(step => ({ ...step, substeps: undefined })); - this.pickedSubItems = {}; - } - - removeStep(step: Step) { - this.chosenEventSteps = this.chosenEventSteps.filter( - (chosenStep) => chosenStep.key !== step.key - ); - if (this.pickedSubItems[step.key]) this.pickedSubItems[step.key] = new Map() - } - - toggleSubStepModal(isOpen: boolean, type: 'note' | 'network' | 'error', stepKey?: string) { - this.isSubStepModalOpen = isOpen; - this.subModalType = type; - this.targetStep = stepKey - if (!this.pickedSubItems[this.targetStep]) this.pickedSubItems[this.targetStep] = new Map() - } - - toggleSubItem(isAdded: boolean, item: SubItem) { - if (isAdded) { - this.pickedSubItems[this.targetStep].set(item.key, item) - } else { - this.pickedSubItems[this.targetStep].delete(item.key) - } - } - - isSubItemChecked(item: SubItem) { - return this.pickedSubItems[this.targetStep]?.get(item.key) !== undefined - } - - saveSubItems() { - const targetIndex = this.chosenEventSteps.findIndex(step => step.key === this.targetStep) - const eventStepsCopy = this.chosenEventSteps - const step = this.chosenEventSteps[targetIndex] - if (this.pickedSubItems[this.targetStep].size > 0) { - step.substeps = Array.from(this.pickedSubItems[this.targetStep], ([name, value]) => ({ ...value })); - } - eventStepsCopy[targetIndex] = step - - return this.chosenEventSteps = eventStepsCopy - } - - resetSteps() { - this.chosenEventSteps = []; - } -} diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 5147f1e89..d92f683fc 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -12,7 +12,6 @@ import NotificationStore from './notificationStore'; import ErrorStore from './errorStore'; import SessionStore from './sessionStore'; import NotesStore from './notesStore'; -import BugReportStore from './bugReportStore'; import RecordingsStore from './recordingsStore'; import AssistMultiviewStore from './assistMultiviewStore'; import WeeklyReportStore from './weeklyReportConfigStore'; @@ -35,7 +34,6 @@ export class RootStore { notificationStore: NotificationStore; sessionStore: SessionStore; notesStore: NotesStore; - bugReportStore: BugReportStore; recordingsStore: RecordingsStore; assistMultiviewStore: AssistMultiviewStore; weeklyReportStore: WeeklyReportStore; @@ -58,7 +56,6 @@ export class RootStore { this.notificationStore = new NotificationStore(); this.sessionStore = new SessionStore(); this.notesStore = new NotesStore(); - this.bugReportStore = new BugReportStore(); this.recordingsStore = new RecordingsStore(); this.assistMultiviewStore = new AssistMultiviewStore(); this.weeklyReportStore = new WeeklyReportStore();