import React , { useEffect , useState } from 'react' ; import ReactDOM from 'react-dom' ; import { Expression , GridDataAutoCompleteHandler } from 'react-filter-box' ; import FilterBox from './Components/FilterBox' ; import Portal from './Components/Portal' ; import StatisticsTable from './Components/StatisticsTable' ; import { ToggleHistoryStatisticsButton } from './Components/ToggleHistoryStatisticsButton' ; import { DefinitionPluginParams } from './types' ; import { get } from './utils/api' ; import { filter } from './utils/misc' ; class DefinitionFilterAutoCompleteHandler extends GridDataAutoCompleteHandler { query = '' ; setQuery ( query ) { this . query = query ; } needCategories ( ) { return super . needCategories ( ) . filter ( ( value ) => ! this . query . includes ( value ) ) ; } needOperators ( parsedCategory ) { if ( parsedCategory === 'started' ) { return [ 'after' ] ; } if ( parsedCategory === 'finished' ) { return [ 'before' ] ; } if ( parsedCategory === 'maxResults' ) { return [ 'is' ] ; } return [ ] ; } needValues ( parsedCategory , parsedOperator ) { if ( parsedOperator === 'after' || parsedOperator === 'before' ) { return [ { customType : 'date' } ] ; } return super . needValues ( parsedCategory , parsedOperator ) ; } } const DefinitionFilterOptions = [ { columnField : 'started' , type : 'date' , } , { columnField : 'finished' , type : 'date' , } , { columnField : 'maxResults' , type : 'text' , } , ] ; const initialState = { viewer : null , statistics : null , } ; const hooks = { setViewer : ( viewer ) => ( initialState . viewer = viewer ) , setStatistics : ( node ) => ( initialState . statistics = node ) , } ; const Plugin = ( { root , api , processDefinitionId } ) => { const [ autoCompleteHandler ] = useState ( new DefinitionFilterAutoCompleteHandler ( [ ] , DefinitionFilterOptions ) ) ; const [ expressions , setExpressions ] = useState ( [ ] as Expression [ ] ) ; const [ query , setQuery ] = useState ( { } as Record < string , string | null > ) ; const [ viewer , setViewer ] = useState ( initialState . viewer ) ; const [ statistics , setStatistics ] = useState ( initialState . statistics ) ; hooks . setViewer = setViewer ; hooks . setStatistics = setStatistics ; const [ activities , setActivities ] = useState ( [ ] as any [ ] ) ; const [ tokens , setTokens ] = useState ( [ ] as Element [ ] ) ; const [ showTokens , setShowTokens ] = useState ( false ) ; useEffect ( ( ) => { if ( Object . keys ( query ) . length > 0 ) { ( async ( ) => { const activities = await get ( api , '/history/activity-instance' , { ... query , processDefinitionId , } ) ; setActivities ( activities ) ; } ) ( ) ; } } , [ query ] ) ; useEffect ( ( ) => { if ( expressions . length > 0 ) { const query = { sortBy : 'endTime' , sortOrder : 'desc' , maxResults : 1000 , } ; for ( const { category , operator , value } of expressions ) { if ( category === 'started' && operator === 'after' && ! isNaN ( new Date ( `${ value }` ) . getTime ( ) ) ) { query [ 'startedAfter' ] = `${ value }T00:00:00.000+0000` ; } else if ( category === 'finished' && operator === 'before' && ! isNaN ( new Date ( `${ value }` ) . getTime ( ) ) ) { query [ 'finishedBefore' ] = `${ value }T00:00:00.000+0000` ; } else if ( category === 'maxResults' && operator == 'is' && ! isNaN ( parseInt ( `${ value }` , 10 ) ) ) { query [ 'maxResults' ] = `${ value }` ; } } setQuery ( query ) ; } else { const weekAgo = new Date ( new Date ( ) - 1000 * 3600 * 24 * 7 ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; const tomorrow = new Date ( new Date ( ) - 1000 * 3600 * 24 * - 1 ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; setQuery ( { sortBy : 'endTime' , sortOrder : 'desc' , startedAfter : `${ weekAgo }T00:00:00.000+0000` , finishedBefore : `${ tomorrow }T00:00:00.000+0000` , maxResults : '1000' , } ) ; } } , [ expressions ] ) ; useEffect ( ( ) => { if ( viewer ) { const toggleHistoryStatisticsButton = document . createElement ( 'div' ) ; toggleHistoryStatisticsButton . style . cssText = `
          position: absolute;
          right: 15px;
          top: 60px;
        ` ; viewer . _container . appendChild ( toggleHistoryStatisticsButton ) ; ReactDOM . render ( < React . StrictMode > < ToggleHistoryStatisticsButton onToggleHistoryStatistics = { ( value ) => { setShowTokens ( value ) ; } } /> < /React.StrictMode>, toggleHistoryStatisticsButton); } } , [ viewer ] ) ; useEffect ( ( ) => { for ( const token of tokens ) { token . parentElement ?. removeChild ( token ) ; } if ( showTokens && viewer && activities . length ) { const overlays = viewer . get ( 'overlays' ) ; const update = [ ] ; const counter = { } ; for ( const activity of activities ) { const id = activity . activityId ; counter [ id ] = counter [ id ] ? counter [ id ] + 1 : 1 ; } const seen = { } ; for ( const activity of activities ) { const id = activity . activityId ; if ( seen [ id ] ) { continue ; } else { seen [ id ] = true ; } const overlay = document . createElement ( 'span' ) ; overlay . innerText = `${ counter [ id ] }` ; overlay . className = 'badge' ; overlay . style . cssText = `
          background: lightgray;
        ` ; try { overlays . add ( id . split ( '#' ) [ 0 ] , { position : { bottom : 17 , right : 10 , } , html : overlay , } ) ; update . push ( overlay ) ; } catch ( e ) { } } setTokens ( update ) ; } } , [ viewer , activities , showTokens ] ) ; if ( statistics && ! Array . from ( statistics . children ) . includes ( root ) ) { statistics . appendChild ( root ) ; } return statistics ? ( < Portal node = { root } > < FilterBox options = { DefinitionFilterOptions } autoCompleteHandler = { autoCompleteHandler } onParseOk = { setExpressions } defaultQuery = { ( ) : string => { const weekAgo = new Date ( new Date ( ) - 1000 * 3600 * 24 * 7 ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; const tomorrow = new Date ( new Date ( ) - 1000 * 3600 * 24 * - 1 ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; return `started after ${ weekAgo } AND finished before ${ tomorrow } AND maxResults is 1000` ; } } /> { activities . length ? ( < StatisticsTable activities = { filter ( activities , activity => activity . activityName && activity . endTime ) } / > ) : null } < /Portal>) : null; } ; export default [ { id : 'definitionHistoricActivitiesDiagramTokens' , pluginPoint : 'cockpit.processDefinition.diagram.plugin' , render : ( viewer ) => hooks . setViewer ( viewer ) , } , { id : 'definitionHistoricActivitiesStatisticsTab' , pluginPoint : 'cockpit.processDefinition.runtime.tab' , properties : { label : 'Statistics' , } , render : ( node ) => hooks . setStatistics ( node ) , } , { id : 'definitionHistoricActivitiesPlugin' , pluginPoint : 'cockpit.processDefinition.runtime.action' , render : ( node , { api , processDefinitionId } ) => { ReactDOM . render ( < React . StrictMode > < Plugin root = { node } api = { api } processDefinitionId = { processDefinitionId } / > < /React.StrictMode>, node); } , } , ] ;