@@ -3,7 +3,7 @@ import * as _ from 'lodash-es';
33import * as React from 'react' ;
44import * as classNames from 'classnames' ;
55import * as PropTypes from 'prop-types' ;
6- import { useParams , Link } from 'react-router-dom-v5-compat' ;
6+ import { Link , useParams } from 'react-router-dom-v5-compat' ;
77import { Helmet } from 'react-helmet' ;
88import { Chip , ChipGroup } from '@patternfly/react-core' ;
99import { Trans , useTranslation } from 'react-i18next' ;
@@ -61,7 +61,7 @@ export const typeFilter = (eventType, event) => {
6161} ;
6262
6363const kindFilter = ( reference , { involvedObject } ) => {
64- if ( reference === 'all' ) {
64+ if ( ! reference ) {
6565 return true ;
6666 }
6767 const kinds = reference . split ( ',' ) ;
@@ -179,29 +179,28 @@ export const EventsList = (props) => {
179179 const { t } = useTranslation ( ) ;
180180 const [ type , setType ] = React . useState ( 'all' ) ;
181181 const [ textFilter , setTextFilter ] = React . useState ( '' ) ;
182- const resourceTypeAll = 'all' ;
183182 const { ns } = useParams ( ) ;
184- const [ selected , setSelected ] = React . useState ( new Set ( [ resourceTypeAll ] ) ) ;
183+ const [ selected , setSelected ] = React . useState ( new Set ( [ ] ) ) ;
185184 const eventTypes = {
186185 all : t ( 'public~All types' ) ,
187186 normal : t ( 'public~Normal' ) ,
188187 warning : t ( 'public~Warning' ) ,
189188 } ;
190189
191190 const toggleSelected = ( selection ) => {
192- if ( selected . has ( resourceTypeAll ) || selection === resourceTypeAll ) {
193- setSelected ( new Set ( [ selection ] ) ) ;
194- } else {
195- const updateItems = new Set ( selected ) ;
191+ setSelected ( ( prev ) => {
192+ const updateItems = new Set ( prev ) ;
196193 updateItems . has ( selection ) ? updateItems . delete ( selection ) : updateItems . add ( selection ) ;
197- setSelected ( updateItems ) ;
198- }
194+ return updateItems ;
195+ } ) ;
199196 } ;
200197
201198 const removeResource = ( selection ) => {
202- const updateItems = new Set ( selected ) ;
203- updateItems . delete ( selection ) ;
204- setSelected ( updateItems ) ;
199+ setSelected ( ( prev ) => {
200+ const updateItems = new Set ( prev ) ;
201+ updateItems . delete ( selection ) ;
202+ return updateItems ;
203+ } ) ;
205204 } ;
206205
207206 const clearSelection = ( ) => {
@@ -241,11 +240,10 @@ export const EventsList = (props) => {
241240 expandedText = { t ( 'public~Show less' ) }
242241 >
243242 { [ ...selected ] . map ( ( chip ) => {
244- const chipString = chip === resourceTypeAll ? t ( 'public~All' ) : chip ;
245243 return (
246244 < Chip key = { chip } onClick = { ( ) => removeResource ( chip ) } >
247- < ResourceIcon kind = { chipString } />
248- { kindForReference ( chipString ) }
245+ < ResourceIcon kind = { chip } />
246+ { kindForReference ( chip ) }
249247 </ Chip >
250248 ) ;
251249 } ) }
@@ -259,11 +257,7 @@ export const EventsList = (props) => {
259257 namespace = { ns }
260258 key = { [ ...selected ] . join ( ',' ) }
261259 type = { type }
262- kind = {
263- selected . has ( resourceTypeAll ) || selected . size === 0
264- ? resourceTypeAll
265- : [ ...selected ] . join ( ',' )
266- }
260+ kind = { [ ...selected ] . join ( ',' ) }
267261 mock = { props . mock }
268262 textFilter = { textFilter }
269263 />
@@ -289,8 +283,12 @@ export const NoMatchingEvents = ({ allCount }) => {
289283 < div className = "cos-status-box__title" > { t ( 'public~No matching events' ) } </ div >
290284 < div className = "pf-v5-u-text-align-center cos-status-box__detail" >
291285 { allCount >= maxMessages
292- ? t ( 'public~{{allCount}}+ events exist, but none match the current filter' , { allCount } )
293- : t ( 'public~{{allCount}} events exist, but none match the current filter' , { allCount } ) }
286+ ? t ( 'public~{{count}}+ event exist, but none match the current filter' , {
287+ count : maxMessages ,
288+ } )
289+ : t ( 'public~{{count}} event exist, but none match the current filter' , {
290+ count : allCount ,
291+ } ) }
294292 </ div >
295293 </ Box >
296294 ) ;
@@ -328,119 +326,112 @@ export const EventStreamPage = withStartGuide(({ noProjectsAvailable, ...rest })
328326 ) ;
329327} ) ;
330328
331- const EventStream = ( props ) => {
329+ const EventStream = ( {
330+ namespace,
331+ fieldSelector,
332+ mock,
333+ resourceEventStream,
334+ kind,
335+ type,
336+ filter,
337+ textFilter,
338+ } ) => {
339+ const { t } = useTranslation ( ) ;
332340 const [ active , setActive ] = React . useState ( true ) ;
333- const [ sortedMessages , setSortedMessages ] = React . useState ( [ ] ) ;
334- const [ filteredEvents , setFilteredEvents ] = React . useState ( [ ] ) ;
341+ const [ sortedEvents , setSortedEvents ] = React . useState ( [ ] ) ;
335342 const [ error , setError ] = React . useState ( null ) ;
336343 const [ loading , setLoading ] = React . useState ( true ) ;
344+ const ws = React . useRef ( null ) ;
337345
338- const { t } = useTranslation ( ) ;
339-
340- const { fieldSelector, mock, resourceEventStream } = props ;
341-
342- let messages = { } ;
343- let ws ;
346+ const filteredEvents = React . useMemo ( ( ) => {
347+ return filterEvents ( sortedEvents , { kind, type, filter, textFilter } ) . slice ( 0 , maxMessages ) ;
348+ } , [ sortedEvents , kind , type , filter , textFilter ] ) ;
344349
345- const wsInit = ( ns ) => {
346- const params = { ns } ;
347- if ( fieldSelector ) {
348- params . queryParams = { fieldSelector : encodeURIComponent ( fieldSelector ) } ;
349- }
350+ // Handle websocket setup and teardown when dependent props change
351+ React . useEffect ( ( ) => {
352+ ws . current ?. destroy ( ) ;
353+ if ( ! mock ) {
354+ const webSocketID = `${ namespace || 'all' } -sysevents` ;
355+ const watchURLOptions = {
356+ ...( namespace ? { ns : namespace } : { } ) ,
357+ ...( fieldSelector
358+ ? {
359+ queryParams : {
360+ fieldSelector : encodeURIComponent ( fieldSelector ) ,
361+ } ,
362+ }
363+ : { } ) ,
364+ } ;
365+ const path = watchURL ( EventModel , watchURLOptions ) ;
366+ const webSocketOptions = {
367+ host : 'auto' ,
368+ reconnect : true ,
369+ path,
370+ jsonParse : true ,
371+ bufferFlushInterval : flushInterval ,
372+ bufferMax : maxMessages ,
373+ } ;
350374
351- ws = new WSFactory ( `${ ns || 'all' } -sysevents` , {
352- host : 'auto' ,
353- reconnect : true ,
354- path : watchURL ( EventModel , params ) ,
355- jsonParse : true ,
356- bufferFlushInterval : flushInterval ,
357- bufferMax : maxMessages ,
358- } )
359- . onbulkmessage ( ( events ) => {
360- events . forEach ( ( { object, type } ) => {
361- const uid = object . metadata . uid ;
362-
363- switch ( type ) {
364- case 'ADDED' :
365- case 'MODIFIED' :
366- if ( messages [ uid ] && messages [ uid ] . count > object . count ) {
367- // We already have a more recent version of this message stored, so skip this one
368- return ;
375+ ws . current = new WSFactory ( webSocketID , webSocketOptions )
376+ . onbulkmessage ( ( messages ) => {
377+ // Make one update to state per batch of events.
378+ setSortedEvents ( ( currentSortedEvents ) => {
379+ const topEvents = currentSortedEvents . slice ( 0 , maxMessages ) ;
380+ const batch = messages . reduce ( ( acc , { object, type : eventType } ) => {
381+ const uid = object . metadata . uid ;
382+ switch ( eventType ) {
383+ case 'ADDED' :
384+ case 'MODIFIED' :
385+ if ( acc [ uid ] && acc [ uid ] . count > object . count ) {
386+ // We already have a more recent version of this message stored, so skip this one
387+ return acc ;
388+ }
389+ return { ...acc , [ uid ] : object } ;
390+ case 'DELETED' :
391+ return _ . omit ( acc , uid ) ;
392+ default :
393+ // eslint-disable-next-line no-console
394+ console . error ( `UNHANDLED EVENT: ${ eventType } ` ) ;
395+ return acc ;
369396 }
370- messages [ uid ] = object ;
371- break ;
372- case 'DELETED' :
373- delete messages [ uid ] ;
374- break ;
375- default :
376- // eslint-disable-next-line no-console
377- console . error ( `UNHANDLED EVENT: ${ type } ` ) ;
378- return ;
397+ } , _ . keyBy ( topEvents , 'metadata.uid' ) ) ;
398+ return sortEvents ( batch ) ;
399+ } ) ;
400+ } )
401+ . onopen ( ( ) => {
402+ setError ( false ) ;
403+ setLoading ( false ) ;
404+ } )
405+ . onclose ( ( evt ) => {
406+ if ( evt ?. wasClean === false ) {
407+ setError ( evt . reason || t ( 'public~Connection did not close cleanly.' ) ) ;
379408 }
409+ } )
410+ . onerror ( ( ) => {
411+ setError ( true ) ;
380412 } ) ;
381- flushMessages ( ) ;
382- } )
383- . onopen ( ( ) => {
384- setError ( false ) ;
385- setLoading ( false ) ;
386- } )
387- . onclose ( ( evt ) => {
388- if ( evt && evt . wasClean === false ) {
389- setError ( evt . reason || t ( 'public~Connection did not close cleanly.' ) ) ;
390- }
391- } )
392- . onerror ( ( ) => {
393- setError ( true ) ;
394- } ) ;
395- } ;
413+ }
414+ return ( ) => {
415+ ws . current ?. destroy ( ) ;
416+ } ;
417+ } , [ namespace , fieldSelector , mock , t ] ) ;
396418
419+ // Pause/unpause the websocket when the active state changes
397420 React . useEffect ( ( ) => {
398- // If the namespace has changed, create a new WebSocket with the new namespace
399- if ( ! props . mock ) {
400- wsInit ( props . namespace ) ;
401- // Reset the messages and events
402- setSortedMessages ( [ ] ) ;
403- setFilteredEvents ( [ ] ) ;
404-
405- return ( ) => {
406- ws && ws . destroy ( ) ;
407- } ;
421+ if ( active ) {
422+ ws . current ?. unpause ( ) ;
423+ } else {
424+ ws . current ?. pause ( ) ;
408425 }
409- // eslint-disable-next-line react-hooks/exhaustive-deps
410- } , [ props . namespace ] ) ;
411-
412- const prevSortedMessages = React . useRef ( [ ] ) ;
426+ } , [ active ] ) ;
413427
414428 const toggleStream = ( ) => {
415429 setActive ( ( prev ) => ! prev ) ;
416- prevSortedMessages . current = sortedMessages ;
417- } ;
418-
419- React . useEffect ( ( ) => {
420- // If the filter has changed, update the filteredEvents
421- setFilteredEvents (
422- EventStream . filterEvents ( active ? sortedMessages : prevSortedMessages . current , props ) ,
423- ) ;
424- } , [ active , sortedMessages , props , prevSortedMessages ] ) ;
425-
426- // Messages can come in extremely fast when the buffer flushes.
427- // Instead of calling setState() on every single message, let onmessage()
428- // update an instance variable, and throttle the actual UI update (see constructor)
429- const flushMessages = ( ) => {
430- const sorted = sortEvents ( messages ) ;
431- sorted . splice ( maxMessages ) ;
432- setSortedMessages ( sorted ) ;
433- setFilteredEvents (
434- EventStream . filterEvents ( active ? sorted : prevSortedMessages . current , props ) ,
435- ) ;
436-
437- // Shrink messages back to maxMessages messages, to stop it growing indefinitely
438- messages = _ . keyBy ( sorted , 'metadata.uid' ) ;
439430 } ;
440431
441432 const count = filteredEvents . length ;
442- const allCount = sortedMessages . length ;
443- const noEvents = allCount === 0 && ws && ws . bufferSize ( ) === 0 ;
433+ const allCount = sortedEvents . length ;
434+ const noEvents = allCount === 0 ;
444435 const noMatches = allCount > 0 && count === 0 ;
445436 let sysEventStatus , statusBtnTxt ;
446437
@@ -455,7 +446,9 @@ const EventStream = (props) => {
455446 statusBtnTxt = (
456447 < span className = "co-sysevent-stream__connection-error" >
457448 { _ . isString ( error )
458- ? t ( 'public~Error connecting to event stream: { error }' , { error } )
449+ ? t ( 'public~Error connecting to event stream: { error }' , {
450+ error,
451+ } )
459452 : t ( 'public~Error connecting to event stream' ) }
460453 </ span >
461454 ) ;
@@ -475,10 +468,7 @@ const EventStream = (props) => {
475468 const messageCount =
476469 count < maxMessages
477470 ? t ( 'public~Showing {{count}} event' , { count } )
478- : t ( 'public~Showing {{messageCount}} of {{allCount}}+ events' , {
479- messageCount : count ,
480- allCount,
481- } ) ;
471+ : t ( 'public~Showing most recent {{count}} event' , { count } ) ;
482472
483473 return (
484474 < div className = "co-m-pane__body" >
@@ -509,7 +499,7 @@ const EventStream = (props) => {
509499
510500EventStream . defaultProps = {
511501 type : 'all' ,
512- kind : 'all ' ,
502+ kind : '' ,
513503 mock : false ,
514504} ;
515505
@@ -523,7 +513,7 @@ EventStream.propTypes = {
523513 textFilter : PropTypes . string ,
524514} ;
525515
526- EventStream . filterEvents = ( messages , { kind, type, filter, textFilter } ) => {
516+ const filterEvents = ( messages , { kind, type, filter, textFilter } ) => {
527517 // Don't use `fuzzy` because it results in some surprising matches in long event messages.
528518 // Instead perform an exact substring match on each word in the text filter.
529519 const words = _ . uniq ( _ . toLower ( textFilter ) . match ( / \S + / g) ) . sort ( ( a , b ) => {
0 commit comments