1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: add count to event list (#2036)

* feat: add count to event list
This commit is contained in:
Christopher Kolstad 2022-09-02 08:35:31 +02:00 committed by GitHub
parent 3d40d0d6db
commit ae19cae8a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 101 additions and 20 deletions

View File

@ -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}

View File

@ -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,
};
};

View File

@ -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)

View File

@ -16,6 +16,10 @@ export const eventsSchema = {
type: 'array',
items: { $ref: eventSchema.$id },
},
totalEvents: {
type: 'integer',
minimum: 0,
},
},
components: {
schemas: {

View File

@ -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(

View File

@ -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,
};
}
}

View File

@ -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;

View File

@ -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[]>;
}

View File

@ -905,6 +905,10 @@ Object {
},
"type": "array",
},
"totalEvents": Object {
"minimum": 0,
"type": "integer",
},
"version": Object {
"minimum": 1,
"type": "integer",

View File

@ -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> {