Skip to content

Commit

Permalink
Rewrote the logs panel to be fully custom
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-sammons committed Jan 8, 2024
1 parent 68411b6 commit 077b6bc
Show file tree
Hide file tree
Showing 12 changed files with 703 additions and 119 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/react": "17.0.14",
"@types/react-test-renderer": "16.9.2",
"@types/react-virtualized-auto-sizer": "1.0.0",
"@types/react-window": "^1.8.8",
"@types/semver": "7.3.7",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/datasource/components/FieldValueFrequency.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React from 'react'
import { Field, ValueFrequency } from 'datasource/types';
import { Toggletip } from './Toggletip';
import { HorizontalGroup, VerticalGroup, Button } from '@grafana/ui';
Expand Down
302 changes: 302 additions & 0 deletions src/datasource/components/Logs/LogsCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import React from 'react'
import { Log } from 'datasource/types'
import { LogColumnType, LogColumn } from 'datasource/components/Logs/types'
import getLogTableContext from 'datasource/components/Logs/context'
import { Button } from '@grafana/ui'


interface LogKeyValProps {
field: string,
val: any
}


const LogKeyVal = ({ field, val }: LogKeyValProps) => {
return (<div style={{display:'inline-block', paddingRight: '10px'}}>
<div style={{backgroundColor: 'grey', display: 'inline', borderRadius: '6px', marginRight: '3px', padding: '2px 4px'}}>
{field + ":"}

</div>
<div style={{display: 'inline-block'}}>
{val}
</div>
</div>);
}

interface ExpandedLogKeyValProps {
field: string,
val: any
}


const ExpandedLogKeyVal = ({ field, val }: ExpandedLogKeyValProps) => {
return (
<tr>
<td style={{}}>
{field + ":"}

</td>
<td>
{val}
</td>
</tr>);
}

interface ExpandedDocumentProps {
log: Log,
index: number,
datasourceUid: string,
datasourceName: string
}


const ExpandedDocument = ({ log, index, datasourceUid, datasourceName, }: ExpandedDocumentProps) => {
const { setSize, windowWidth } = getLogTableContext();
const root = React.useRef<HTMLDivElement>();
React.useEffect(() => {
if (root.current) {
setSize(index, root.current.getBoundingClientRect().height);
}
}, [windowWidth]);

const link = {
datasource: datasourceUid,
queries: [{
query: log.get("trace_id"), // TODO: This should be user configurable
refId: "A",
}],
range:{
from: "now-15m", // TODO: This shouldn't be hardcoded
to: "now", // TODO: This also shouldn't be hardcoded
},
};

const orgId = 1; // TODO: This shouldn't be hardcoded
const protocol = window.location.protocol.toString();
const hostname = window.location.hostname.toString();
const port = window.location.port.toString();

/*
* The format for the Grafana Explore UI (in a urlencoded form):
*'https://<grafana URL>/explore?left={"datasource":"<datasource UID>","queries":[{"query":"<trace ID>","refId":"A"}],"range":{"from":"now-15m"," to":"now"}}&orgId=<org ID>'
*/
const formattedLink = `${protocol}//${hostname}${port ? ":" + port : ""}/explore?left=${encodeURIComponent(JSON.stringify(link))}&orgId=${orgId}`

// TODO: We should add an icon here as well
return (
<div ref={root}>
<span style={
{
fontWeight: 'bold',
paddingTop: '25px',
paddingBottom: '25px'
}
}>
Expanded Document
</span>
<table>
<tr>
<th>
Field
</th>
<th>
Value
</th>

</tr>
{
Array.from(log.keys()).map((key) => (
<ExpandedLogKeyVal
field={key}
val={log.get(key)}
key={key}
/>
))
}
</table>
{
// TODO: Should this value be configurable?
log.has('trace_id') ?
(
<a href={formattedLink}>
<Button
size={'sm'}
variant={'primary'}
>
View in {datasourceName}
</Button>
</a>
)
: ''
}

</div>
);
}


const DocumentCell = (log: Log, style: any, rowIndex: number, expanded: boolean, datasourceUid: string, datasourceName: string) => (
<div
style={{
display: 'inline-block',
lineHeight: '2em',
fontFamily: 'monospace',
fontSize: '12px',
overflow: 'hidden',
paddingTop: '10px',
...style
}}
>
<div style={{maxHeight: '115px', overflow: 'hidden'}}>
{
Array.from(log.keys()).map((key) => (
<LogKeyVal
field={key}
val={log.get(key)}
key={key}
/>
))
}
</div>
{
expanded ?
(<ExpandedDocument
log={log}
index={rowIndex}
datasourceUid={datasourceUid}
datasourceName={datasourceName}
/>)

: ''
}
</div>
)

const TimestampCell = (timestamp: number, style: any, rowIndex: number, expandedRows: boolean[], onClick: ((index: number) => void)) => {
const getFoldIcon = () => {
if (expandedRows[rowIndex]) {
return 'angle-down';
}
return 'angle-right';
};

return (
<div style={
{
alignContent: 'center',
textAlign: 'center',
fontFamily: 'monospace',
fontSize: '12px',
paddingTop: '10px',
display: 'flex',
...style
}}>
<div
style={{
paddingLeft: '10px',
paddingRight: '15px'
}}
>
<Button
size={'sm'}
variant={'secondary'}
icon={getFoldIcon()}
onClick={() => onClick(rowIndex)}
/>
</div>
<div>
{timestamp === undefined ? '' : new Date(timestamp).toISOString()}
</div>
</div>
);
}

const FieldCell = () => {
return (<></>);
}

const HeaderCell = (column: LogColumn, style) => {
// TODO: Handle field types
// const log = data.logs[rowIndex];
// const _timeField = data.timeField;

// TODO: Implement sorting?
return (
<div
style={{
paddingLeft: column.logColumnType === LogColumnType.TIME ? '10px' : '0px',
...style
}}
>
{column.logColumnType === LogColumnType.TIME ? 'Time' : 'Document'}
</div>
)
}

// Either expands or collapses the row, depending on the state its currently in
const invertRow = (expandedRows: boolean[], rowIndex: number): boolean[] => {
expandedRows[rowIndex] = !expandedRows[rowIndex]
return expandedRows
}

// If the row isn't being expanded, then shrink it back to its original size.
// NOTE: Because of how we handle it down below, "shrink it to its original size"
// effectively means set it to 0.
const shrinkRows = (expandedRows: boolean[], rowIndex: number, setSize: (index: number, value: number) => void): boolean => {
if (!expandedRows[rowIndex]) {
setSize(rowIndex, 0);
return true;
}
return false;
}



const LogCell = ({ columnIndex, rowIndex, style, data }) => {
const log = data.logs[rowIndex];
const timestamp = data.timestamps[rowIndex];
const column = data.columns[columnIndex];
const setExpandedRowsAndReRender = data.setExpandedRowsAndReRender
const expandedRows = data.expandedRows
const datasourceUid = data.datasourceUid
const datasourceName = data.datasourceName
const { setSize } = getLogTableContext();


// TODO: Ignoring for now as these will be used in a future pass
// const _timeField = data.timeField;
// const _setColumns = data.setColumns

const handleOnClick = (rowIndex: number): any => {
const newExpandedRows = invertRow(expandedRows, rowIndex);
shrinkRows(newExpandedRows, rowIndex, setSize);
setExpandedRowsAndReRender([...newExpandedRows], rowIndex);
}

// Handle drawing the borders for the entire row
if (columnIndex === 0) {
style['borderLeft'] = '1px solid rgb(71, 71, 71)'
}
style['borderTop'] = '1px solid rgb(71, 71, 71)'
style['borderBottom'] = '1px solid rgb(71, 71, 71)'

if (columnIndex === data.columns.length - 1) {
style['borderRight'] = '1px solid rgb(71, 71, 71)'
}

// Header row
if (rowIndex === 0) {
return HeaderCell(column, style)

}

if (column.logColumnType === LogColumnType.TIME) {
return TimestampCell(timestamp, style, rowIndex, expandedRows, handleOnClick);
} else if (column.logColumnType === LogColumnType.DOCUMENT) {
return DocumentCell(log, style, rowIndex, expandedRows[rowIndex], datasourceUid, datasourceName);
} else {
return FieldCell();
}
};

export default LogCell
Loading

0 comments on commit 077b6bc

Please sign in to comment.