change(ui): add steps, change store and some layout

This commit is contained in:
sylenien 2022-10-20 15:15:39 +02:00 committed by Delirium
parent 61829d8260
commit f4350ee083
9 changed files with 261 additions and 38 deletions

View file

@ -1,9 +1,14 @@
import React from 'react';
import { connect } from 'react-redux';
import { countries } from 'App/constants';
import { useStore } from 'App/mstore';
import { session as sessionRoute } from 'App/routes';
import { ReportDefaults, EnvData, Step } 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'
interface Props {
hideModal: () => void;
@ -13,7 +18,48 @@ interface Props {
height: number;
}
const TYPES = { CLICKRAGE: 'CLICKRAGE', CLICK: 'CLICK', LOCATION: 'LOCATION' }
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: 'pointer',
details: event.url,
time: event.time,
}
steps.push(step)
}
if (event.type === TYPES.CLICK) {
const step = {
key: event.key,
type: TYPES.CLICK,
icon: 'finger',
details: event.label,
time: event.time,
}
steps.push(step)
}
if (event.type === TYPES.CLICKRAGE) {
const step = {
key: event.key,
type: TYPES.CLICKRAGE,
icon: 'smile',
details: event.label,
time: event.time,
}
steps.push(step)
}
})
return steps
}
function BugReportModal({ hideModal, session, width, height, account }: Props) {
const { bugReportStore } = useStore()
const {
userBrowser,
userDevice,
@ -26,11 +72,12 @@ function BugReportModal({ hideModal, session, width, height, account }: Props) {
revId,
metadata,
sessionId,
events,
} = session;
console.log(session.toJS(), account.toJS?.())
console.log(session.toJS())
const envObject = {
const envObject: EnvData = {
Device: `${userDevice}${userDeviceType !== userDevice ? ` ${userDeviceType}` : ''}`,
Resolution: `${width}x${height}`,
Browser: `${userBrowser} v${userBrowserVersion}`,
@ -41,14 +88,30 @@ function BugReportModal({ hideModal, session, width, height, account }: Props) {
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,
}
}
bugReportStore.updateReportDefaults(defaults)
bugReportStore.setSteps(mapEvents(events))
return (
<div
className="flex flex-col p-4 gap-4 bg-white"
className="flex flex-col p-4 gap-4 bg-white overflow-y-scroll"
style={{ maxWidth: '70vw', width: 620, height: '100vh' }}
>
<Title userName={account.name} />
<MetaInfo envObject={envObject} metadata={metadata} />
<Session user={userDisplayName} sessionId={sessionId} />
<Steps />
<Session user={userDisplayName} sessionId={sessionId} sessionUrl={sessionUrl} />
<Comments />
</div>
);
}

View file

@ -0,0 +1,63 @@
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.isTitleEdit) {
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">
<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="name"
placeholder="Comment..."
rows={3}
autoFocus
className="rounded fluid border -mx-2 px-2 py-1 w-full"
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',
'h-8 pt-1 flex items-center w-fit',
'cursor-pointer select-none border-b'
)}
>
{commentStr}
</div>
)}
</div>
);
}
export default observer(Comments);

View file

