mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	
							parent
							
								
									3d40d0d6db
								
							
						
					
					
						commit
						ae19cae8a9
					
				| @ -35,7 +35,11 @@ export const EventLog = ({ | ||||
|     displayInline, | ||||
| }: IEventLogProps) => { | ||||
|     const [query, setQuery] = useState(''); | ||||
|     const { events, fetchNextPage } = useEventSearch(project, feature, query); | ||||
|     const { events, totalEvents, fetchNextPage } = useEventSearch( | ||||
|         project, | ||||
|         feature, | ||||
|         query | ||||
|     ); | ||||
|     const fetchNextPageRef = useOnVisible<HTMLDivElement>(fetchNextPage); | ||||
|     const { eventSettings, setEventSettings } = useEventSettings(); | ||||
|     const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
| @ -64,13 +68,17 @@ export const EventLog = ({ | ||||
|         /> | ||||
|     ); | ||||
| 
 | ||||
|     let count = events?.length || 0; | ||||
|     let totalCount = totalEvents || 0; | ||||
|     let countText = `${count} of ${totalCount}`; | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             disablePadding={displayInline} | ||||
|             disableBorder={displayInline} | ||||
|             header={ | ||||
|                 <PageHeader | ||||
|                     title={title} | ||||
|                     title={`${title} (${countText})`} | ||||
|                     actions={ | ||||
|                         <> | ||||
|                             {showDataSwitch} | ||||
|  | ||||
| @ -10,6 +10,7 @@ export interface IUseEventSearchOutput { | ||||
|     events?: IEvent[]; | ||||
|     fetchNextPage: () => void; | ||||
|     loading: boolean; | ||||
|     totalEvents?: number; | ||||
|     error?: Error; | ||||
| } | ||||
| 
 | ||||
| @ -28,6 +29,7 @@ export const useEventSearch = ( | ||||
|     query?: string | ||||
| ): IUseEventSearchOutput => { | ||||
|     const [events, setEvents] = useState<IEvent[]>(); | ||||
|     const [totalEvents, setTotalEvents] = useState<number>(0); | ||||
|     const [offset, setOffset] = useState(0); | ||||
| 
 | ||||
|     const search: IEventSearch = useMemo( | ||||
| @ -35,14 +37,15 @@ export const useEventSearch = ( | ||||
|         [project, feature, query] | ||||
|     ); | ||||
| 
 | ||||
|     const { data, error, isValidating } = useSWR<{ events: IEvent[] }>( | ||||
|         [PATH, search, offset], | ||||
|         () => searchEvents(PATH, { ...search, offset }) | ||||
|     ); | ||||
|     const { data, error, isValidating } = useSWR<{ | ||||
|         events: IEvent[]; | ||||
|         totalEvents?: number; | ||||
|     }>([PATH, search, offset], () => searchEvents(PATH, { ...search, offset })); | ||||
| 
 | ||||
|     // Reset the page when there are new search conditions.
 | ||||
|     useEffect(() => { | ||||
|         setOffset(0); | ||||
|         setTotalEvents(0); | ||||
|         setEvents(undefined); | ||||
|     }, [search]); | ||||
| 
 | ||||
| @ -50,6 +53,9 @@ export const useEventSearch = ( | ||||
|     useEffect(() => { | ||||
|         if (data) { | ||||
|             setEvents(prev => [...(prev ?? []), ...data.events]); | ||||
|             if (data.totalEvents) { | ||||
|                 setTotalEvents(data.totalEvents); | ||||
|             } | ||||
|         } | ||||
|     }, [data]); | ||||
| 
 | ||||
| @ -64,6 +70,7 @@ export const useEventSearch = ( | ||||
|         events, | ||||
|         loading: !error && !data, | ||||
|         fetchNextPage, | ||||
|         totalEvents, | ||||
|         error, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| @ -57,6 +57,36 @@ class EventStore extends AnyEventEmitter implements IEventStore { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async count(): Promise<number> { | ||||
|         let count = await this.db(TABLE) | ||||
|             .count<Record<string, number>>() | ||||
|             .first(); | ||||
|         if (typeof count.count === 'string') { | ||||
|             return parseInt(count.count, 10); | ||||
|         } else { | ||||
|             return count.count; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async filteredCount(eventSearch: SearchEventsSchema): Promise<number> { | ||||
|         let query = this.db(TABLE); | ||||
|         if (eventSearch.type) { | ||||
|             query = query.andWhere({ type: eventSearch.type }); | ||||
|         } | ||||
|         if (eventSearch.project) { | ||||
|             query = query.andWhere({ project: eventSearch.project }); | ||||
|         } | ||||
|         if (eventSearch.feature) { | ||||
|             query = query.andWhere({ feature_name: eventSearch.feature }); | ||||
|         } | ||||
|         let count = await query.count().first(); | ||||
|         if (typeof count.count === 'string') { | ||||
|             return parseInt(count.count, 10); | ||||
|         } else { | ||||
|             return count.count; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async batchStore(events: IBaseEvent[]): Promise<void> { | ||||
|         try { | ||||
|             const savedRows = await this.db(TABLE) | ||||
|  | ||||
| @ -16,6 +16,10 @@ export const eventsSchema = { | ||||
|             type: 'array', | ||||
|             items: { $ref: eventSchema.$id }, | ||||
|         }, | ||||
|         totalEvents: { | ||||
|             type: 'integer', | ||||
|             minimum: 0, | ||||
|         }, | ||||
|     }, | ||||
|     components: { | ||||
|         schemas: { | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { IUnleashConfig } from '../../types/option'; | ||||
| import { IUnleashServices } from '../../types/services'; | ||||
| import EventService from '../../services/event-service'; | ||||
| import { ADMIN, NONE } from '../../types/permissions'; | ||||
| import { IEvent } from '../../types/events'; | ||||
| import { IEvent, IEventList } from '../../types/events'; | ||||
| import Controller from '../controller'; | ||||
| import { anonymise } from '../../util/anonymise'; | ||||
| import { OpenApiService } from '../../services/openapi-service'; | ||||
| @ -121,16 +121,17 @@ export default class EventController extends Controller { | ||||
|         res: Response<EventsSchema>, | ||||
|     ): Promise<void> { | ||||
|         const { project } = req.query; | ||||
|         let events: IEvent[]; | ||||
|         let eventList: IEventList; | ||||
|         if (project) { | ||||
|             events = await this.eventService.searchEvents({ project }); | ||||
|             eventList = await this.eventService.searchEvents({ project }); | ||||
|         } else { | ||||
|             events = await this.eventService.getEvents(); | ||||
|             eventList = await this.eventService.getEvents(); | ||||
|         } | ||||
| 
 | ||||
|         const response: EventsSchema = { | ||||
|             version, | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(eventList.events)), | ||||
|             totalEvents: eventList.totalEvents, | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
| @ -146,12 +147,13 @@ export default class EventController extends Controller { | ||||
|         res: Response<FeatureEventsSchema>, | ||||
|     ): Promise<void> { | ||||
|         const feature = req.params.featureName; | ||||
|         const events = await this.eventService.searchEvents({ feature }); | ||||
|         const eventList = await this.eventService.searchEvents({ feature }); | ||||
| 
 | ||||
|         const response = { | ||||
|             version, | ||||
|             toggleName: feature, | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(eventList.events)), | ||||
|             totalEvents: eventList.totalEvents, | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
| @ -166,11 +168,12 @@ export default class EventController extends Controller { | ||||
|         req: Request<unknown, unknown, SearchEventsSchema>, | ||||
|         res: Response<EventsSchema>, | ||||
|     ): Promise<void> { | ||||
|         const events = await this.eventService.searchEvents(req.body); | ||||
|         const eventList = await this.eventService.searchEvents(req.body); | ||||
| 
 | ||||
|         const response = { | ||||
|             version, | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(events)), | ||||
|             events: serializeDates(this.maybeAnonymiseEvents(eventList.events)), | ||||
|             totalEvents: eventList.totalEvents, | ||||
|         }; | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { IUnleashConfig } from '../types/option'; | ||||
| import { IUnleashStores } from '../types/stores'; | ||||
| import { Logger } from '../logger'; | ||||
| import { IEventStore } from '../types/stores/event-store'; | ||||
| import { IEvent } from '../types/events'; | ||||
| import { IEventList } from '../types/events'; | ||||
| import { SearchEventsSchema } from '../openapi/spec/search-events-schema'; | ||||
| 
 | ||||
| export default class EventService { | ||||
| @ -18,12 +18,22 @@ export default class EventService { | ||||
|         this.eventStore = eventStore; | ||||
|     } | ||||
| 
 | ||||
|     async getEvents(): Promise<IEvent[]> { | ||||
|         return this.eventStore.getEvents(); | ||||
|     async getEvents(): Promise<IEventList> { | ||||
|         let totalEvents = await this.eventStore.count(); | ||||
|         let events = await this.eventStore.getEvents(); | ||||
|         return { | ||||
|             events, | ||||
|             totalEvents, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     async searchEvents(search: SearchEventsSchema): Promise<IEvent[]> { | ||||
|         return this.eventStore.searchEvents(search); | ||||
|     async searchEvents(search: SearchEventsSchema): Promise<IEventList> { | ||||
|         let totalEvents = await this.eventStore.filteredCount(search); | ||||
|         let events = await this.eventStore.searchEvents(search); | ||||
|         return { | ||||
|             events, | ||||
|             totalEvents, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -92,6 +92,11 @@ export interface IEvent extends IBaseEvent { | ||||
|     createdAt: Date; | ||||
| } | ||||
| 
 | ||||
| export interface IEventList { | ||||
|     totalEvents: number; | ||||
|     events: IEvent[]; | ||||
| } | ||||
| 
 | ||||
| class BaseEvent implements IBaseEvent { | ||||
|     readonly type: string; | ||||
| 
 | ||||
|  | ||||
| @ -7,5 +7,7 @@ export interface IEventStore extends Store<IEvent, number>, EventEmitter { | ||||
|     store(event: IBaseEvent): Promise<void>; | ||||
|     batchStore(events: IBaseEvent[]): Promise<void>; | ||||
|     getEvents(): Promise<IEvent[]>; | ||||
|     count(): Promise<number>; | ||||
|     filteredCount(search: SearchEventsSchema): Promise<number>; | ||||
|     searchEvents(search: SearchEventsSchema): Promise<IEvent[]>; | ||||
| } | ||||
|  | ||||
| @ -905,6 +905,10 @@ Object { | ||||
|             }, | ||||
|             "type": "array", | ||||
|           }, | ||||
|           "totalEvents": Object { | ||||
|             "minimum": 0, | ||||
|             "type": "integer", | ||||
|           }, | ||||
|           "version": Object { | ||||
|             "minimum": 1, | ||||
|             "type": "integer", | ||||
|  | ||||
							
								
								
									
										8
									
								
								src/test/fixtures/fake-event-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								src/test/fixtures/fake-event-store.ts
									
									
									
									
										vendored
									
									
								
							| @ -40,6 +40,14 @@ class FakeEventStore extends AnyEventEmitter implements IEventStore { | ||||
|         this.events = []; | ||||
|     } | ||||
| 
 | ||||
|     async count(): Promise<number> { | ||||
|         return Promise.resolve(this.events.length); | ||||
|     } | ||||
| 
 | ||||
|     filteredCount(): Promise<number> { | ||||
|         throw new Error('Method not implemented'); | ||||
|     } | ||||
| 
 | ||||
|     destroy(): void {} | ||||
| 
 | ||||
|     async exists(key: number): Promise<boolean> { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user