Skip to content

Commit

Permalink
Add Events view
Browse files Browse the repository at this point in the history
Changes on the VM Details -> Overview Card:
1. display last 2 events that are associated with the VM
2. provide a "View All" link to a new sub-page: the Events view.

Events view features:
1. display events in a table with 3 columns:
  a) severity
  b) date (includes time)
  c) message
2. allow filtering and sorting for all columns via toolbar
3. limit the max number of events that can be fetched to 500
4. do incremental refresh by fetching only new events
  • Loading branch information
rszwajko committed Jun 14, 2022
1 parent 1da925a commit ea9a10e
Show file tree
Hide file tree
Showing 19 changed files with 657 additions and 11 deletions.
57 changes: 56 additions & 1 deletion src/actions/userMessages.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import {
ADD_LAST_VM_EVENTS,
ADD_USER_MESSAGE,
ADD_VM_EVENTS,
AUTO_ACKNOWLEDGE,
CLEAR_USER_MSGS,
DISMISS_EVENT,
DISMISS_USER_MSG,
GET_ALL_EVENTS,
GET_VM_EVENTS,
SAVE_EVENT_FILTERS,
SET_EVENT_SORT,
SET_USERMSG_NOTIFIED,
SET_SERVER_MESSAGES,
GET_ALL_EVENTS,
} from '_/constants'

export function addUserMessage ({ message, messageDescriptor, type = '' }) {
Expand Down Expand Up @@ -77,3 +82,53 @@ export function setServerMessages ({ messages }) {
export function getAllEvents () {
return { type: GET_ALL_EVENTS }
}

export function addVmEvents ({ events = [], vmId }) {
return {
type: ADD_VM_EVENTS,
payload: {
events,
vmId,
},
}
}

export function addLastVmEvents ({ events = [], vmId }) {
return {
type: ADD_LAST_VM_EVENTS,
payload: {
events,
vmId,
},
}
}

export function getVmEvents ({ vmId, vmName, newestEventId = 0, maxItems = 0 }) {
return {
type: GET_VM_EVENTS,
payload: {
vmId,
vmName,
newestEventId,
maxItems,
},
}
}

export function setEventSort ({ sort }) {
return {
type: SET_EVENT_SORT,
payload: {
sort,
},
}
}

export function saveEventFilters ({ filters }) {
return {
type: SAVE_EVENT_FILTERS,
payload: {
filters,
},
}
}
87 changes: 87 additions & 0 deletions src/components/Events/EventFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { enumMsg, withMsg } from '_/intl'
import { saveEventFilters } from '_/actions'
import { localeCompare, toJS } from '_/helpers'
import moment from 'moment'

import { Filters } from '_/components/Toolbar'

export const SEVERITY = 'severity'
export const DATE = 'date'
export const MESSAGE = 'message'
export const UNKNOWN = 'unknown'

export const EVENT_SEVERITY = {
error: 3,
warning: 2,
normal: 1,
[UNKNOWN]: 0,
}

export function filterEvents ({ events, severityFilters, dateFilters, messageFilters }) {
return events?.filter(({ severity, time, description }) => {
const ackFromSeverity = !severityFilters?.length || severityFilters?.some(level => level === severity)
const ackFromTime = !dateFilters?.length || dateFilters?.some(isoDateStr => moment(time).isSame(isoDateStr, 'day'))
const ackFromMessage = !messageFilters?.length || messageFilters?.some(str => description?.includes(str))
return ackFromSeverity && ackFromTime && ackFromMessage
})
}

const composeSeverity = (msg, locale) => {
return {
id: SEVERITY,
title: msg.severity(),
placeholder: msg.eventsFilterTypePlaceholderSeverity(),
filterValues: Object.entries(
Object.keys(EVENT_SEVERITY)
.map((status) => ({ title: enumMsg('EventSeverity', status, msg), id: status }))
.reduce((acc, { title, id }) => {
acc[title] = { ...acc[title], [id]: id }
return acc
}, {}))
.map(([title, ids]) => ({ title, ids }))
.sort((a, b) => localeCompare(a.title, b.title, locale)),
}
}

const EventFilters = ({ msg, locale, selectedFilters = {}, onFilterUpdate }) => {
const filterTypes = useMemo(() => [
composeSeverity(msg, locale),
{
id: DATE,
title: msg.date(),
datePicker: true,
},
{
id: MESSAGE,
title: msg.message(),
placeholder: msg.eventsFilterTypePlaceholderMessage(),
},
], [msg, locale])
return (
<Filters
selectedFilters={selectedFilters}
onFilterUpdate={onFilterUpdate}
filterTypes={filterTypes}
textBasedFilterId={MESSAGE}
/>
)
}

EventFilters.propTypes = {
selectedFilters: PropTypes.object,
onFilterUpdate: PropTypes.func.isRequired,
msg: PropTypes.object.isRequired,
locale: PropTypes.string.isRequired,
}

export default connect(
({ userMessages }) => ({
selectedFilters: toJS(userMessages.get('eventFilters')),
}),
(dispatch) => ({
onFilterUpdate: (filters) => dispatch(saveEventFilters({ filters })),
})
)(withMsg(EventFilters))
44 changes: 44 additions & 0 deletions src/components/Events/EventSort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Sort } from '_/components/Toolbar'
import { setEventSort } from '_/actions'
import { withMsg } from '_/intl'
import { toJS } from '_/helpers'

import { SEVERITY, DATE, MESSAGE } from './EventFilters'

export const SortFields = {
[SEVERITY]: {
id: SEVERITY,
messageDescriptor: { id: 'severity' },
},
[DATE]: {
id: DATE,
messageDescriptor: { id: 'date' },
},
[MESSAGE]: {
id: MESSAGE,
messageDescriptor: { id: 'message' },
},
}

const EventSort = ({ sort = { ...SortFields[DATE], isAsc: false }, onSortChange }) => <Sort sort={sort} onSortChange={onSortChange} SortFields={SortFields}/>

EventSort.propTypes = {
sort: PropTypes.shape({
id: PropTypes.string.isRequired,
messageDescriptor: PropTypes.object.isRequired,
isAsc: PropTypes.bool,
}),
onSortChange: PropTypes.func.isRequired,
}

export default connect(
({ userMessages }) => ({
sort: toJS(userMessages.get('eventSort')),
}),
(dispatch) => ({
onSortChange: (sort) => dispatch(setEventSort({ sort })),
})
)(withMsg(EventSort))
18 changes: 18 additions & 0 deletions src/components/Events/EventStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ExclamationTriangleIcon, ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons'

export const EventStatus = ({ severity }) => {
switch (severity) {
case 'error':
return <ExclamationCircleIcon color='#c9190b'/>
case 'warning':
return <ExclamationTriangleIcon color='#f0ab00'/>
default:
return null
}
}

EventStatus.propTypes = {
severity: PropTypes.oneOf(['error', 'warning', 'normal']),
}
Loading

0 comments on commit ea9a10e

Please sign in to comment.