feat(ui): create template types and components for bug report

This commit is contained in:
sylenien 2022-10-20 09:17:49 +02:00 committed by Delirium
parent 138b0ef549
commit 2f0b5359b7
10 changed files with 447 additions and 34 deletions

View file

@ -17,7 +17,7 @@ function Autoplay(props) {
return (
<div className="flex items-center">
<div onClick={props.toggleAutoplay} className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-1">
<div onClick={props.toggleAutoplay} className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-2">
<Toggler
name="sessionsLive"
onChange={ props.toggleAutoplay }

View file

@ -0,0 +1,131 @@
import React from 'react';
import PlayLink from 'Shared/SessionItem/PlayLink';
import { connect } from 'react-redux';
import { countries } from 'App/constants';
import { session as sessionRoute } from 'App/routes';
interface Props {
hideModal: () => void;
session: Record<string, any>;
width: number;
height: number;
}
function BugReportModal({ hideModal, session, width, height }: Props) {
const {
userBrowser,
userDevice,
userCountry,
userBrowserVersion,
userOs,
userOsVersion,
userDisplayName,
userDeviceType,
revId,
metadata,
sessionId,
} = session;
console.log(session.toJS())
const envObject = {
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 })
}
return (
<div
className="flex flex-col p-4 gap-4 bg-white"
style={{ maxWidth: '70vw', width: 620, height: '100vh' }}
>
<Title />
<MetaInfo envObject={envObject} metadata={metadata} />
<Session user={userDisplayName} sessionId={sessionId} />
</div>
);
}
function Title() {
return (
<div className="flex items-center py-2 px-3 justify-between bg-gray-lightest rounded">
<div className="flex flex-col gap-2">
<div>Title</div>
<div className="text-gray-medium">By author</div>
</div>
<div>
<div>Severity</div>
<div>select here</div>
</div>
</div>
);
}
interface EnvObj {
Device: string;
Resolution: string;
Browser: string;
OS: string;
Country: string;
Rev?: string;
}
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 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">
{/* @ts-ignore */}
{envObject[envTag]}
</div>
</div>
))}
</div>
<div className="flex flex-col gap-2">
<SectionTitle>Metadata</SectionTitle>
{Object.keys(metadata).map((meta) => (
<div 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>
</div>
);
}
function Session({ user, sessionId }: { user: string, sessionId: string }) {
return (
<div>
<SectionTitle>Session recording</SectionTitle>
<div className="border rounded flex items-center justify-between p-2">
<div className="flex flex-col">
<div className="text-lg">{user}</div>
<div className="text-disabled-text">
{`${window.location.origin}/${window.location.pathname.split('/')[1]}${sessionRoute(sessionId)}`}
</div>
</div>
<PlayLink isAssist={false} viewed={false} sessionId={sessionId} />
</div>
</div>
);
}
function SectionTitle({ children }: { children: React.ReactNode }) {
return <div className="text-xl font-semibold mb-2">{children}</div>;
}
const WithUIState = connect((state) => ({ session: state.getIn(['sessions', 'current']) }))(
BugReportModal
);
export default WithUIState

View file

@ -0,0 +1,69 @@
export interface BugReportPdf {
author: string;
env: EnvData;
meta: {
[key: string]: string;
};
session: {
user: string;
url: string;
id: string;
};
comment?: string;
steps: Step[];
activity: {
network: NetworkError[];
console: ConsoleError[];
clickRage: ClickRage[];
};
}
export interface EnvData {
browser: string;
os: string;
country: string;
device: string;
resolution: string;
}
export interface NetworkError {
time: number;
}
export interface ConsoleError {
time: number;
}
export interface ClickRage {
time: number;
}
export interface Step {
type: string;
icon: string;
details: string;
substeps?: SubStep[];
}
export type SubStep = Note | Error | Request;
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';
}

View file

@ -110,13 +110,6 @@ function UserCard({ className, request, session, width, height, similarSessions,
</div>
</div>
</div>
{/* <SlideModal
title={ <div>User Sessions</div> }
isDisplayed={ showUserSessions }
content={ showUserSessions && <SessionList similarSessions={ similarSessions } loading={ loading } /> }
onClose={ () => showUserSessions ? setShowUserSessions(false) : null }
/> */}
</div>
);
}

View file

@ -7,11 +7,14 @@ import copy from 'copy-to-clipboard';
import { Tooltip } from 'react-tippy';
import Issues from './Issues/Issues';
import NotePopup from './components/NotePopup';
import { connectPlayer } from 'Player';
import { connectPlayer, pause } from 'Player';
import ItemMenu from './components/HeaderMenu';
import { useModal } from 'App/components/Modal';
import BugReportModal from './BugReport/BugReportModal';
function SubHeader(props) {
const [isCopied, setCopied] = React.useState(false);
const { showModal, hideModal } = useModal();
const isAssist = window.location.pathname.includes('/assist/');
const location =
@ -19,6 +22,11 @@ function SubHeader(props) {
? `${props.currentLocation.slice(0, 60)}...`
: props.currentLocation;
const showReportModal = () => {
pause();
showModal(<BugReportModal width={props.width} height={props.height} hideModal={hideModal} />, { right: true });
};
return (
<div className="w-full px-4 py-2 flex items-center border-b">
{location && (
@ -45,39 +53,62 @@ function SubHeader(props) {
)}
{!isAssist ? (
<div
className="ml-auto text-sm flex items-center color-gray-medium"
className="ml-auto text-sm flex items-center color-gray-medium gap-2"
style={{ width: 'max-content' }}
>
<NotePopup />
<div className="cursor-pointer mr-4 hover:bg-gray-light-shade rounded-md p-1">
{props.jiraConfig && props.jiraConfig.token && <Issues sessionId={props.sessionId} />}
</div>
<div className="cursor-pointer">
<SharePopup
entity="sessions"
id={props.sessionId}
showCopyLink={true}
trigger={
<div className="flex items-center hover:bg-gray-light-shade rounded-md p-1">
<Icon className="mr-2" disabled={props.disabled} name="share-alt" size="16" />
<span>Share</span>
</div>
}
/>
</div>
<div className="mx-4 hover:bg-gray-light-shade rounded-md p-1">
<Bookmark noMargin sessionId={props.sessionId} />
<div
onClick={showReportModal}
className="cursor-pointer rounded flex items-center p-2 gap-1 hover:bg-gray-light-shade"
>
<Icon name="file-pdf" size={16} />
<span>Create Bug Report</span>
</div>
<ItemMenu
items={[
{ key: 1, component: <NotePopup /> },
{
key: 2,
component: props.jiraConfig && props.jiraConfig.token && (
<Issues sessionId={props.sessionId} />
),
},
{
key: 3,
component: (
<SharePopup
entity="sessions"
id={props.sessionId}
showCopyLink={true}
trigger={
<div className="flex items-center">
<Icon
className="mr-2"
disabled={props.disabled}
name="share-alt"
size="16"
/>
<span>Share</span>
</div>
}
/>
),
},
{
key: 4,
component: <Bookmark noMargin sessionId={props.sessionId} />,
},
]}
/>
<div>
<Autoplay />
</div>
<div></div>
</div>
) : null}
</div>
);
}
const SubH = connectPlayer((state) => ({ currentLocation: state.location }))(SubHeader);
const SubH = connectPlayer((state) => ({ width: state.width, height: state.height, currentLocation: state.location }))(SubHeader);
export default React.memo(SubH);

View file

@ -0,0 +1,75 @@
import React from 'react';
import { Icon } from 'UI';
import styles from './menu.module.css';
import cn from 'classnames';
interface MenuItem {
key: number;
component?: React.ReactElement;
}
interface Props {
items: MenuItem[];
}
export default class ItemMenu extends React.PureComponent<Props> {
state = {
displayed: false,
};
handleEsc = (e: KeyboardEvent) => e.key === 'Escape' && this.state.displayed && this.toggleMenu();
componentDidMount() {
document.addEventListener('keydown', this.handleEsc, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleEsc, false);
}
toggleMenu = () => {
this.setState({ displayed: !this.state.displayed });
};
render() {
const { items } = this.props;
const { displayed } = this.state;
return (
<div className={styles.wrapper}>
<div
onClick={this.toggleMenu}
className={cn(
'flex items-center cursor-pointer select-none',
'rounded p-2 hover:bg-gray-light-shade',
{
'bg-gray-light': displayed,
}
)}
>
<div
className={cn('rounded-full flex items-center justify-center', {
'bg-gray-light': displayed,
})}
role="button"
>
<Icon name="ellipsis-v" size="16" />
</div>
<span className={'mr-1 text-disabled-text'}>More</span>
</div>
<div className={cn(styles.menu, styles.menuDim)} data-displayed={displayed}>
{items.map((item) =>
item.component ? (
<div
key={item.key}
role="menuitem"
className="w-full h-full p-3 hover:bg-gray-light-shade cursor-pointer flex items-center"
>
{item.component}
</div>
) : null
)}
</div>
</div>
);
}
}

View file

@ -28,7 +28,7 @@ function NotePopup({
<div
onClick={toggleNotePopup}
className={cn(
'mr-4 hover:bg-gray-light-shade rounded-md p-1 flex items-center',
'mr-4 hover:bg-gray-light-shade rounded-md flex items-center',
tooltipActive ? 'cursor-not-allowed' : 'cursor-pointer'
)}
>

View file

@ -0,0 +1,102 @@
@import 'icons.css';
.wrapper {
position: relative;
display: inline-block;
}
.menuBtn {
@mixin icon-before ellipsis-v, $gray-darkest, 18px {
margin: 5px;
}
width: 36px;
height: 36px;
border-radius: 18px;
border: 1px solid transparent;
transition: all 0.2s;
margin: 0 auto;
cursor: pointer;
&:hover {
border-color: $active-blue-border;
transition: all 0.2s;
background-color: #fff;
}
}
.menuDim {
border: none!important;
box-shadow: 0 1px 3px 0 $gray-light!important;
& .menuItem {
color: $gray-dark!important;
}
}
.menu {
&[data-displayed=false] {
display: none;
}
white-space: nowrap;
z-index: 20;
position: absolute;
right: 0px;
top: 37px;
min-width: 150px;
background-color: $white;
border-radius: 3px;
border: 1px solid rgba(34,36,38,.15);
box-shadow: 0 2px 3px 0 rgb(34 36 38 / 15%);
& .menuItem {
cursor: pointer;
padding: 10px;
color: black;
display: flex;
align-items: center;
border-bottom: 1px solid $gray-light;
position: relative;
& .iconWrapper {
width: 13px;
height: 13px ;
margin-right: 8px;
}
&:hover {
background-color: $active-blue;
}
&:last-child {
border: none;
}
& .edit {
@mixin icon pencil, $gray-medium, 15px;
margin-right: 10px;
}
& .copy {
@mixin icon copy, $gray-medium, 15px;
margin-right: 10px;
}
& .remove {
@mixin icon trash, $gray-medium, 15px;
margin-right: 10px;
}
& .enabled {
@mixin icon eye, $gray-medium, 15px;
margin-right: 10px;
}
& .disabled {
@mixin icon eye-slash, $gray-medium, 15px;
margin-right: 10px;
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
<svg viewBox="0 0 13 14" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2148_10595)">
<path d="M3.49328 0.66217C3.07306 0.66217 2.67005 0.829104 2.3729 1.12625C2.07576 1.42339 1.90883 1.8264 1.90883 2.24662V11.7533C1.90883 12.1736 2.07576 12.5766 2.3729 12.8737C2.67005 13.1709 3.07306 13.3378 3.49328 13.3378H9.8311C10.2513 13.3378 10.6543 13.1709 10.9515 12.8737C11.2486 12.5766 11.4156 12.1736 11.4156 11.7533V2.24662C11.4156 1.8264 11.2486 1.42339 10.9515 1.12625C10.6543 0.829104 10.2513 0.66217 9.8311 0.66217H3.49328ZM3.49328 1.4544H9.8311C10.0412 1.4544 10.2427 1.53786 10.3913 1.68644C10.5399 1.83501 10.6233 2.03651 10.6233 2.24662V11.7533C10.6233 11.9635 10.5399 12.165 10.3913 12.3135C10.2427 12.4621 10.0412 12.5456 9.8311 12.5456H3.49328C3.28317 12.5456 3.08167 12.4621 2.93309 12.3135C2.78452 12.165 2.70106 11.9635 2.70106 11.7533V2.24662C2.70106 2.03651 2.78452 1.83501 2.93309 1.68644C3.08167 1.53786 3.28317 1.4544 3.49328 1.4544Z" fill-opacity="0.6"/>
<path d="M3.97099 10.2378C3.81658 10.1761 3.69217 10.0568 3.62399 9.9051C3.46951 9.59772 3.521 9.29033 3.68737 9.03207C3.84423 8.78885 4.10408 8.58208 4.398 8.40859C4.7703 8.19743 5.16384 8.02616 5.57208 7.8976C5.88911 7.32766 6.17011 6.73841 6.41343 6.13331C6.26796 5.80277 6.1539 5.45928 6.07277 5.10738C6.00464 4.79048 5.97849 4.47676 6.03633 4.20741C6.09574 3.92696 6.2534 3.67503 6.55127 3.5554C6.70338 3.4944 6.86816 3.46034 7.02819 3.4944C7.10869 3.51154 7.18437 3.54638 7.24973 3.59639C7.31509 3.64641 7.3685 3.71034 7.40609 3.78356C7.4758 3.91349 7.50115 4.0656 7.5067 4.20978C7.51224 4.35793 7.49719 4.52271 7.46946 4.69621C7.40292 5.10024 7.25556 5.59459 7.05751 6.11746C7.27607 6.58489 7.5359 7.0319 7.83389 7.45316C8.18651 7.42531 8.54117 7.4386 8.89072 7.49277C9.17909 7.54427 9.47221 7.64725 9.65126 7.86116C9.74632 7.97524 9.80416 8.11467 9.8097 8.27153C9.81525 8.42364 9.77247 8.57416 9.70038 8.71755C9.63793 8.85044 9.5411 8.96422 9.41993 9.04712C9.30015 9.12525 9.15873 9.16352 9.01589 9.15645C8.75366 9.14536 8.49777 9.00117 8.27674 8.82609C8.00799 8.60385 7.76581 8.3513 7.55502 8.07347C7.01928 8.13424 6.48989 8.24186 5.97295 8.39512C5.73595 8.81498 5.46545 9.21504 5.16408 9.59138C4.93434 9.86866 4.68241 10.1103 4.43048 10.2149C4.28573 10.2803 4.12155 10.2886 3.97099 10.2378ZM5.06347 8.73181C4.93196 8.79202 4.80996 8.8554 4.69984 8.92036C4.43999 9.07406 4.27124 9.22379 4.18727 9.35371C4.1128 9.46859 4.11121 9.55177 4.15558 9.63971C4.1635 9.65714 4.17142 9.66823 4.17618 9.67456C4.18563 9.67203 4.19489 9.66886 4.2039 9.66506C4.31244 9.62069 4.48514 9.47888 4.70697 9.2119C4.83313 9.05744 4.95209 8.89724 5.06347 8.73181ZM6.36272 7.67815C6.62726 7.61636 6.89419 7.56535 7.16287 7.52525C7.01856 7.30454 6.88376 7.07776 6.75884 6.84552C6.6346 7.12645 6.50251 7.40384 6.36272 7.67736V7.67815ZM8.30051 8.03465C8.41934 8.16299 8.53501 8.27232 8.64513 8.35947C8.83526 8.50999 8.96757 8.5599 9.03966 8.56228C9.05896 8.5648 9.07854 8.56061 9.09511 8.55039C9.12807 8.52437 9.15373 8.49025 9.16958 8.45137C9.19775 8.40311 9.21379 8.34874 9.21632 8.29292C9.21587 8.27431 9.20853 8.25653 9.19573 8.24301C9.15453 8.19389 9.03728 8.12259 8.78535 8.07743C8.6251 8.05065 8.46298 8.03661 8.30051 8.03545V8.03465ZM6.72398 5.25711C6.79065 5.04203 6.84358 4.82294 6.88242 4.60114C6.90698 4.4522 6.91649 4.32941 6.91253 4.23276C6.91275 4.17943 6.90418 4.12644 6.88718 4.0759C6.84755 4.0808 6.80885 4.09148 6.7723 4.10758C6.70338 4.13531 6.64713 4.19156 6.61703 4.33178C6.58534 4.48389 6.59326 4.70334 6.65347 4.983C6.67248 5.07093 6.69625 5.16283 6.72477 5.25711H6.72398Z" />
</g>
<defs>
<clipPath id="clip0_2148_10595">
<rect width="12.6756" height="12.6756" fill="white" transform="translate(0.324371 0.66217)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB