Skip to content
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

Conversation

chriscollins3456
Copy link
Collaborator

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 the EntityProfile with a new higher GlossaryEntityContext which the two nav components can access. Whenever entityData 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 and refetch and remove themselves from updatedUrns 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

  • The PR conforms to DataHub's Contributing Guideline (particularly Commit Message Format)
  • Links to related issues (if applicable)
  • Tests for the changes have been added/updated (if applicable)
  • Docs related to the changes have been added/updated (if applicable). If a new feature has been added a Usage Guide has been added for the same.
  • For any breaking change/potential downtime/deprecation/big changes an entry has been made in Updating DataHub

@@ -38,6 +39,14 @@ export default class EntityRegistry {
return this.entities;
}

getNonGlossaryEntities(): Array<Entity<any>> {
Copy link
Collaborator

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);
Copy link
Collaborator

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[];
Copy link
Collaborator

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

Copy link
Contributor

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} />}
Copy link
Collaborator

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();
Copy link
Collaborator

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...

Copy link
Collaborator Author

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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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>({
Copy link
Contributor

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[];
Copy link
Contributor

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];
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Collaborator

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]);
Copy link
Collaborator

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

Copy link
Collaborator Author

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() {
Copy link
Collaborator

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));
Copy link
Collaborator

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

Copy link
Collaborator

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

Copy link
Collaborator Author

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)) {
Copy link
Collaborator

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

Copy link
Collaborator Author

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) {
Copy link
Collaborator

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

@chriscollins3456 chriscollins3456 merged commit 4f78de6 into datahub-project:master Mar 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product PR or Issue related to the DataHub UI/UX
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants