Skip to content

Commit

Permalink
feat(studio): add logs filter (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidvitora authored Nov 2, 2023
1 parent 55bc0ee commit e89fb36
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Button, Intent } from '@blueprintjs/core'
import { lang } from 'botpress/shared'
import cx from 'classnames'
import React from 'react'
import CheckboxTree from 'react-checkbox-tree'
import 'react-checkbox-tree/lib/react-checkbox-tree.css'
import {
FaCheckSquare,
FaChevronDown,
FaChevronRight,
FaFile,
FaFolder,
FaFolderOpen,
FaMinusSquare,
FaPlusSquare,
FaSquare
} from 'react-icons/fa'

import style from '../style.scss'

interface Props {
setLogFilter: (checked: string[]) => any
ref?: any
}

interface State {
nodes: any
checked: any
expanded: any
}

export const LOG_FILTER_STORAGE_KEY = 'logsFilter'
export const LOG_FILTER_DEFAULT_VALUE = [
'severity',
'debug',
'info',
'warn',
'error',
'critical',
'botLevel',
'global',
'currentBot'
]

export default class Debug extends React.Component<Props, State> {
state = {
nodes: undefined,
checked: undefined,
expanded: ['severity', 'botLevel']
}

async componentDidMount() {
await this.loadConfiguration()
}

loadConfiguration = async () => {
let logFilter = LOG_FILTER_DEFAULT_VALUE
try {
const logFilterString = localStorage.getItem('logsFilter')
const persistedLogFilter = JSON.parse(logFilterString)

if (logFilterString?.length && Array.isArray(persistedLogFilter)) {
logFilter = persistedLogFilter
}
} catch (e) {}

this.setState({
nodes: [
{
value: 'severity',
label: 'Severity',
children: [
{ value: 'debug', label: 'Debug' },
{ value: 'info', label: 'Info' },
{ value: 'warn', label: 'Warning' },
{ value: 'error', label: 'Error' },
{ value: 'critical', label: 'Critical' }
]
},
{
value: 'botLevel',
label: 'Bot Level',
children: [
{ value: 'global', label: 'Global' },
{ value: 'currentBot', label: 'Current Bot' }
]
}
],
checked: logFilter
})

this.props.setLogFilter(logFilter)
}

saveConfiguration = async () => {
localStorage.setItem(LOG_FILTER_STORAGE_KEY, JSON.stringify(this.state.checked))
this.props.setLogFilter(this.state.checked)
}

render() {
if (!this.state.nodes) {
return null
}

return (
<div className={cx(style.tabContainer, style.flex)}>
<div className={style.fullWidth}>
<CheckboxTree
nodes={this.state.nodes || []}
checked={this.state.checked}
checkModel={'all'}
expanded={this.state.expanded}
onCheck={(checked) => this.setState({ checked })}
icons={{
check: <FaCheckSquare />,
uncheck: <FaSquare />,
halfCheck: <FaCheckSquare fillOpacity="0.5" />,
expandClose: <FaChevronRight />,
expandOpen: <FaChevronDown />,
expandAll: <FaPlusSquare />,
collapseAll: <FaMinusSquare />,
parentClose: <FaFolder stroke="blue" fill="none" />,
parentOpen: <FaFolderOpen stroke="blue" fill="none" />,
leaf: <FaFile stroke="blue" fill="none" />
}}
/>
<div>
<br />
<br />
{lang.tr('bottomPanel.logs.botLevelOBS')}
</div>
</div>
<div>
<Button
id="btn-save"
onClick={() => this.saveConfiguration()}
intent={Intent.PRIMARY}
fill={true}
icon="tick"
text={lang.tr('apply')}
/>
</div>
</div>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import EventBus from '~/util/EventBus'
import style from '../style.scss'

import Debug from './Debug'
import Filter, { LOG_FILTER_DEFAULT_VALUE } from './Filter'
import logStyle from './style.scss'

const INITIAL_LOGS_LIMIT = 200
Expand All @@ -35,12 +36,14 @@ interface State {
followLogs: boolean
selectedPanel: string
initialLogs: LogEntry[]
logFilter: string[]
}

interface LogEntry {
level: string
message: string
args: any
botId: string
ts: string
}

Expand All @@ -56,6 +59,14 @@ class BottomPanel extends React.Component<Props, State> {
private debounceRefreshLogs: typeof this.forceUpdate
private logs: LogEntry[]
private debugRef = React.createRef<any>()
private filterRef = React.createRef<any>()

state = {
followLogs: true,
selectedPanel: 'logs',
initialLogs: [],
logFilter: LOG_FILTER_DEFAULT_VALUE
}

constructor(props: Props) {
super(props)
Expand All @@ -77,6 +88,7 @@ class BottomPanel extends React.Component<Props, State> {
return {
ts: log.timestamp,
level: log.level,
botId: log.type?.replace?.('logs::', ''),
message: this.ansiiToSafeHTML(log.message),
args: this.ansiiToSafeHTML(log.metadata || log.args || '')
}
Expand All @@ -97,12 +109,6 @@ class BottomPanel extends React.Component<Props, State> {
})
}

state = {
followLogs: true,
selectedPanel: 'logs',
initialLogs: []
}

queryLogs = async () => {
const { data } = await axios.get<APILog[]>(`${window.API_PATH}/admin/logs/bots/${window.BOT_ID}`, {
params: {
Expand Down Expand Up @@ -182,13 +188,22 @@ class BottomPanel extends React.Component<Props, State> {
return null
}
const allLogs = [...this.state.initialLogs, ...this.logs]
const logFilter = this.state.logFilter
const LogsPanel = (
<ul
className={cn(logStyle.logs, style.tabContainer)}
ref={this.messageListRef}
onScroll={this.handleLogsScrolled}
>
{allLogs.map((e) => this.renderEntry(e))}
{allLogs
.filter((log) => logFilter.includes(log.level))
.filter(
(log) =>
!log.botId ||
(logFilter.includes('global') && log.botId === '*') ||
(logFilter.includes('currentBot') && log.botId !== '*')
)
.map((e) => this.renderEntry(e))}
<li className={logStyle.end}>{lang.tr('bottomPanel.logs.endOfLogs')}</li>
</ul>
)
Expand All @@ -197,6 +212,12 @@ class BottomPanel extends React.Component<Props, State> {
<Tabs className={style.tabs} onChange={this.handleTabChange} selectedTabId={this.state.selectedPanel}>
<Tab id="logs" className={style.tab} title={lang.tr('logs')} panel={LogsPanel} />
<Tab id="debug" className={style.tab} title={lang.tr('debug')} panel={<Debug ref={this.debugRef} />} />
<Tab
id="filter"
className={style.tab}
title={lang.tr('bottomPanel.logs.filter')}
panel={<Filter setLogFilter={(logFilter) => this.setState({ logFilter })} ref={this.filterRef} />}
/>

{this.state.selectedPanel === 'logs' && (
<Fragment>
Expand Down Expand Up @@ -244,6 +265,25 @@ class BottomPanel extends React.Component<Props, State> {
</ButtonGroup>
</Fragment>
)}

{this.state.selectedPanel === 'filter' && (
<Fragment>
<Tabs.Expander />
<div></div>
<ButtonGroup minimal>
<ToolTip content={lang.tr('refresh')}>
<Button
id="btn-filter-refresh"
icon="refresh"
small
onClick={this.filterRef.current?.loadConfiguration}
/>
</ToolTip>

{this.props.commonButtons}
</ButtonGroup>
</Fragment>
)}
</Tabs>
)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/studio-ui/src/web/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,9 @@
"endOfLogs": "End of logs",
"debug": {
"confUpdated": "Debug configuration updated successfully!"
}
},
"filter": "filter",
"botLevelOBS": "OBS: A log is considered to be from bot if bp.logger.forBot(...) is used"
},
"debugger": {
"fetchingEvent": "Fetching event...",
Expand Down
4 changes: 3 additions & 1 deletion packages/studio-ui/src/web/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,9 @@
"endOfLogs": "Fin de registros",
"debug": {
"confUpdated": "¡Configuración de depuración actualizada correctamente!"
}
},
"filter": "filtrar",
"botLevelOBS": "OBS: Se considera que un log proviene de un bot si se usa bp.logger.forBot(...)"
},
"depurador": {
"fetchingEvent": "Obteniendo evento ...",
Expand Down
4 changes: 3 additions & 1 deletion packages/studio-ui/src/web/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,9 @@
"endOfLogs": "Fin des journaux",
"debug": {
"confUpdated": "La configuration de débogage a été mise à jour avec succès!"
}
},
"filter": "filtre",
"botLevelOBS": "OBS : Un log est considéré comme provenant d'un bot si bp.logger.forBot(...) est utilisé"
},
"debugger": {
"fetchingEvent": "Récupération de l'événement...",
Expand Down

0 comments on commit e89fb36

Please sign in to comment.