Skip to content

Commit

Permalink
chore: implement event grouping in the event timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
nunogois committed Sep 26, 2024
1 parent 823f633 commit 39d841c
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 102 deletions.
59 changes: 49 additions & 10 deletions frontend/src/component/events/EventTimeline/EventTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { styled } from '@mui/material';
import type { EventSchema, EventSchemaType } from 'openapi';
import { startOfDay, sub } from 'date-fns';
import { useEventSearch } from 'hooks/api/getters/useEventSearch/useEventSearch';
import { EventTimelineEvent } from './EventTimelineEvent/EventTimelineEvent';
import { EventTimelineEventGroup } from './EventTimelineEventGroup/EventTimelineEventGroup';
import { EventTimelineHeader } from './EventTimelineHeader/EventTimelineHeader';
import { useEventTimeline } from './useEventTimeline';
import { useMemo } from 'react';

export type EnrichedEvent = EventSchema & {
label: string;
summary: string;
timestamp: number;
};

export type TimelineEventGroup = EnrichedEvent[];

const StyledRow = styled('div')({
display: 'flex',
flexDirection: 'row',
Expand Down Expand Up @@ -88,6 +92,8 @@ export const EventTimeline = () => {

const endDate = new Date();
const startDate = sub(endDate, timeSpan.value);
const endTime = endDate.getTime();
const startTime = startDate.getTime();

const { events: baseEvents } = useEventSearch(
{
Expand All @@ -98,19 +104,52 @@ export const EventTimeline = () => {
{ refreshInterval: 10 * 1000 },
);

const events = baseEvents as EnrichedEvent[];
const events = useMemo(() => {
return baseEvents.map((event) => ({
...event,
timestamp: new Date(event.createdAt).getTime(),
}));
}, [baseEvents]) as EnrichedEvent[];

const filteredEvents = events.filter(
(event) =>
new Date(event.createdAt).getTime() >= startDate.getTime() &&
new Date(event.createdAt).getTime() <= endDate.getTime() &&
RELEVANT_EVENT_TYPES.includes(event.type) &&
event.timestamp >= startTime &&
event.timestamp <= endTime &&
(!event.environment ||
!environment ||
event.environment === environment.name),
);

const sortedEvents = [...filteredEvents].reverse();
const sortedEvents = filteredEvents.reverse();

const timespanInMs = endTime - startTime;
const groupingThresholdInMs = useMemo(
() => timespanInMs * 0.02,
[timespanInMs],
);

const groups = useMemo(
() =>
sortedEvents.reduce((groups: TimelineEventGroup[], event) => {
if (groups.length === 0) {
groups.push([event]);
} else {
const lastGroup = groups[groups.length - 1];
const lastEventInGroup = lastGroup[lastGroup.length - 1];

if (
event.timestamp - lastEventInGroup.timestamp <=
groupingThresholdInMs
) {
lastGroup.push(event);
} else {
groups.push([event]);
}
}
return groups;
}, []),
[sortedEvents, groupingThresholdInMs],
);

return (
<>
Expand All @@ -126,10 +165,10 @@ export const EventTimeline = () => {
<StyledTimelineContainer>
<StyledTimeline />
<StyledStart />
{sortedEvents.map((event) => (
<EventTimelineEvent
key={event.id}
event={event}
{groups.map((group) => (
<EventTimelineEventGroup
key={group[0].id}
group={group}
startDate={startDate}
endDate={endDate}
/>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,34 +1,22 @@
import type { EventSchemaType } from 'openapi';
import { styled } from '@mui/material';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { EventTimelineEventTooltip } from './EventTimelineEventTooltip/EventTimelineEventTooltip';
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
import ToggleOffIcon from '@mui/icons-material/ToggleOff';
import FlagOutlinedIcon from '@mui/icons-material/FlagOutlined';
import ExtensionOutlinedIcon from '@mui/icons-material/ExtensionOutlined';
import SegmentsIcon from '@mui/icons-material/DonutLargeOutlined';
import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
import type { EnrichedEvent } from '../EventTimeline';
import { styled } from '@mui/material';
import type { TimelineEventGroup } from '../EventTimeline';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import type { HTMLAttributes } from 'react';

type DefaultEventVariant = 'secondary';
type CustomEventVariant = 'success' | 'neutral';
type EventVariant = DefaultEventVariant | CustomEventVariant;

const StyledEvent = styled('div', {
shouldForwardProp: (prop) => prop !== 'position',
})<{ position: string }>(({ position }) => ({
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
left: position,
transform: 'translateX(-50%)',
zIndex: 1,
}));

const StyledEventCircle = styled('div', {
shouldForwardProp: (prop) => prop !== 'variant',
})<{ variant: EventVariant }>(({ theme, variant }) => ({
})<{ variant?: EventVariant }>(({ theme, variant = 'secondary' }) => ({
height: theme.spacing(3),
width: theme.spacing(3),
borderRadius: '50%',
Expand Down Expand Up @@ -76,35 +64,31 @@ const customEventVariants: Partial<
'feature-archived': 'neutral',
};

interface IEventTimelineEventProps {
event: EnrichedEvent;
startDate: Date;
endDate: Date;
interface IEventTimelineEventCircleProps
extends HTMLAttributes<HTMLDivElement> {
group: TimelineEventGroup;
}

export const EventTimelineEvent = ({
event,
startDate,
endDate,
}: IEventTimelineEventProps) => {
const timelineDuration = endDate.getTime() - startDate.getTime();
const eventTime = new Date(event.createdAt).getTime();

const position = `${((eventTime - startDate.getTime()) / timelineDuration) * 100}%`;
export const EventTimelineEventCircle = ({
group,
...props
}: IEventTimelineEventCircleProps) => {
if (group.length === 1) {
const event = group[0];

const variant = customEventVariants[event.type] || 'secondary';
return (
<StyledEventCircle
variant={customEventVariants[event.type]}
{...props}
>
{getEventIcon(event.type)}
</StyledEventCircle>
);
}

return (
<StyledEvent position={position}>
<HtmlTooltip
title={<EventTimelineEventTooltip event={event} />}
maxWidth={320}
arrow
>
<StyledEventCircle variant={variant}>
{getEventIcon(event.type)}
</StyledEventCircle>
</HtmlTooltip>
</StyledEvent>
<StyledEventCircle {...props}>
<MoreHorizIcon />
</StyledEventCircle>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Badge, styled } from '@mui/material';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { EventTimelineEventTooltip } from './EventTimelineEventTooltip/EventTimelineEventTooltip';
import type { TimelineEventGroup } from '../EventTimeline';
import { EventTimelineEventCircle } from './EventTimelineEventCircle';

const StyledEvent = styled('div', {
shouldForwardProp: (prop) => prop !== 'position',
})<{ position: string }>(({ position }) => ({
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
left: position,
transform: 'translateX(-50%)',
zIndex: 1,
}));

interface IEventTimelineEventProps {
group: TimelineEventGroup;
startDate: Date;
endDate: Date;
}

export const EventTimelineEventGroup = ({
group,
startDate,
endDate,
}: IEventTimelineEventProps) => {
const timelineDuration = endDate.getTime() - startDate.getTime();
const eventTime = new Date(group[0].createdAt).getTime();

const position = `${((eventTime - startDate.getTime()) / timelineDuration) * 100}%`;

return (
<StyledEvent position={position}>
<HtmlTooltip
title={<EventTimelineEventTooltip group={group} />}
maxWidth={320}
arrow
>
<Badge
badgeContent={group.length}
color='primary'
invisible={group.length < 2}
>
<EventTimelineEventCircle group={group} />
</Badge>
</HtmlTooltip>
</StyledEvent>
);
};
Loading

0 comments on commit 39d841c

Please sign in to comment.