mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-19 00:15:43 +01:00
fix: open/close animation on personal dashboard is choppy (#9253)
Extracts each panel into its own component for the personal dashboard.
This lets us use separate states for each panel, which in turn lets each
panel change its open / close state without causing the other panels to
re-render.
When you have a lot of flags and/or projects, the list to render becomes
very long, which causes performance problems, especially when you need
to rerender both flags and projects and the timeline whenever one of
them changes.
The problems were especially noticeable in Firefox for me. Even with
this, the event timeline is a little choppy. I suspect that's because of
it might take a long time to paint? But we can look into that later.
Also updates the dashboard state hook to let you only pass in the
flags/projects you want. We could extract this into three different
hooks that all use the same localhost key, but I'm not sure whether
that's better or worse 🤷🏼
This commit is contained in:
parent
ccd8de6e74
commit
2b668bc5c8
@ -111,6 +111,158 @@ const AccordionSummarySubtitle = styled(Typography)(({ theme }) => ({
|
||||
fontWeight: theme.typography.body2.fontWeight,
|
||||
}));
|
||||
|
||||
const EventTimelinePanel = () => {
|
||||
const { toggleSectionState, expandTimeline } = useDashboardState();
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
const signalsLink = '/integrations/signals';
|
||||
return (
|
||||
<SectionAccordion
|
||||
disableGutters
|
||||
expanded={expandTimeline ?? false}
|
||||
onChange={() => toggleSectionState('timeline')}
|
||||
>
|
||||
<StyledAccordionSummary
|
||||
expandIcon={
|
||||
<ExpandMore titleAccess='Toggle timeline section' />
|
||||
}
|
||||
id='timeline-panel-header'
|
||||
aria-controls='timeline-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>
|
||||
Event timeline
|
||||
</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Overview of recent activities across all projects in
|
||||
Unleash. Make debugging easier and{' '}
|
||||
<Link
|
||||
to={signalsLink}
|
||||
onClick={() => {
|
||||
trackEvent('event-timeline', {
|
||||
props: {
|
||||
eventType: 'signals clicked',
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
include external signals
|
||||
</Link>{' '}
|
||||
to get a fuller overview.
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummary>
|
||||
<StyledAccordionDetails>
|
||||
<AccordionContent>
|
||||
<EventTimeline />
|
||||
</AccordionContent>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
);
|
||||
};
|
||||
|
||||
const FlagPanel = () => {
|
||||
const { personalDashboard, refetch: refetchDashboard } =
|
||||
usePersonalDashboard();
|
||||
|
||||
const projects = personalDashboard?.projects || [];
|
||||
|
||||
const { activeFlag, setActiveFlag, toggleSectionState, expandFlags } =
|
||||
useDashboardState({ flags: personalDashboard?.flags ?? [] });
|
||||
|
||||
return (
|
||||
<SectionAccordion
|
||||
expanded={expandFlags ?? true}
|
||||
onChange={() => toggleSectionState('flags')}
|
||||
>
|
||||
<StyledAccordionSummaryWithBorder
|
||||
expandIcon={<ExpandMore titleAccess='Toggle flags section' />}
|
||||
id='flags-panel-header'
|
||||
aria-controls='flags-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>
|
||||
My feature flags
|
||||
</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Feature flags you have created or favorited
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummaryWithBorder>
|
||||
<StyledAccordionDetails>
|
||||
<MyFlags
|
||||
hasProjects={projects?.length > 0}
|
||||
flagData={
|
||||
personalDashboard?.flags.length
|
||||
? {
|
||||
state: 'flags' as const,
|
||||
activeFlag,
|
||||
flags: personalDashboard.flags,
|
||||
}
|
||||
: { state: 'no flags' as const }
|
||||
}
|
||||
setActiveFlag={setActiveFlag}
|
||||
refetchDashboard={refetchDashboard}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectPanel = () => {
|
||||
const { personalDashboard } = usePersonalDashboard();
|
||||
|
||||
const projects = personalDashboard?.projects || [];
|
||||
|
||||
const {
|
||||
activeProject,
|
||||
setActiveProject,
|
||||
toggleSectionState,
|
||||
expandProjects,
|
||||
} = useDashboardState({ projects });
|
||||
|
||||
const personalDashboardProjectDetails =
|
||||
fromPersonalDashboardProjectDetailsOutput(
|
||||
usePersonalDashboardProjectDetails(activeProject),
|
||||
);
|
||||
|
||||
return (
|
||||
<SectionAccordion
|
||||
disableGutters
|
||||
expanded={expandProjects ?? true}
|
||||
onChange={() => toggleSectionState('projects')}
|
||||
>
|
||||
<StyledAccordionSummaryWithBorder
|
||||
expandIcon={
|
||||
<ExpandMore titleAccess='Toggle projects section' />
|
||||
}
|
||||
id='projects-panel-header'
|
||||
aria-controls='projects-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>My projects</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Favorite projects, projects you own, and projects you
|
||||
are a member of
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummaryWithBorder>
|
||||
<StyledAccordionDetails>
|
||||
<MyProjects
|
||||
owners={personalDashboard?.projectOwners ?? []}
|
||||
admins={personalDashboard?.admins ?? []}
|
||||
projects={projects}
|
||||
activeProject={activeProject || ''}
|
||||
setActiveProject={setActiveProject}
|
||||
personalDashboardProjectDetails={
|
||||
personalDashboardProjectDetails
|
||||
}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const PersonalDashboard = () => {
|
||||
const { user } = useAuthUser();
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
@ -122,24 +274,6 @@ export const PersonalDashboard = () => {
|
||||
|
||||
usePageTitle(name ? `Dashboard: ${name}` : 'Dashboard');
|
||||
|
||||
const { personalDashboard, refetch: refetchDashboard } =
|
||||
usePersonalDashboard();
|
||||
|
||||
const projects = personalDashboard?.projects || [];
|
||||
|
||||
const {
|
||||
activeProject,
|
||||
setActiveProject,
|
||||
activeFlag,
|
||||
setActiveFlag,
|
||||
toggleSectionState,
|
||||
expandFlags,
|
||||
expandProjects,
|
||||
expandTimeline,
|
||||
} = useDashboardState(projects, personalDashboard?.flags ?? []);
|
||||
|
||||
const signalsLink = '/integrations/signals';
|
||||
|
||||
const [welcomeDialog, setWelcomeDialog] = useLocalStorageState<
|
||||
'open' | 'closed'
|
||||
>(
|
||||
@ -147,11 +281,6 @@ export const PersonalDashboard = () => {
|
||||
splash?.personalDashboardKeyConcepts ? 'closed' : 'open',
|
||||
);
|
||||
|
||||
const personalDashboardProjectDetails =
|
||||
fromPersonalDashboardProjectDetailsOutput(
|
||||
usePersonalDashboardProjectDetails(activeProject),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
trackEvent('personal-dashboard', {
|
||||
props: {
|
||||
@ -188,122 +317,12 @@ export const PersonalDashboard = () => {
|
||||
</ViewKeyConceptsButton>
|
||||
</WelcomeSection>
|
||||
|
||||
{showTimelinePanel && (
|
||||
<SectionAccordion
|
||||
disableGutters
|
||||
expanded={expandTimeline ?? false}
|
||||
onChange={() => toggleSectionState('timeline')}
|
||||
>
|
||||
<StyledAccordionSummary
|
||||
expandIcon={
|
||||
<ExpandMore titleAccess='Toggle timeline section' />
|
||||
}
|
||||
id='timeline-panel-header'
|
||||
aria-controls='timeline-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>
|
||||
Event timeline
|
||||
</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Overview of recent activities across all
|
||||
projects in Unleash. Make debugging easier and{' '}
|
||||
<Link
|
||||
to={signalsLink}
|
||||
onClick={() => {
|
||||
trackEvent('event-timeline', {
|
||||
props: {
|
||||
eventType: 'signals clicked',
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
include external signals
|
||||
</Link>{' '}
|
||||
to get a fuller overview.
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummary>
|
||||
<StyledAccordionDetails>
|
||||
<AccordionContent>
|
||||
<EventTimeline />
|
||||
</AccordionContent>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
)}
|
||||
<SectionAccordion
|
||||
disableGutters
|
||||
expanded={expandProjects ?? true}
|
||||
onChange={() => toggleSectionState('projects')}
|
||||
>
|
||||
<StyledAccordionSummaryWithBorder
|
||||
expandIcon={
|
||||
<ExpandMore titleAccess='Toggle projects section' />
|
||||
}
|
||||
id='projects-panel-header'
|
||||
aria-controls='projects-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>
|
||||
My projects
|
||||
</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Favorite projects, projects you own, and projects
|
||||
you are a member of
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummaryWithBorder>
|
||||
<StyledAccordionDetails>
|
||||
<MyProjects
|
||||
owners={personalDashboard?.projectOwners ?? []}
|
||||
admins={personalDashboard?.admins ?? []}
|
||||
projects={projects}
|
||||
activeProject={activeProject || ''}
|
||||
setActiveProject={setActiveProject}
|
||||
personalDashboardProjectDetails={
|
||||
personalDashboardProjectDetails
|
||||
}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
{showTimelinePanel && <EventTimelinePanel />}
|
||||
|
||||
<ProjectPanel />
|
||||
|
||||
<FlagPanel />
|
||||
|
||||
<SectionAccordion
|
||||
expanded={expandFlags ?? true}
|
||||
onChange={() => toggleSectionState('flags')}
|
||||
>
|
||||
<StyledAccordionSummaryWithBorder
|
||||
expandIcon={
|
||||
<ExpandMore titleAccess='Toggle flags section' />
|
||||
}
|
||||
id='flags-panel-header'
|
||||
aria-controls='flags-panel-content'
|
||||
>
|
||||
<AccordionSummaryText>
|
||||
<AccordionSummaryHeader>
|
||||
My feature flags
|
||||
</AccordionSummaryHeader>
|
||||
<AccordionSummarySubtitle>
|
||||
Feature flags you have created or favorited
|
||||
</AccordionSummarySubtitle>
|
||||
</AccordionSummaryText>
|
||||
</StyledAccordionSummaryWithBorder>
|
||||
<StyledAccordionDetails>
|
||||
<MyFlags
|
||||
hasProjects={projects?.length > 0}
|
||||
flagData={
|
||||
personalDashboard?.flags.length
|
||||
? {
|
||||
state: 'flags' as const,
|
||||
activeFlag,
|
||||
flags: personalDashboard.flags,
|
||||
}
|
||||
: { state: 'no flags' as const }
|
||||
}
|
||||
setActiveFlag={setActiveFlag}
|
||||
refetchDashboard={refetchDashboard}
|
||||
/>
|
||||
</StyledAccordionDetails>
|
||||
</SectionAccordion>
|
||||
<WelcomeDialog
|
||||
open={welcomeDialog === 'open'}
|
||||
onClose={() => {
|
||||
|
@ -5,10 +5,11 @@ import type {
|
||||
} from 'openapi';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useDashboardState = (
|
||||
projects: PersonalDashboardSchemaProjectsItem[],
|
||||
flags: PersonalDashboardSchemaFlagsItem[],
|
||||
) => {
|
||||
type StateProps = {
|
||||
projects?: PersonalDashboardSchemaProjectsItem[];
|
||||
flags?: PersonalDashboardSchemaFlagsItem[];
|
||||
};
|
||||
export const useDashboardState = (props?: StateProps) => {
|
||||
type State = {
|
||||
activeProject: string | undefined;
|
||||
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
|
||||
@ -35,34 +36,31 @@ export const useDashboardState = (
|
||||
|
||||
useEffect(() => {
|
||||
const updates: Partial<State> = {};
|
||||
const setDefaultFlag =
|
||||
flags.length &&
|
||||
(!state.activeFlag ||
|
||||
!flags.some((flag) => flag.name === state.activeFlag?.name));
|
||||
|
||||
if (setDefaultFlag) {
|
||||
updates.activeFlag = flags[0];
|
||||
if (
|
||||
props?.flags?.length &&
|
||||
(!state.activeFlag ||
|
||||
!props.flags.some(
|
||||
(flag) => flag.name === state.activeFlag?.name,
|
||||
))
|
||||
) {
|
||||
updates.activeFlag = props.flags[0];
|
||||
}
|
||||
|
||||
const setDefaultProject =
|
||||
projects.length &&
|
||||
if (
|
||||
props?.projects?.length &&
|
||||
(!state.activeProject ||
|
||||
!projects.some(
|
||||
!props.projects.some(
|
||||
(project) => project.id === state.activeProject,
|
||||
));
|
||||
|
||||
if (setDefaultProject) {
|
||||
updates.activeProject = projects[0].id;
|
||||
))
|
||||
) {
|
||||
updates.activeProject = props.projects[0].id;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length) {
|
||||
updateState(updates);
|
||||
}
|
||||
}, [
|
||||
JSON.stringify(projects),
|
||||
JSON.stringify(flags),
|
||||
JSON.stringify(state),
|
||||
]);
|
||||
}, [JSON.stringify(props), JSON.stringify(state)]);
|
||||
|
||||
const { activeFlag, activeProject } = state;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user