Skip to content

Conversation

@nickofthyme
Copy link
Contributor

Backport

This will backport the following commits from main to 8.19:

Questions ?

Please refer to the Backport tool documentation

## Summary

This PR fixes an issue in which the SO save modal in Lens, Dashboard and
others, allowed the user to spam the save button, resulting in multiple
saved instances.

### Demo

#### Before

https://github.com/user-attachments/assets/70ddc6c1-b99d-4758-8333-fe9ab47474a4

#### After

https://github.com/user-attachments/assets/84f016dd-05d9-441c-864c-b06160c9d531

Fixes elastic#233906

## Details

When attempting to save any SO using the
[`SavedObjectSaveModal`](https://github.com/elastic/kibana/blob/07cc9e9c1a271c37f42c84f57a1600596070fd9c/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx#L95),
the logic was attempting to `await` the call to `onSave` and block any
additional calls. This was working correctly to set the `isLoading`
state, but the `onSave` callback is not expecting a `Promise` to be
returned so it has no affect and the `onSave` is immediately resolved,
unsetting the loading state and thus allowing multiple calls to save.

So the issue is that `onSave` should expect `() => Promise<void>` not
`() => void`. A crude fix to this could be to throttle the calls to
`onSave` and preventing and future clicks until the save is eventually
complete. But if the save takes longer or fails this will enable the
save button and allow clicking again, creating other possible issues and
not solving the root of the problem.

So the chain of `onSave` is broken from calling it here to the eventual
function up the call stack that is performing the save. This involves
updating the chain of promised from the `onSave` prop all the way up to
the root consumer, which is typically calling an async function.

Further complicating things.... there is this dreadful, I mean
deprecated, `showSaveModal` function that wraps a modal component
(almost like a HOC) and calls `props.onSave` of the passed component to
determine the success state of the call to `onSave`.

https://github.com/elastic/kibana/blob/07cc9e9c1a271c37f42c84f57a1600596070fd9c/src/platform/plugins/shared/saved_objects/public/save_modal/show_saved_object_save_modal.tsx#L34-L37

Here's a rough example...

```tsx
import { showSaveModal } from "@kbn/saved-objects-plugin/public";

export function openSaveModal() {
  // ...

  showSaveModal(
    <SavedObjectSaveModal
      ...
      onSave={async () => {
        const response = await doTheSave();

        if(response.success) {
          return { id: response.id };
        } else {
          return { error: 'failed to save' }
        }
      }}
    />
  );
}
```

The strange thing is that the `SavedObjectSaveModal.onSave` doesn't need
the `SaveResult` to be returned and these modal components are used in
various places but only some are passed to `showSaveModal`.

So I cleaned up this code as well to expect the `SaveResult` when using
`showSaveModal` and `void` otherwise.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] 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.
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
- [x] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.

## Release Notes

Fixes issue with save modal allowing duplicate saves of dashboard,
visualizations and others.

---------

Co-authored-by: Marco Vettorello <[email protected]>
Co-authored-by: Davis McPhee <[email protected]>
(cherry picked from commit 8415158)

# Conflicts:
#	src/platform/plugins/private/links/public/content_management/save_to_library.tsx
#	src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.tsx
#	src/platform/plugins/shared/discover/public/application/main/components/top_nav/save_discover_session/save_modal.tsx
#	src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.test.tsx
#	src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx
#	src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx
#	src/platform/plugins/shared/saved_objects/public/save_modal/show_saved_object_save_modal.tsx
#	src/platform/plugins/shared/visualizations/public/legacy/embeddable/attribute_service.tsx
#	src/platform/plugins/shared/visualizations/public/visualize_app/utils/get_top_nav_config.tsx
#	x-pack/platform/plugins/private/graph/public/components/save_modal.tsx
#	x-pack/platform/plugins/shared/maps/public/routes/map_page/top_nav_config.tsx
@nickofthyme nickofthyme added the backport This PR is a backport of another PR label Sep 9, 2025
@nickofthyme nickofthyme enabled auto-merge (squash) September 9, 2025 18:50
@botelastic botelastic bot added the Team:actionable-obs Formerly "obs-ux-management", responsible for SLO, o11y alerting, significant events, & synthetics. label Sep 9, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/obs-ux-management-team (Team:obs-ux-management)

@nickofthyme nickofthyme requested a review from a team September 10, 2025 17:21
@nickofthyme nickofthyme added the Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t// label Sep 10, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-visualizations (Team:Visualizations)

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
presentationUtil 129 130 +1

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
savedObjects 96 89 -7

Async chunks

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

id before after diff
aiops 513.3KB 513.3KB +15.0B
dashboard 703.0KB 703.0KB +28.0B
discover 1.1MB 1.1MB +29.0B
graph 381.5KB 381.6KB +23.0B
lens 1.5MB 1.5MB +57.0B
links 116.0KB 116.0KB +14.0B
maps 3.1MB 3.1MB +14.0B
ml 5.5MB 5.5MB +15.0B
presentationUtil 76.9KB 77.1KB +203.0B
slo 1012.3KB 1012.3KB +10.0B
synthetics 1.0MB 1.0MB +5.0B
visualizations 404.5KB 404.5KB +18.0B
total +431.0B

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
presentationUtil 8.2KB 8.3KB +126.0B
savedObjects 15.5KB 15.5KB +80.0B
total +206.0B
Unknown metric groups

API count

id before after diff
presentationUtil 106 108 +2
savedObjects 97 92 -5
total -3

async chunk count

id before after diff
presentationUtil 9 10 +1

ESLint disabled line counts

id before after diff
presentationUtil 10 11 +1

References to deprecated APIs

id before after diff
dashboard 9 13 +4
discover 10 12 +2
graph 65 67 +2
links 3 5 +2
maps 26 28 +2
visualizations 55 59 +4
total +16

Total ESLint disabled count

id before after diff
presentationUtil 10 11 +1

History

@nickofthyme nickofthyme merged commit 937cdcc into elastic:8.19 Sep 15, 2025
8 checks passed
@nickofthyme nickofthyme deleted the backport/8.19/pr-233933 branch September 15, 2025 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport This PR is a backport of another PR Team:actionable-obs Formerly "obs-ux-management", responsible for SLO, o11y alerting, significant events, & synthetics. Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t//

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants