mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
refactor: implement an event timeline context and provider (#8321)
https://linear.app/unleash/issue/2-2730/refactor-the-event-timeline-state-management-to-a-context-and-provider This PR refactors the state management for the **Event Timeline** component by introducing a context and provider to improve accessibility of state across the component tree.
This commit is contained in:
parent
2d8bc3268f
commit
5dae654022
@ -4,11 +4,11 @@ import { startOfDay, sub } from 'date-fns';
|
||||
import { useEventSearch } from 'hooks/api/getters/useEventSearch/useEventSearch';
|
||||
import { EventTimelineEventGroup } from './EventTimelineEventGroup/EventTimelineEventGroup';
|
||||
import { EventTimelineHeader } from './EventTimelineHeader/EventTimelineHeader';
|
||||
import type { TimeSpanOption } from './useEventTimeline';
|
||||
import { useMemo } from 'react';
|
||||
import { useSignalQuery } from 'hooks/api/getters/useSignalQuery/useSignalQuery';
|
||||
import type { ISignalQuerySignal } from 'interfaces/signal';
|
||||
import type { IEnvironment } from 'interfaces/environments';
|
||||
import { useEventTimelineContext } from './EventTimelineContext';
|
||||
|
||||
export type TimelineEventType = 'signal' | EventSchemaType;
|
||||
|
||||
@ -157,21 +157,8 @@ const getTimelineEvent = (
|
||||
}
|
||||
};
|
||||
|
||||
interface IEventTimelineProps {
|
||||
timeSpan: TimeSpanOption;
|
||||
environment: IEnvironment | undefined;
|
||||
setTimeSpan: (timeSpan: TimeSpanOption) => void;
|
||||
setEnvironment: (environment: IEnvironment) => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const EventTimeline = ({
|
||||
timeSpan,
|
||||
environment,
|
||||
setTimeSpan,
|
||||
setEnvironment,
|
||||
setOpen,
|
||||
}: IEventTimelineProps) => {
|
||||
export const EventTimeline = () => {
|
||||
const { timeSpan, environment } = useEventTimelineContext();
|
||||
const endDate = new Date();
|
||||
const startDate = sub(endDate, timeSpan.value);
|
||||
const endTime = endDate.getTime();
|
||||
@ -246,14 +233,7 @@ export const EventTimeline = ({
|
||||
return (
|
||||
<>
|
||||
<StyledRow>
|
||||
<EventTimelineHeader
|
||||
totalEvents={events.length}
|
||||
timeSpan={timeSpan}
|
||||
setTimeSpan={setTimeSpan}
|
||||
environment={environment}
|
||||
setEnvironment={setEnvironment}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
<EventTimelineHeader totalEvents={events.length} />
|
||||
</StyledRow>
|
||||
<StyledTimelineBody>
|
||||
<StyledTimelineContainer>
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import type { IEventTimelineContext } from './EventTimelineProvider';
|
||||
|
||||
export const EventTimelineContext = createContext<
|
||||
IEventTimelineContext | undefined
|
||||
>(undefined);
|
||||
|
||||
export const useEventTimelineContext = (): IEventTimelineContext => {
|
||||
const context = useContext(EventTimelineContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useEventTimelineContext must be used within a EventTimelineProvider',
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
@ -7,10 +7,10 @@ import {
|
||||
} from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||
import type { IEnvironment } from 'interfaces/environments';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { type TimeSpanOption, timeSpanOptions } from '../useEventTimeline';
|
||||
import { timeSpanOptions } from '../EventTimelineProvider';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { useEventTimelineContext } from '../EventTimelineContext';
|
||||
|
||||
const StyledCol = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -36,21 +36,13 @@ const StyledTimelineEventsCount = styled('span')(({ theme }) => ({
|
||||
|
||||
interface IEventTimelineHeaderProps {
|
||||
totalEvents: number;
|
||||
timeSpan: TimeSpanOption;
|
||||
setTimeSpan: (timeSpan: TimeSpanOption) => void;
|
||||
environment: IEnvironment | undefined;
|
||||
setEnvironment: (environment: IEnvironment) => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const EventTimelineHeader = ({
|
||||
totalEvents,
|
||||
timeSpan,
|
||||
setTimeSpan,
|
||||
environment,
|
||||
setEnvironment,
|
||||
setOpen,
|
||||
}: IEventTimelineHeaderProps) => {
|
||||
const { timeSpan, environment, setOpen, setTimeSpan, setEnvironment } =
|
||||
useEventTimelineContext();
|
||||
const { environments } = useEnvironments();
|
||||
|
||||
const activeEnvironments = useMemo(
|
||||
|
@ -1,13 +1,33 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { EventTimelineContext } from './EventTimelineContext';
|
||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||
import type { IEnvironment } from 'interfaces/environments';
|
||||
|
||||
export type TimeSpanOption = {
|
||||
type TimeSpanOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
value: Duration;
|
||||
markers: string[];
|
||||
};
|
||||
|
||||
type EventTimelineState = {
|
||||
open: boolean;
|
||||
timeSpan: TimeSpanOption;
|
||||
environment?: IEnvironment;
|
||||
signalsAlertSeen?: boolean;
|
||||
};
|
||||
|
||||
type EventTimelineStateSetters = {
|
||||
setOpen: (open: boolean) => void;
|
||||
setTimeSpan: (timeSpan: TimeSpanOption) => void;
|
||||
setEnvironment: (environment: IEnvironment) => void;
|
||||
setSignalsAlertSeen: (seen: boolean) => void;
|
||||
};
|
||||
|
||||
export interface IEventTimelineContext
|
||||
extends EventTimelineState,
|
||||
EventTimelineStateSetters {}
|
||||
|
||||
export const timeSpanOptions: TimeSpanOption[] = [
|
||||
{
|
||||
key: '30m',
|
||||
@ -57,18 +77,18 @@ export const timeSpanOptions: TimeSpanOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type EventTimelineState = {
|
||||
open: boolean;
|
||||
timeSpan: TimeSpanOption;
|
||||
environment?: IEnvironment;
|
||||
};
|
||||
|
||||
const defaultState: EventTimelineState = {
|
||||
open: false,
|
||||
timeSpan: timeSpanOptions[0],
|
||||
};
|
||||
|
||||
export const useEventTimeline = () => {
|
||||
interface IEventTimelineProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const EventTimelineProvider = ({
|
||||
children,
|
||||
}: IEventTimelineProviderProps) => {
|
||||
const [state, setState] = useLocalStorageState<EventTimelineState>(
|
||||
'event-timeline:v1',
|
||||
defaultState,
|
||||
@ -81,12 +101,20 @@ export const useEventTimeline = () => {
|
||||
setState((prevState) => ({ ...prevState, [key]: value }));
|
||||
};
|
||||
|
||||
return {
|
||||
const contextValue: IEventTimelineContext = {
|
||||
...state,
|
||||
setOpen: (open: boolean) => setField('open', open),
|
||||
setTimeSpan: (timeSpan: TimeSpanOption) =>
|
||||
setField('timeSpan', timeSpan),
|
||||
setEnvironment: (environment: IEnvironment) =>
|
||||
setField('environment', environment),
|
||||
setSignalsAlertSeen: (seen: boolean) =>
|
||||
setField('signalsAlertSeen', seen),
|
||||
};
|
||||
|
||||
return (
|
||||
<EventTimelineContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</EventTimelineContext.Provider>
|
||||
);
|
||||
};
|
@ -17,8 +17,8 @@ import { DraftBanner } from './DraftBanner/DraftBanner';
|
||||
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
||||
import { NavigationSidebar } from './NavigationSidebar/NavigationSidebar';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useEventTimeline } from 'component/events/EventTimeline/useEventTimeline';
|
||||
import { MainLayoutEventTimeline } from './MainLayoutEventTimeline';
|
||||
import { EventTimelineProvider } from 'component/events/EventTimeline/EventTimelineProvider';
|
||||
|
||||
interface IMainLayoutProps {
|
||||
children: ReactNode;
|
||||
@ -112,20 +112,11 @@ const MainLayoutContentContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
({ children }, ref) => {
|
||||
const { uiConfig, isOss } = useUiConfig();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const projectId = useOptionalPathParam('projectId');
|
||||
const { isChangeRequestConfiguredInAnyEnv } = useChangeRequestsEnabled(
|
||||
projectId || '',
|
||||
);
|
||||
const eventTimeline = useUiFlag('eventTimeline') && !isOss();
|
||||
const {
|
||||
open: showTimeline,
|
||||
timeSpan,
|
||||
environment,
|
||||
setOpen: setShowTimeline,
|
||||
setTimeSpan,
|
||||
setEnvironment,
|
||||
} = useEventTimeline();
|
||||
|
||||
const sidebarNavigationEnabled = useUiFlag('navigationSidebar');
|
||||
const StyledMainLayoutContent = sidebarNavigationEnabled
|
||||
@ -135,22 +126,12 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
|
||||
return (
|
||||
<>
|
||||
<EventTimelineProvider>
|
||||
<SkipNavLink />
|
||||
<ConditionallyRender
|
||||
condition={sidebarNavigationEnabled}
|
||||
show={
|
||||
<Header
|
||||
showTimeline={showTimeline}
|
||||
setShowTimeline={setShowTimeline}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<OldHeader
|
||||
showTimeline={showTimeline}
|
||||
setShowTimeline={setShowTimeline}
|
||||
/>
|
||||
}
|
||||
show={<Header />}
|
||||
elseShow={<OldHeader />}
|
||||
/>
|
||||
|
||||
<SkipNavTarget />
|
||||
@ -185,14 +166,7 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<MainLayoutEventTimeline
|
||||
open={eventTimeline && showTimeline}
|
||||
setOpen={setShowTimeline}
|
||||
timeSpan={timeSpan}
|
||||
setTimeSpan={setTimeSpan}
|
||||
environment={environment}
|
||||
setEnvironment={setEnvironment}
|
||||
/>
|
||||
<MainLayoutEventTimeline />
|
||||
|
||||
<StyledMainLayoutContent>
|
||||
<MainLayoutContentContainer ref={ref}>
|
||||
@ -222,7 +196,7 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
</MainLayoutContentWrapper>
|
||||
<Footer />
|
||||
</MainLayoutContainer>
|
||||
</>
|
||||
</EventTimelineProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { EventTimeline } from 'component/events/EventTimeline/EventTimeline';
|
||||
import type { TimeSpanOption } from 'component/events/EventTimeline/useEventTimeline';
|
||||
import type { IEnvironment } from 'interfaces/environments';
|
||||
import { useEventTimelineContext } from 'component/events/EventTimeline/EventTimelineContext';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const StyledEventTimelineSlider = styled(Box)(({ theme }) => ({
|
||||
@ -17,25 +18,14 @@ const StyledEventTimelineWrapper = styled(Box)(({ theme }) => ({
|
||||
padding: theme.spacing(1.5, 2),
|
||||
}));
|
||||
|
||||
interface IMainLayoutEventTimelineProps {
|
||||
open: boolean;
|
||||
timeSpan: TimeSpanOption;
|
||||
environment: IEnvironment | undefined;
|
||||
setTimeSpan: (timeSpan: TimeSpanOption) => void;
|
||||
setEnvironment: (environment: IEnvironment) => void;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const MainLayoutEventTimeline = ({
|
||||
open,
|
||||
timeSpan,
|
||||
environment,
|
||||
setTimeSpan,
|
||||
setEnvironment,
|
||||
setOpen,
|
||||
}: IMainLayoutEventTimelineProps) => {
|
||||
export const MainLayoutEventTimeline = () => {
|
||||
const { isOss } = useUiConfig();
|
||||
const { open: showTimeline } = useEventTimelineContext();
|
||||
const eventTimelineEnabled = useUiFlag('eventTimeline') && !isOss();
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
|
||||
const open = showTimeline && eventTimelineEnabled;
|
||||
|
||||
useEffect(() => {
|
||||
setIsInitialLoad(false);
|
||||
}, []);
|
||||
@ -52,15 +42,7 @@ export const MainLayoutEventTimeline = ({
|
||||
<StyledEventTimelineWrapper>
|
||||
<ConditionallyRender
|
||||
condition={open}
|
||||
show={
|
||||
<EventTimeline
|
||||
timeSpan={timeSpan}
|
||||
environment={environment}
|
||||
setTimeSpan={setTimeSpan}
|
||||
setEnvironment={setEnvironment}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
}
|
||||
show={<EventTimeline />}
|
||||
/>
|
||||
</StyledEventTimelineWrapper>
|
||||
</StyledEventTimelineSlider>
|
||||
|
@ -34,6 +34,7 @@ import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { CommandBar } from 'component/commandBar/CommandBar';
|
||||
import LinearScaleIcon from '@mui/icons-material/LinearScale';
|
||||
import { useEventTimelineContext } from 'component/events/EventTimeline/EventTimelineContext';
|
||||
|
||||
const HeaderComponent = styled(AppBar)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
@ -97,12 +98,7 @@ const StyledIconButton = styled(IconButton)<{
|
||||
},
|
||||
}));
|
||||
|
||||
interface IHeaderProps {
|
||||
showTimeline: boolean;
|
||||
setShowTimeline: (show: boolean) => void;
|
||||
}
|
||||
|
||||
const Header = ({ showTimeline, setShowTimeline }: IHeaderProps) => {
|
||||
const Header = () => {
|
||||
const { onSetThemeMode, themeMode } = useThemeMode();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -113,6 +109,8 @@ const Header = ({ showTimeline, setShowTimeline }: IHeaderProps) => {
|
||||
const toggleDrawer = () => setOpenDrawer((prev) => !prev);
|
||||
const celebatoryUnleash = useUiFlag('celebrateUnleash');
|
||||
const eventTimeline = useUiFlag('eventTimeline') && !isOss();
|
||||
const { open: showTimeline, setOpen: setShowTimeline } =
|
||||
useEventTimelineContext();
|
||||
|
||||
const routes = getRoutes();
|
||||
const adminRoutes = useAdminRoutes();
|
||||
|
@ -37,6 +37,7 @@ import { useAdminRoutes } from 'component/admin/useAdminRoutes';
|
||||
import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import LinearScaleIcon from '@mui/icons-material/LinearScale';
|
||||
import { useEventTimelineContext } from 'component/events/EventTimeline/EventTimelineContext';
|
||||
|
||||
const HeaderComponent = styled(AppBar)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
@ -131,12 +132,7 @@ const StyledIconButton = styled(IconButton)<{
|
||||
},
|
||||
}));
|
||||
|
||||
interface IOldHeaderProps {
|
||||
showTimeline: boolean;
|
||||
setShowTimeline: (show: boolean) => void;
|
||||
}
|
||||
|
||||
const OldHeader = ({ showTimeline, setShowTimeline }: IOldHeaderProps) => {
|
||||
const OldHeader = () => {
|
||||
const { onSetThemeMode, themeMode } = useThemeMode();
|
||||
const theme = useTheme();
|
||||
const adminId = useId();
|
||||
@ -153,6 +149,8 @@ const OldHeader = ({ showTimeline, setShowTimeline }: IOldHeaderProps) => {
|
||||
const onConfigureClose = () => setConfigRef(null);
|
||||
const celebatoryUnleash = useUiFlag('celebrateUnleash');
|
||||
const eventTimeline = useUiFlag('eventTimeline') && !isOss();
|
||||
const { open: showTimeline, setOpen: setShowTimeline } =
|
||||
useEventTimelineContext();
|
||||
|
||||
const routes = getRoutes();
|
||||
const adminRoutes = useAdminRoutes();
|
||||
|
Loading…
Reference in New Issue
Block a user