Skip to content

Commit

Permalink
OPG-480: Alarm Table Panel scrolling issue fix
Browse files Browse the repository at this point in the history
  • Loading branch information
synqotik committed May 22, 2024
1 parent 796df1e commit 1f4d0bd
Show file tree
Hide file tree
Showing 15 changed files with 8,631 additions and 4,323 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"latex",
"plaintext",
"asciidoc"
]
],
"editor.tabSize": 2
}
12,070 changes: 8,130 additions & 3,940 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"homepage": "https://github.com/OpenNMS/grafana-plugin",
"devDependencies": {
"@antora/cli": "^3.1.4",
"@antora/site-generator-default": "^3.1.4",
"@antora/site-generator-default": "^3.1.7",
"@babel/core": "^7.24.5",
"@grafana/e2e": "^9.5.5",
"@grafana/e2e-selectors": "^9.5.5",
Expand All @@ -30,15 +30,15 @@
"@types/glob": "^8.1.0",
"@types/grafana": "github:CorpGlory/types-grafana",
"@types/jest": "^29.5.12",
"@types/jquery": "^3.5.13",
"@types/node": "^18.17.0",
"@types/jquery": "^3.5.29",
"@types/node": "^18.19.32",
"@types/react-router-dom": "^5.3.3",
"commander": "^11.0.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.1",
"eslint-webpack-plugin": "^4.0.1",
"fork-ts-checker-webpack-plugin": "^9.0.0",
"fs-extra": "^11.1.0",
"fs-extra": "^11.2.0",
"glob": "^8.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
Expand All @@ -48,12 +48,12 @@
"recursive-copy": "^2.0.13",
"replace-in-file-webpack-plugin": "^1.0.6",
"rimraf": "^5.0.0",
"sass": "^1.75.0",
"sass": "^1.76.0",
"sass-loader": "^14.2.1",
"specit": "^1.4.4",
"style-loader": "^4.0.0",
"swc-loader": "^0.2.6",
"ts-node": "^10.5.0",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.1.0",
"tslib": "^2.3.1",
"typescript": "^4.9.5",
Expand Down
44 changes: 24 additions & 20 deletions src/panels/alarm-table/AlarmTableControl.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react'
import { AppEvents, PanelProps } from '@grafana/data'
import { AppEvents, DataFrame, PanelProps } from '@grafana/data'
import { getAppEvents } from '@grafana/runtime'
import { Button, ContextMenu, Modal, Pagination, Tab, TabContent, Table, TabsBar } from '@grafana/ui'
import { AlarmTableMenu } from './AlarmTableMenu'
import { AlarmTableModalContent } from './modal/AlarmTableModalContent'
import { AlarmTableSelectionStyles } from './AlarmTableSelectionStyles'
import { AlarmTableControlProps } from './AlarmTableTypes'
import { useAlarm } from './hooks/useAlarm'
import { useAlarmProperties } from './hooks/useAlarmProperties'
import { useAlarmTableMenuActions } from './hooks/useAlarmTableMenuActions'
import { useAlarmTableConfigDefaults } from './hooks/useAlarmTableConfigDefaults'
Expand All @@ -14,34 +14,36 @@ import { useAlarmTableRowHighlighter } from './hooks/useAlarmTableRowHighlighter
import { useAlarmTableSelection } from './hooks/useAlarmTableSelection'
import { useAlarmTableModalTabs } from './hooks/useAlarmTableModalTabs'
import { useOpenNMSClient } from '../../hooks/useOpenNMSClient'
import { useAlarm } from './hooks/useAlarm'
import { AlarmTableModalContent } from './modal/AlarmTableModalContent'
import { capitalize } from 'lib/utils'

export const AlarmTableControl: React.FC<PanelProps<AlarmTableControlProps>> = (props) => {
const alarmIndexes = useRef<boolean[]>([] as boolean[])

const { state, setState, rowClicked, soloIndex } = useAlarmTableSelection(() => {
setDetailsModal(true)
})
const selectedAlarmIds = useRef<Set<number>>(new Set<number>())
const filteredData = useRef<DataFrame>(props?.data?.series[0])
const table = useRef<HTMLDivElement>(null)

const { client } = useOpenNMSClient(props.data?.request?.targets?.[0]?.datasource)
const { filteredProps, page, setPage, totalPages } = useAlarmProperties(props?.data?.series[0], props?.options?.alarmTable)
const { table, menu, menuOpen, setMenuOpen } = useAlarmTableMenu(alarmIndexes, rowClicked, filteredProps, setState)
const { page, setPage, totalPages } = useAlarmProperties(filteredData, props?.data?.series[0], props?.options?.alarmTable)

const { alarmControlState, setAlarmControlState, rowClicked, soloAlarmId } = useAlarmTableSelection(() => { setDetailsModal(true) })

const { menu, menuOpen, setMenuOpen } = useAlarmTableMenu(table, alarmIndexes, selectedAlarmIds, rowClicked, filteredData, setAlarmControlState)

const { actions, detailsModal, setDetailsModal } = useAlarmTableMenuActions(
state.indexes,
props?.data?.series?.[0]?.fields || [],
alarmControlState.selectedIndexes,
alarmControlState.selectedAlarmIds,
() => setMenuOpen(false),
(actionName: string, results: any[]) => displayActionNotice(actionName, results),
props?.options?.alarmTable?.alarmTableAdditional?.useGrafanaUser ?? false,
client)

const { tabActive, tabClick, resetTabs } = useAlarmTableModalTabs()
const { alarm, goToAlarm, alarmQuery } = useAlarm(props?.data?.series, soloIndex, client)
const { alarm, goToAlarm, alarmQuery } = useAlarm(filteredData, soloAlarmId, client)

const paginationRef = useRef<HTMLDivElement>(null)

useAlarmTableRowHighlighter(state, table)
useAlarmTableRowHighlighter(alarmControlState, table, filteredData)
useAlarmTableConfigDefaults(props.fieldConfig, props.onFieldConfigChange, props.options)

/**
Expand Down Expand Up @@ -86,9 +88,10 @@ export const AlarmTableControl: React.FC<PanelProps<AlarmTableControlProps>> = (
}

useEffect(() => {
alarmIndexes.current = state.indexes
alarmIndexes.current = [...alarmControlState.selectedIndexes]
selectedAlarmIds.current = new Set(alarmControlState.selectedAlarmIds)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state])
}, [alarmControlState])

return (
<div className={
Expand All @@ -106,18 +109,19 @@ export const AlarmTableControl: React.FC<PanelProps<AlarmTableControlProps>> = (
}>
<AlarmTableSelectionStyles />
<div className='alarm-table-wrapper'>
{alarmQuery ? <Table data={filteredProps} width={props.width} height={props.height - calcPaginationHeight()} /> :
<div>Select the Entity Datasource below, and choose an Alarm query to see results.</div>
{alarmQuery ?
<Table data={filteredData.current} width={props.width} height={props.height - calcPaginationHeight()} /> :
<div>Select the Entity Datasource below, and choose an Alarm query to see results.</div>
}
</div>
{menuOpen && <ContextMenu
x={menu.x}
y={menu.y}
onClose={() => {
resetTabs();
setMenuOpen(false);
resetTabs();
setMenuOpen(false);
}}
renderMenuItems={() => <AlarmTableMenu state={state} actions={actions} />}
renderMenuItems={() => <AlarmTableMenu state={alarmControlState} actions={actions} />}
/>}
<Modal isOpen={detailsModal} title='Alarm Detail' onDismiss={() => setDetailsModal(false)}>
<Button style={{ marginBottom: 12 }} onClick={goToAlarm}><i className='fa fa-external-link'></i>&nbsp;Full Details</Button>
Expand Down
92 changes: 47 additions & 45 deletions src/panels/alarm-table/AlarmTableData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,58 @@ import { AlarmTableDataState } from './AlarmTableTypes'
import { alarmTableDefaultColumns } from './constants'

interface AlarmTableDataProps {
onChange: Function;
context: any;
onChange: Function
context: any
}

export const AlarmTableData: React.FC<AlarmTableDataProps> = ({ onChange, context }) => {
const [alarmTableData, setAlarmTableData] = useState<AlarmTableDataState>(context?.options?.alarmTable?.alarmTableData || {
columns: alarmTableDefaultColumns
})
const [alarmTableData, setAlarmTableData] = useState<AlarmTableDataState>(context?.options?.alarmTable?.alarmTableData || {
columns: alarmTableDefaultColumns
})

useEffect(() => {
onChange(alarmTableData);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alarmTableData])
useEffect(() => {
onChange(alarmTableData)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alarmTableData])

const setAlarmTableState = (key, value) => {
const newState = { ...alarmTableData }
newState[key] = value
setAlarmTableData(newState)
}
const setAlarmTableState = (key, value) => {
const newState = { ...alarmTableData }
newState[key] = value
setAlarmTableData(newState)
}

return (
<div>
{/**
*
* Commented out for now. This is in the original table, but
* from what I can tell there was only ever one transformer
* written, so there's no need to have the ability to swap them
* TODO: Double check if we need the ability to 'transform' in different ways
*
* <OnmsInlineField label="Table Transform">
<Select
value={alarmTableData.transformType}
onChange={(val) => setAlarmTableState('transformType', val)}
options={[{ label: 'Table', value: 0 }]}
/>
</OnmsInlineField> */}
return (
<div>
{
/**
*
* Commented out for now. This is in the original table, but
* from what I can tell there was only ever one transformer
* written, so there's no need to have the ability to swap them
* TODO: Double check if we need the ability to 'transform' in different ways
*
* <OnmsInlineField label="Table Transform">
<Select
value={alarmTableData.transformType}
onChange={(val) => setAlarmTableState('transformType', val)}
options={[{ label: 'Table', value: 0 }]}
/>
</OnmsInlineField> */
}

<OnmsInlineField label="Columns">
<Select
placeholder='Add Column'
value={''}
onChange={(val) => {
const newColumns = [...alarmTableData.columns]
newColumns.push(val)
setAlarmTableState('columns', newColumns)
}}
options={context?.data?.[0]?.fields.map((field, index) => ({ ...field, value: index, label: field.name }))}
/>
</OnmsInlineField>
<DragList values={alarmTableData?.columns} onChange={(val) => { setAlarmTableState('columns', val) }} />
</div>
)
<OnmsInlineField label="Columns">
<Select
placeholder='Add Column'
value={''}
onChange={(val) => {
const newColumns = [...alarmTableData.columns]
newColumns.push(val)
setAlarmTableState('columns', newColumns)
}}
options={context?.data?.[0]?.fields.map((field, index) => ({ ...field, value: index, label: field.name }))}
/>
</OnmsInlineField>
<DragList values={alarmTableData?.columns} onChange={(val) => { setAlarmTableState('columns', val) }} />
</div>
)
}
46 changes: 44 additions & 2 deletions src/panels/alarm-table/AlarmTableHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
import { Field } from '@grafana/data'
import { DataFrame, Field } from '@grafana/data'

