Skip to content

Fixes #28608: API account tooltips are broken when table is sorted or refreshed#7061

Merged
clarktsiory merged 1 commit intoNormation:branches/rudder/9.1from
clarktsiory:bug_28608/api_account_tooltips_are_broken_when_table_is_sorted_or_refreshed
Apr 22, 2026
Merged

Fixes #28608: API account tooltips are broken when table is sorted or refreshed#7061
clarktsiory merged 1 commit intoNormation:branches/rudder/9.1from
clarktsiory:bug_28608/api_account_tooltips_are_broken_when_table_is_sorted_or_refreshed

Conversation

@clarktsiory
Copy link
Copy Markdown
Contributor

@clarktsiory clarktsiory commented Apr 8, 2026

https://issues.rudder.io/issues/28608

EDIT : New understanding of tooltips

  • the only attribute that seems to be universally working is data-bs-original-title, instead of title/data-bs-title

  • removeBsTooltips is only a hack for removing "stale" tooltips in some specific UX concerns ("the tooltip is frozen")

    • it leads to memory leaks : when using tooltips in Elm apps and e.g. filtering items, the DOM changes, but event handlers of tooltips are still there
    • it is used in this PR because a "button click" when opening a modal leads to a frozen one
  • we need a new function to properly dispose of tooltips, it's called disposeBsTooltips :

    • it needs to be always followed by an initialization of tooltips, because when "sorting elements", the DOM elements would swap
    • resetTooltips is a single Port that guarantees that we have "dispose", then "init", and it is used in this PR upon the update of "table filters"
  • to maximize performance, we could restrict tooltips actions to a selector of "tooltips within the Elm app" : just add a <div id="app-container"> and pass to the "dispose" and "init" functions


Adding a new function to dispose of tooltips completely : removeBsTooltips is to not work in this context because of how Elm swaps the DOM elements, by leaving tooltips at their previous location (which made the bug appear when : sorting the table, filtering, and adding and API account).

I could not get rid of them :

  • the previous one always remains with a data-bs-original-title :
image
  • sometimes it's the title that would remain, even on a - :
image

I tried removing every attributes (including the title) upon calling dispose() on the tooltip, and this solves the issue, but would make all tooltips not work globally when applying some filters on the table 🙃, so I didn't risk doing that

@clarktsiory clarktsiory marked this pull request as draft April 8, 2026 16:02
Comment on lines +926 to +935
// remove attributes, handlers left by tooltips: sometimes (with Elm replacing the DOM elements) they are still present
function disposeBsTooltips() {
document
.querySelectorAll('[data-bs-toggle="tooltip"],[data-bs-original-title]')
.forEach(tooltip => {
bootstrap.Tooltip.getInstance(tooltip)?.dispose()
tooltip.removeAttribute("data-bs-original-title")
tooltip.removeEventListener('hide.bs.tooltip', bsTooltipsRemovalListener)
tooltip.removeEventListener('show.bs.tooltip', bsTooltipsRemovalListener)
})
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this new function does the "unmounting" of the tooltip from the DOM element (at least in theory, from the doc)

But as explained in the description, several attributes still remain... it won't clean up correctly

Comment on lines +909 to +917
const bsTooltipsRemovalListener = () => removeBsTooltips()

function initBsTooltips(){
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
return tooltipTriggerList.map(function (tooltipTriggerEl) {
let dataTrigger = $(tooltipTriggerEl).attr('data-bs-trigger');
let trigger = dataTrigger === undefined ? 'hover' : dataTrigger;
tooltipTriggerEl.addEventListener('hide.bs.tooltip', () => {
removeBsTooltips();
});
tooltipTriggerEl.addEventListener('show.bs.tooltip', () => {
removeBsTooltips()
});
tooltipTriggerEl.addEventListener('hide.bs.tooltip', bsTooltipsRemovalListener);
tooltipTriggerEl.addEventListener('show.bs.tooltip', bsTooltipsRemovalListener);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding a stable reference to the event listener avoids duplicating the event listener function (it appeared several times when I looked in the inspector)

Also, this allows calling removeEventListener(..., registeredListener)

@RaphaelGauthier
Copy link
Copy Markdown
Member

I've tried different things that improve the result, here is my conclusion:

  • Adding a clearTooltips port was a good idea, but disposeBsTooltips() does not work as expected, removeBsTooltip() is more effectice as it actually destroy the generated .tooltip elements. So we better use this one in the subscribe() function.

  • As explained in the Bootstrap Tooltip documentation: "Feel free to use either title or data-bs-title in your HTML. When title is used, Popper will replace it automatically with data-bs-title when the element is rendered.". It looks like this creates another delay before the tooltip initialisation, so it is better to use ‘data-bs-title’ directly rather than ‘title’ attribute in order to avoid this extra delay

  • Finally, the timeout we use before each tooltip initialisation is one of the main causes of the problem.
    Its duration is purely arbitrary and may not correspond at all to the time required before initialising the tooltips.
    The best solution I have found is to wait until the elements containing the tooltips are present in the DOM, by using the MutationObserver api.

So instead of:

app.ports.initTooltips.subscribe(function (msg) {
  setTimeout(function () {
    initBsTooltips();
  }, 600);
});

We should do something like:

const tooltipSelector = '.dataTable [data-bs-toggle="tooltip"]';
app.ports.initTooltips.subscribe(function () {
  waitForElement(tooltipSelector).then((el) => {
    initBsTooltips();
  });
});

The last remaining issue (I hope 🤞) is the resolution delay of the MutationObserver's promise, which takes few millisecond to complete. But I can’t see how to improve on that.

@RaphaelGauthier
Copy link
Copy Markdown
Member

Last but not least, we should replace the attribute "title" with "data-bs-original-title"... This makes the generated .tooltip element actually disappeared 💀

model.ui
cmd =
if filters /= ui.filters then
Cmd.batch [ clearTooltips (), initTooltips () ]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cmd.batch is not garanteed to be sequential (clear and init sequentially)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I found no easy Elm solution for this (other than a rather complex library) : the solution I opted for was to have a single resetTooltips port.

Tooltips are always reset anyway... (and sometimes we want the actual "removeBsTooltips" for frozen ones)

@clarktsiory
Copy link
Copy Markdown
Contributor Author

PR updated with a new commit

@clarktsiory clarktsiory marked this pull request as ready for review April 16, 2026 14:31
@clarktsiory
Copy link
Copy Markdown
Contributor Author

clarktsiory commented Apr 20, 2026

PR updated with a new commit : forgot the initBsTooltips optimization

Comment thread webapp/sources/rudder/rudder-web/src/main/javascript/rudder/rudder.js Outdated
@clarktsiory
Copy link
Copy Markdown
Contributor Author

clarktsiory commented Apr 22, 2026

PR updated with a new commit : fix the initBsTooltips selector optimization

@clarktsiory
Copy link
Copy Markdown
Contributor Author

OK, squash merging this PR

@clarktsiory clarktsiory force-pushed the bug_28608/api_account_tooltips_are_broken_when_table_is_sorted_or_refreshed branch from a03f6e6 to 233cc06 Compare April 22, 2026 08:44
@clarktsiory clarktsiory merged commit 233cc06 into Normation:branches/rudder/9.1 Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants