Skip to content

[Dashboard] Skip automatic scroll when panel is visible#233226

Merged
cqliu1 merged 24 commits intoelastic:mainfrom
cqliu1:dashboard/skip-scroll-to-panel-in-view
Sep 16, 2025
Merged

[Dashboard] Skip automatic scroll when panel is visible#233226
cqliu1 merged 24 commits intoelastic:mainfrom
cqliu1:dashboard/skip-scroll-to-panel-in-view

Conversation

@cqliu1
Copy link
Contributor

@cqliu1 cqliu1 commented Aug 27, 2025

Summary

Closes #230175.

This adds a check in dashboardApi.scrollToPanel comparing the panel's position and viewport and skips the automatic scroll if the panel is already in view. This improves the UX by removing the slightly jarring auto scroll whenever you edit any panel, especially noticeable with any panel that supports inline editing in Dashboard.

Before

Aug-27-2025 15-38-34

After

Aug-27-2025 15-38-21

Per @nreese feedback, I add a dashboardContainerRef to the dashboardInternalApi so we don't have to rely on classes to retrieve the dashboard container DOM element. This way we also avoid prop drilling the container ref from the dashboardRenderer component and make it accessible from anywhere the dashboardApi is available.

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

  • Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the docker list
  • This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The release_note:breaking label should be applied in these situations.
  • Flaky Test Runner was used on any tests changed
  • The PR description includes the appropriate Release Notes section, and the correct release_note:* label is applied per the guidelines
  • Review the backport guidelines and apply applicable backport:* labels.

Identify risks

Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging.

@cqliu1 cqliu1 requested a review from a team as a code owner August 27, 2025 22:39
@cqliu1 cqliu1 added Feature:Dashboard Dashboard related features Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// loe:small Small Level of Effort impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. Project:Dashboard Usability Related to the Dashboard Usability initiative labels Aug 27, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-presentation (Team:Presentation)

@cqliu1 cqliu1 added the v9.2.0 label Aug 27, 2025
const clientBottom = window.innerHeight;
const { top: panelTop, bottom: panelBottom } = panelRef.getBoundingClientRect();

console.log(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets remove console.log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 013211e

scrollPosition = undefined;
} else {
panelRef.scrollIntoView({ block: 'start', behavior: 'smooth' });
const dashboardTop =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a unit test for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 38c09f6

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the test file did not make the commit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, they're in 17b1d3d

@cqliu1 cqliu1 added release_note:fix backport:skip This PR does not require backporting labels Aug 29, 2025
data-test-subj="dashboardContainer"
css={styles.renderer}
ref={(e) => (dashboardContainerRef.current = e)}
ref={dashboardInternalApi.setDashboardContainerRef}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause problems. dashboardInternalApi is loaded async and may not be available when ref is provided. To avoid race conditions, how about

ref={(e) => {
  if (dashboardInternalApi) {
    dashboardInternalApi.setDashboardContainerRef(dashboardInternalApi);
    return;
  }

  dashboardContainerRef.current = e;
}}

Then when dashboardInternalApi is set, if dashboardContainerRef.current call setDashboardContainerRef

if (dashboardInternalApi) {
dashboardInternalApi.setDashboardContainerRef(dashboardContainerRef.current);
}
}, [dashboardInternalApi, dashboardContainerRef]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refs do not trigger re-renders so lets remove dashboardContainerRef from this useEffect dependency array.

dashboardContainerRef$ may already be this value if ref callback fires first. So how about checking equality before calling set.

Copy link
Contributor Author

@cqliu1 cqliu1 Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in c9ef5db

and added the equality check here ba933b9

Copy link
Contributor

@Heenawter Heenawter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI We no longer scroll to the bottom of the dashboard when adding a new section:

Screen.Recording.2025-09-02.at.12.23.00.PM.mov
@Heenawter
Copy link
Contributor

Heenawter commented Sep 11, 2025

@cqliu1 Oops! Looks like I had the following in my config/kibana.dev.yml and that was impacting the scroll behaviour 😬

feature_flags.overrides:
  core.chrome.layoutType: 'grid'

Ignore my above comment 🤦

Comment on lines 145 to 147
if (dashboardInternalApi) {
dashboardInternalApi.setDashboardContainerRef(e);
}
Copy link
Contributor

@Heenawter Heenawter Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we setting dashboardContainerRef twice? Do we need this if we have the useEffect above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect is a back up to account for a race condition where dashboardInternalApi may not be initialized when the ref is set initially. This might be redundant though. I can remove this, and we can just rely on the useEffect

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just keep this and then listen for changes to dashboardContainerRef$ via usePublishingSubjects? Rather than declaring dashboardContainerRef locally - that feels confusing to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From earlier feedback, @nreese wanted to avoid a race condition where the dashboardInternalApi potentially isn't available when setting the ref. #233226 (comment)

Copy link
Contributor

@Heenawter Heenawter Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just use the useEffect to set dashboardContainerRef (dependant on dashboardInternalApi) then - the double setting seems redundant to me

Comment on lines 59 to 61
const dashboardContainerRef = useStateFromPublishingSubject(
dashboardInternalApi?.dashboardContainerRef$ || new BehaviorSubject(null)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry - I was wrong but didn't delete my original comment in time :) see the updated comment here: #233226 (comment)

return;
}

// only store ref locally if dashboardInternalApi is not yet available
Copy link
Contributor

@nreese nreese Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to cause some problems when savedObjectId changes and useEffect that loads dashboard re-runs. Here is the scenario

  1. load dashboard returns dashboardInternalApi and setDashboardApi is called.
  2. dashboardInternalApi useEffect calls dashboardInternalApi.setDashboardContainerRef(undefined)
  3. ref returns. dashboardInternalApi.setDashboardContainerRef(newRef) is called. dashboardContainer ref is not set.
  4. saved object id changes, causing load dashboard to run again. Now when dashboardInternalApi is returned and setDashboardApi is called triggering dashboardInternalApi useEffect
  5. dashboardContainer ref is undefined so dashboardInternalApi.setDashboardContainerRef is not called and dashboardInteralApi does not have dashboard container ref.

Regardless of whether dashboardInteralApi has the right ref, this component should always set dashboardContainerRef.current.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in afa173d

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
dashboard 646.1KB 646.5KB +388.0B

History

Copy link
Contributor

@Heenawter Heenawter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - tested locally + code review. 💃 This makes the editing experience so much better! I'm excited to see it land 🎉

Copy link
Contributor

@nreese nreese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kibana-presentation changes LGTM. Thanks for taking the extra time to clean up dashboardContainerRef usage and consolidating this into a single location

@cqliu1 cqliu1 merged commit db30388 into elastic:main Sep 16, 2025
12 checks passed
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Sep 24, 2025
rylnd pushed a commit to rylnd/kibana that referenced this pull request Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting Feature:Dashboard Dashboard related features impact:high Addressing this issue will have a high level of impact on the quality/strength of our product. loe:small Small Level of Effort Project:Dashboard Usability Related to the Dashboard Usability initiative release_note:fix Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// v9.2.0

5 participants