export const getAlarmIdFromFields = (fields: Field[], index: number) => {
return fields.find((d) => d.name === 'ID')?.values.get(index)
return fields.find((d) => d.name === 'ID')?.values.get(index)
}

// given an array of row elements (divs in the Alarm Table), return the alarmIds
// associated with the rows, in row order
// rows can be generated via something like: table.current?.querySelectorAll('.table-body div[role="row"]')
export const getAlarmIdsForRows = (rows: Element[], frame: DataFrame) => {
return rows.map(row => getAlarmIdFromRow(row, frame))
}

/**
* Find the current 0-based column index of the Alarm ID field.
* This should be the frame after column including/exclusion and column sorting have been applied.
*/
export const getColumnIndexOfAlarmId = (frame: DataFrame) => {
return frame.fields.findIndex(f => f.name === 'ID')
}

/**
* Get the Alarm ID from the table cell HTMLElement.
*/
export const getAlarmIdFromRow = (row: Element, frame: DataFrame) => {
// const row = cell.parentElement
const columnIndex = getColumnIndexOfAlarmId(frame)

if (row && columnIndex >= 0) {
const dataIndexCell = row?.childNodes?.[columnIndex] || null
const text = dataIndexCell?.textContent

const alarmId = Number(text)
return Number.isInteger(alarmId) ? alarmId : -1
}

return -1
}