@ -16,7 +16,7 @@ export default function MetaInfo({ envObject, metadata }: { envObject: EnvObj, m
<div className="flex flex-col gap-2">
<SectionTitle>Environment</SectionTitle>
{Object.keys(envObject).map((envTag) => (
<div className="flex items-center">
<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">
{/* @ts-ignore */}
@ -29,7 +29,7 @@ export default function MetaInfo({ envObject, metadata }: { envObject: EnvObj, m
<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 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>

View file

@ -3,15 +3,15 @@ import SectionTitle from './SectionTitle';
import { session as sessionRoute } from 'App/routes';
import PlayLink from 'Shared/SessionItem/PlayLink';
export default function Session({ user, sessionId }: { user: string, sessionId: string }) {
export default function Session({ user, sessionId, sessionUrl }: { user: string, sessionId: string, sessionUrl: string }) {
return (
<div>
<SectionTitle>Session recording</SectionTitle>
<div className="border rounded flex items-center justify-between p-2">
<div className="border hover:border-gray-light 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)}`}
{sessionUrl}
</div>
</div>
<PlayLink isAssist={false} viewed={false} sessionId={sessionId} />

View file

@ -0,0 +1,43 @@
import React from 'react';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import SectionTitle from './SectionTitle';
import { Step as IStep } from '../types';
const STEP_NAMES = { CLICKRAGE: 'Multiple click', CLICK: 'Clicked', LOCATION: 'Visited' };
function Steps() {
const { bugReportStore } = useStore();
return (
<div>
<SectionTitle>Steps to reproduce</SectionTitle>
<div className="mb-2 text-gray-medium">STEPS</div>
<div className="flex flex-col gap-4">
{bugReportStore.sessionEventSteps.map((step, ind) => (
<React.Fragment key={step.key}>
<Step step={step} ind={ind} />
</React.Fragment>
))}
</div>
</div>
);
}
function Step({ step, ind }: { step: IStep; ind: number }) {
return (
<div className="py-1 px-2 flex items-center gap-2 w-full rounded hover:bg-figmaColors-secondary-outlined-hover-background">
<div className="rounded-3xl px-4 bg-gray-lightest">{ind + 1}</div>
<div className="flex items-center gap-1">
<div>{step.icon}</div>
{/* @ts-ignore */}
<div className="font-semibold">{STEP_NAMES[step.type]}</div>
<div className="text-gray-medium">{step.details}</div>
</div>
</div>
);
}
export default observer(Steps);

View file

@ -1,17 +1,27 @@
import React from 'react'
import Select from 'Shared/Select';
import ReportTitle from './ReportTitle';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { SeverityLevels } from 'App/mstore/bugReportStore'
const selectOptions = [{ label: 'HIGH', value: SeverityLevels.High }, { label: 'MEDIUM', value: SeverityLevels.Medium }, { label: 'LOW', value: SeverityLevels.Low}]
function Title({ userName }: { userName: string }) {
const { bugReportStore } = useStore();
export default function Title({ userName }: { userName: string }) {
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>
<div>Severity</div>
<div>select here</div>
<div className="flex items-center gap-2">
<div className="font-semibold">Severity</div>
<Select plain controlStyle={{ minWidth: 100 }} defaultValue={SeverityLevels.High} options={selectOptions} onChange={({ value }) => bugReportStore.setSeverity(value.value) } />
</div>
</div>
);
}
export default observer(Title)

View file

@ -1,4 +1,18 @@
export interface BugReportPdf {
import { SeverityLevels } from 'App/mstore/bugReportStore';
export interface BugReportPdf extends ReportDefaults {
title: string;
comment?: string;
severity: SeverityLevels;
steps: Step[];
activity: {
network: NetworkError[];
console: ConsoleError[];
clickRage: ClickRage[];
};
}
export interface ReportDefaults {
author: string;
env: EnvData;
meta: {
@ -9,21 +23,14 @@ export interface BugReportPdf {
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;
Browser: string;
OS: string;
Country: string;
Device: string;
Resolution: string;
}
export interface NetworkError {
@ -38,15 +45,17 @@ export interface ClickRage {
time: number;
}
export interface Step {
type: string;
icon: string;
details: string;
substeps?: SubStep[];
}
export type SubStep = Note | Error | Request;
export interface Step {
key: string;
type: string;
time: number;
details: string;
icon: string;
substeps?: SubStep[]
}
export interface Note {
author: string;
message: string;

View file

@ -15,14 +15,15 @@ interface Props<Value extends ValueObject> {
defaultValue?: string | number;
plain?: boolean;
components?: any;
styles?: any;
styles?: Record<string, any>;
controlStyle?: Record<string, any>;
onChange: (newValue: { name: string, value: Value }) => void;
name?: string;
placeholder?: string;
[x:string]: any;
}
export default function<Value extends ValueObject>({ placeholder='Select', name = '', onChange, right = false, plain = false, options, isSearchable = false, components = {}, styles = {}, defaultValue = '', ...rest }: Props<Value>) {
export default function<Value extends ValueObject>({ placeholder='Select', name = '', onChange, right = false, plain = false, options, isSearchable = false, components = {}, styles = {}, defaultValue = '', controlStyle = {}, ...rest }: Props<Value>) {
const defaultSelected = defaultValue ? (options.find(o => o.value === defaultValue) || options[0]): null;
const customStyles = {
option: (provided: any, state: any) => ({
@ -71,7 +72,8 @@ export default function<Value extends ValueObject>({ placeholder='Select', name
['&:hover']: {
backgroundColor: colors['gray-lightest'],
transition: 'all 0.2s ease-in-out'
}
},
...controlStyle,
}
if (plain) {
obj['backgroundColor'] = 'transparent';

View file

@ -1,6 +1,7 @@
import { makeAutoObservable } from "mobx"
import { BugReportPdf, ReportDefaults, Step } from 'Components/Session_/BugReport/types'
enum SeverityLevels {
export enum SeverityLevels {
Low,
Medium,
High
@ -8,9 +9,15 @@ enum SeverityLevels {
export default class BugReportStore {
reportTitle = 'Untitled Report'
isTitleEdit = false
comment = ''
severity = SeverityLevels.High
isCommentEdit = false
isTitleEdit = false
bugReport: Partial<BugReportPdf>
sessionEventSteps: Step[] = []
constructor() {
makeAutoObservable(this)
}
@ -21,5 +28,31 @@ export default class BugReportStore {
setTitle(title: string) {
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)
}
setSteps(steps: Step[]) {
this.sessionEventSteps = steps
}
}