-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(ui) Fix UI flickering when switching between glossary entities #7432
fix(ui) Fix UI flickering when switching between glossary entities #7432
Conversation
@@ -38,6 +39,14 @@ export default class EntityRegistry { | |||
return this.entities; | |||
} | |||
|
|||
getNonGlossaryEntities(): Array<Entity<any>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
@@ -77,6 +80,10 @@ function CreateGlossaryEntityModal(props: Props) { | |||
duration: 2, | |||
}); | |||
refetch(); | |||
if (isInGlossaryContext) { | |||
const parentNodeToUpdate = getParentNodeToUpdate(entityData, entityType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting.. So this is only the case when we are editing a glossary term in the context of the glossary... cool
isInGlossaryContext: boolean; | ||
entityData: GenericEntityProperties | null; | ||
setEntityData: (entityData: GenericEntityProperties | null) => void; | ||
updatedUrns: string[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These fields are less intuitive to understand than the others ... might be helpful to have comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -303,8 +298,7 @@ export const EntityProfile = <T, U>({ | |||
<> | |||
<OnboardingTour stepIds={stepIds} /> | |||
<EntityHead /> | |||
{customNavBar} | |||
{showBrowseBar && !customNavBar && <EntityProfileNavBar urn={urn} entityType={entityType} />} | |||
{showBrowseBar && <EntityProfileNavBar urn={urn} entityType={entityType} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice kinda like how we are removing custom browse bar
@@ -27,6 +29,7 @@ function EntityName(props: Props) { | |||
const { isNameEditable } = props; | |||
const refetch = useRefetch(); | |||
const entityRegistry = useEntityRegistry(); | |||
const { isInGlossaryContext, updatedUrns, setUpdatedUrns } = useGlossaryEntityData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit unfortunate but I see why we have to do it. To communicate this information...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah :/ just the tough thing with having the same info displayed in separate components with different fetching happening in different places.
if (isInGlossaryContext) { | ||
const oldParentToUpdate = getParentNodeToUpdate(entityData, entityType); | ||
const newParentToUpdate = selectedParentUrn || getGlossaryRootToUpdate(entityType); | ||
updateGlossarySidebar([oldParentToUpdate, newParentToUpdate], updatedUrns, setUpdatedUrns); | ||
} | ||
}, 2000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah so are we still doing the 2 second wait?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for now unfortunately..
setUpdatedUrns: (updatdUrns: string[]) => void; | ||
} | ||
|
||
export const GlossaryEntityContext = React.createContext<GlossaryEntityContextType>({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this solution!
isInGlossaryContext: boolean; | ||
entityData: GenericEntityProperties | null; | ||
setEntityData: (entityData: GenericEntityProperties | null) => void; | ||
updatedUrns: string[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -76,6 +76,8 @@ export const ENTITY_TYPES_WITH_MANUAL_LINEAGE = new Set([ | |||
EntityType.DataJob, | |||
]); | |||
|
|||
export const GLOSSARY_ENTITY_TYPES = [EntityType.GlossaryTerm, EntityType.GlossaryNode]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Very useful
const previousEntityData = usePrevious(entityData); | ||
|
||
useEffect(() => { | ||
// first check this is a glossary entity to prevent unnecessary comparisons in non-glossary context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comment
useEffect(() => { | ||
// first check this is a glossary entity to prevent unnecessary comparisons in non-glossary context | ||
if (GLOSSARY_ENTITY_TYPES.includes(entityType) && !isEqual(entityData, previousEntityData)) { | ||
setEntityData(entityData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
updatedUrns: string[], | ||
setUpdatedUrns: (updatdUrns: string[]) => void, | ||
) { | ||
setUpdatedUrns([...updatedUrns, ...parentNodesToUpdate]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a bit confusing how we are merging the updatedUrns themselves with the parent Node urns... Might be warranting of an explanation via comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally. i think the name change rec from below will help with this confusion as well
flex: 1; | ||
`; | ||
|
||
export default function GlossaryRoutes() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice & tight!
useEffect(() => { | ||
if (updatedUrns.includes(node.urn)) { | ||
refetch(); | ||
setUpdatedUrns(updatedUrns.filter((urn) => urn !== node.urn)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So after an update the leaf component is responsible for removing itself from the updated Urns list?
Minor: but maybe we consider calling this urnsToUpdate to make the transaction that's happening here clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, is there any risk in having a single array here?
Instead of urnsToUpdate and updatedUrns as separate structures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooo i definitely like having urnsToUpdate
as a name much more here. and yeah having a singular array i think is fine as long as we're smart about only updating state when se need to (like after the entity that needs to update refetches and only if it's already in the array).
Will update the name!
@@ -59,6 +63,17 @@ function GlossaryBrowser(props: Props) { | |||
} | |||
}, [refreshBrowser, refetchNodes, refetchTerms]); | |||
|
|||
useEffect(() => { | |||
if (updatedUrns.includes(ROOT_NODES)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Special case handling for rootNodes and rootTerms? (Where there is no parent) Got it. Might be worth adding a comment somewhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely agreed. a few comments around this change in general wouldn't hurt.
export const ROOT_NODES = 'rootNodes'; | ||
export const ROOT_TERMS = 'rootTerms'; | ||
|
||
export function getGlossaryRootToUpdate(entityType: EntityType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting how we have the special case values in this array. I didn't piece this together until i read all the code and took a step back. Just FYI
Fixes the whole page flickering we would see when switching between terms and nodes in the business glossary. This happened because each entity page itself rendered its own version of the lest side bar browse and the top parent nodes navigation. Whenever you switched to a new glossary entity, it would un-mount and re-mount the whole page causing the whole page to flicker. It also caused an excess of queries such as getting all root terms and root nodes on every change between glossary entities. It also refreshed your sidebar nav to close everything that's open which is a bad experience.
This PR pulls those components out and puts them in a new
GlossaryRoutes
component that wraps the term and node profiles with them as well as the root glossary page. These components are now fixed!The tough part is that these components rely on information retrieved from the entity profile component such as the parent nodes and what the current entity is (to know whether or not to highlight it on the sidebar and which parent nodes to default open when you land on a page). There's a few ways we could tackle this, but the simplest and easiest way is to share
entityData
from theEntityProfile
with a new higherGlossaryEntityContext
which the two nav components can access. WheneverentityData
changes and we're in a glossary context, we update the glossary context with new entity data.Finally, when we make edits (edit name, move a node/term, delete a node/term, create a node/term) the sidebar browser needs to be aware of those updates. What I implemented was passing in
updatedUrns
to the glossary context and have the components in that sidebar listen for those changing andrefetch
and remove themselves fromupdatedUrns
if need be. That way the sidebar component is always up to date with any mutation the user makes.Here's what it looks like now
https://user-images.githubusercontent.com/28656603/221290287-ed9ed1e2-6380-46f8-ab71-58c2582b633e.mov
Checklist