Skip to content

Commit

Permalink
feat(log): split large log into chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
YuryShkoda committed Apr 18, 2023
1 parent c72ecc7 commit 75f5a3c
Show file tree
Hide file tree
Showing 5 changed files with 387 additions and 40 deletions.
153 changes: 153 additions & 0 deletions web/src/containers/Studio/internal/components/log/logChunk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { useState, useEffect, SyntheticEvent } from 'react'
import { Typography } from '@mui/material'
import Highlight from 'react-highlight'
import { ErrorOutline, Warning } from '@mui/icons-material'
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { makeStyles } from '@mui/styles'
import {
defaultChunkSize,
parseErrorsAndWarnings,
LogInstance,
clearErrorsAndWarningsHtmlWrapping
} from '../../../../../utils'

const useStyles: any = makeStyles((theme: any) => ({
expansionDescription: {
backgroundColor: '#fbfbfb',
border: '1px solid #e2e2e2',
borderRadius: '3px',
minHeight: '50px',
padding: '10px',
boxSizing: 'border-box',
whiteSpace: 'pre-wrap',
fontFamily: 'Monaco, Courier, monospace',
position: 'relative',
width: '100%',
[theme.breakpoints.down('sm')]: {
fontSize: theme.typography.pxToRem(12)
},
[theme.breakpoints.up('md')]: {
fontSize: theme.typography.pxToRem(16)
}
}
}))

interface LogChunkProps {
id: number
text: string
expanded: boolean
logLineCount: number
onClick: (evt: any, id: number) => void
scrollToLogInstance?: LogInstance
}

const LogChunk = (props: LogChunkProps) => {
const { id, text, logLineCount, scrollToLogInstance } = props

const classes = useStyles()
const [expanded, setExpanded] = useState(props.expanded)

useEffect(() => {
setExpanded(props.expanded)
}, [props.expanded])

useEffect(() => {
if (expanded && scrollToLogInstance) {
const { type, id } = scrollToLogInstance
const line = document.getElementById(`${type}_${id}`)
const logWrapper: HTMLDivElement | null =
document.querySelector(`#logWrapper`)
const logContainer: HTMLHeadElement | null =
document.querySelector(`#log_container`)

if (line && logWrapper && logContainer) {
const initialColor = line.style.color

line.style.backgroundColor = '#f6e30599'

line.scrollIntoView({ behavior: 'smooth', block: 'start' })

setTimeout(() => {
line.setAttribute('style', `color: ${initialColor};`)
}, 3000)
}
}
}, [expanded, scrollToLogInstance])

const { errors, warnings } = parseErrorsAndWarnings(text)

return (
<div onClick={(evt) => props.onClick(evt, id)}>
<button
style={{
color: '#444',
cursor: 'pointer',
padding: '18px',
width: '100%',
textAlign: 'left',
border: 'none',
outline: 'none',
transition: '0.4s',
boxShadow:
'rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px'
}}
>
<Typography variant="subtitle1">
<div
style={{
display: 'flex',
flexDirection: 'row',
gap: 6,
alignItems: 'center'
}}
>
<span>{`Lines: ${id * defaultChunkSize} ... ${
(id + 1) * defaultChunkSize < logLineCount
? (id + 1) * defaultChunkSize
: logLineCount
}`}</span>
<ContentCopyIcon
style={{ fontSize: 20 }}
onClick={(evt: SyntheticEvent) => {
evt.stopPropagation()

navigator.clipboard.writeText(
clearErrorsAndWarningsHtmlWrapping(text)
)
}}
/>
{errors && errors.length !== 0 && (
<ErrorOutline color="error" style={{ fontSize: 20 }} />
)}
{warnings && warnings.length !== 0 && (
<Warning style={{ fontSize: 20, color: 'green' }} />
)}{' '}
<ExpandMoreIcon
style={{
marginLeft: 'auto',
transform: expanded ? 'rotate(180deg)' : 'unset'
}}
/>
</div>
</Typography>
</button>
<div
style={{
padding: '0 18px',
backgroundColor: 'white',
display: expanded ? 'block' : 'none',
overflow: 'hidden'
}}
>
<div id={`log_container`} className={classes.expansionDescription}>
<Highlight className={'html'} innerHTML={true}>
{expanded ? text : ''}
</Highlight>
</div>
</div>
</div>
)
}

export default LogChunk
183 changes: 153 additions & 30 deletions web/src/containers/Studio/internal/components/log/logComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import { Typography } from '@mui/material'
import { ListItemText } from '@mui/material'
import { makeStyles } from '@mui/styles'
import Highlight from 'react-highlight'
import { LogObject } from '../../../../../utils'
import { LogObject, defaultChunkSize } from '../../../../../utils'
import { RunTimeType } from '../../../../../context/appContext'
import { splitIntoChunks, LogInstance } from '../../../../../utils'
import LogChunk from './logChunk'
import { useEffect, useState } from 'react'

// TODO:
// link to download log.log

const useStyles: any = makeStyles((theme: any) => ({
expansionDescription: {
Expand Down Expand Up @@ -37,32 +43,96 @@ interface LogComponentProps {
const LogComponent = (props: LogComponentProps) => {
const { log, selectedRunTime } = props
const logObject = log as LogObject
const logChunks = splitIntoChunks(logObject?.body || '')
const [logChunksState, setLogChunksState] = useState<boolean[]>(
new Array(logChunks.length).fill(false)
)

const [scrollToLogInstance, setScrollToLogInstance] = useState<LogInstance>()
const [oldestExpandedChunk, setOldestExpandedChunk] = useState<number>(
logChunksState.length - 1
)
const maxOpenedChunks = 2

const classes = useStyles()

const goToLogLine = (type: 'error' | 'warning', ind: number) => {
const line = document.getElementById(`${type}_${ind}`)
const goToLogLine = (logInstance: LogInstance, ind: number) => {
let chunkNumber = 0

for (
let i = 0;
i <= Math.ceil(logObject.linesCount / defaultChunkSize);
i++
) {
if (logInstance.line < (i + 1) * defaultChunkSize) {
chunkNumber = i

break
}
}

setLogChunksState((prevState) => {
const newState = [...prevState]
newState[chunkNumber] = true

const chunkToCollapse = getChunkToAutoCollapse()

if (chunkToCollapse !== undefined) {
newState[chunkToCollapse] = false
}

return newState
})

setScrollToLogInstance(logInstance)
}

useEffect(() => {
// INFO: expand the last chunk by default
setLogChunksState((prevState) => {
const lastChunk = prevState.length - 1

const newState = [...prevState]
newState[lastChunk] = true

return newState
})

setTimeout(() => {
scrollToTheBottom()
}, 100)
}, [])

// INFO: scroll to the bottom of the log
const scrollToTheBottom = () => {
const logWrapper: HTMLDivElement | null =
document.querySelector(`#logWrapper`)
const logContainer: HTMLHeadElement | null =
document.querySelector(`#log_container`)

if (line && logWrapper && logContainer) {
line.style.backgroundColor = '#f6e30599'
logWrapper.scrollTop =
line.offsetTop - logWrapper.offsetTop + logContainer.offsetTop

setTimeout(() => {
line.setAttribute('style', '')
}, 3000)
if (logWrapper) {
logWrapper.scrollTop = logWrapper.scrollHeight
}
}

const decodeHtml = (encodedString: string) => {
const tempElement = document.createElement('textarea')
tempElement.innerHTML = encodedString
const getChunkToAutoCollapse = () => {
const openedChunks = logChunksState
.map((chunkState: boolean, id: number) => (chunkState ? id : undefined))
.filter((chunk) => chunk !== undefined)

if (openedChunks.length < maxOpenedChunks) return undefined
else {
const chunkToCollapse = oldestExpandedChunk
const newOldestChunk = openedChunks.filter(
(chunk) => chunk !== chunkToCollapse
)[0]

if (newOldestChunk !== undefined) {
setOldestExpandedChunk(newOldestChunk)

return tempElement.value
return chunkToCollapse
}

return undefined
}
}

return (
Expand Down Expand Up @@ -100,9 +170,19 @@ const LogComponent = (props: LogComponentProps) => {
logObject.errors.map((error, ind) => (
<TreeItem
nodeId={`error_${ind}`}
label={<ListItemText primary={error} />}
label={<ListItemText primary={error.body} />}
key={`error_${ind}`}
onClick={() => goToLogLine('error', ind)}
onClick={() => {
setLogChunksState((prevState) => {
const newState = [...prevState]

newState[ind] = true

return newState
})

goToLogLine(error, ind)
}}
/>
))}
</TreeItem>
Expand All @@ -118,9 +198,19 @@ const LogComponent = (props: LogComponentProps) => {
logObject.warnings.map((warning, ind) => (
<TreeItem
nodeId={`warning_${ind}`}
label={<ListItemText primary={warning} />}
label={<ListItemText primary={warning.body} />}
key={`warning_${ind}`}
onClick={() => goToLogLine('warning', ind)}
onClick={() => {
setLogChunksState((prevState) => {
const newState = [...prevState]

newState[ind] = true

return newState
})

goToLogLine(warning, ind)
}}
/>
))}
</TreeItem>
Expand All @@ -129,15 +219,48 @@ const LogComponent = (props: LogComponentProps) => {
</div>
</div>

<Typography
id={`log_container`}
variant="h5"
className={classes.expansionDescription}
>
<Highlight className={'html'} innerHTML={true}>
{decodeHtml(logObject?.body || '')}
</Highlight>
</Typography>
{Array.isArray(logChunks) ? (
logChunks.map((chunk: string, id: number) => (
<LogChunk
id={id}
text={chunk}
expanded={logChunksState[id]}
key={`log-chunk-${id}`}
logLineCount={logObject.linesCount}
scrollToLogInstance={scrollToLogInstance}
onClick={(evt, chunkNumber) => {
setLogChunksState((prevState) => {
const newState = [...prevState]
const expand = !newState[chunkNumber]

newState[chunkNumber] = expand

if (expand) {
const chunkToCollapse = getChunkToAutoCollapse()

if (chunkToCollapse !== undefined) {
newState[chunkToCollapse] = false
}
}

return newState
})

setScrollToLogInstance(undefined)
}}
/>
))
) : (
<Typography
id={`log_container`}
variant="h5"
className={classes.expansionDescription}
>
<Highlight className={'html'} innerHTML={true}>
{logChunks}
</Highlight>
</Typography>
)}
</div>
) : (
<div>
Expand Down
3 changes: 2 additions & 1 deletion web/src/containers/Studio/internal/hooks/useEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ const useEditor = ({
const log: LogObject = {
body: logLines.join(`\n`),
errors,
warnings
warnings,
linesCount: logLines.length
}

setLog(log)
Expand Down
Loading

0 comments on commit 75f5a3c

Please sign in to comment.