/**
* Get the Alarm ID from the table cell Element.
*/
export const getAlarmIdFromCell = (cell: Element, frame: DataFrame) => {
const row = cell.parentElement

return row ? getAlarmIdFromRow(row, frame) : -1
}
4 changes: 2 additions & 2 deletions src/panels/alarm-table/AlarmTableMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface AlarmTableMenuProps {
}

export const AlarmTableMenu: React.FC<AlarmTableMenuProps> = ({ state,actions }) => {
const selectedCount = state.indexes.filter(d => d === true).length
const selectedCount = state.selectedAlarmIds.size
const suffix = selectedCount > 1 ? ` (${selectedCount})` : ''

let items = [
Expand Down Expand Up @@ -42,7 +42,7 @@ export const AlarmTableMenu: React.FC<AlarmTableMenuProps> = ({ state,actions })
return (
<Menu>
{items.map((item, index) => {
let elem = <MenuItem label={item.label} key={index} onClick={item.action} />
let elem = <MenuItem label={item.label} key={item.label} onClick={item.action} />

if (item.type === 'divider') {
elem = <div className={styles.divider}></div>
Expand Down
20 changes: 12 additions & 8 deletions src/panels/alarm-table/AlarmTableTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { SelectableValue } from '@grafana/data'

export interface AlarmTableControlState {
indexes: boolean[]
selectedAlarmIds: Set<number>
selectedIndexes: boolean[]
lastClicked: number
lastClickedAlarmId: number
}

export interface AlarmTableAdditionalState {
Expand Down Expand Up @@ -37,14 +39,16 @@ export interface AlarmTableColumnSizeState {
columnSizes: AlarmTableColumnSizeItem[]
}

export interface AlarmTableOptionsState {
alarmTableAdditional: AlarmTableAdditionalState
alarmTableAlarms: AlarmTableAlarmDataState
alarmTableData: AlarmTableDataState
alarmTablePaging: AlarmTablePaginationState
alarmTableColumnSizes?: AlarmTableColumnSizeState
}

export interface AlarmTableControlProps {
alarmTable: {
alarmTableAdditional: AlarmTableAdditionalState
alarmTableAlarms: AlarmTableAlarmDataState
alarmTableData: AlarmTableAlarmDataState
alarmTablePaging: AlarmTablePaginationState
alarmTableColumnSizes?: AlarmTableColumnSizeState
}
alarmTable: AlarmTableOptionsState
}

export interface AlarmTableControlActions {
Expand Down
1 change: 1 addition & 0 deletions src/panels/alarm-table/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export const alarmTableDefaultColumns = [
{ label: 'Location', value: 8 },
{ label: 'Node Label', value: 14 },
{ label: 'Log Message', value: 9 },
{ label: 'ID', value: 0 },
]
Loading

0 comments on commit 1f4d0bd

Please sign in to comment.