Skip to content

[Dashboard] Fix error handling in "Save to library" modal for Links panel#231168

Merged
olapawlus merged 6 commits intoelastic:mainfrom
olapawlus:errorHandlingLinks
Aug 13, 2025
Merged

[Dashboard] Fix error handling in "Save to library" modal for Links panel#231168
olapawlus merged 6 commits intoelastic:mainfrom
olapawlus:errorHandlingLinks

Conversation

@olapawlus
Copy link
Member

Closes #231164

When saving a Links panel to the library from the Dashboard, errors during the save process are not properly caught or displayed to the user.

This PR fixes the error handling and ensures that an error toast is shown to the user.

Video Demonstration of the fix:

Screen.Recording.2025-08-08.at.16.14.58.mov
@olapawlus olapawlus self-assigned this Aug 8, 2025
@olapawlus olapawlus added bug Fixes for quality problems that affect the customer experience Feature:Dashboard Dashboard related features Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// loe:small Small Level of Effort impact:medium Addressing this issue will have a medium level of impact on the quality/strength of our product. labels Aug 8, 2025
@olapawlus olapawlus marked this pull request as ready for review August 8, 2025 19:41
@olapawlus olapawlus requested a review from a team as a code owner August 8, 2025 19:41
@elasticmachine
Copy link
Contributor

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

@olapawlus olapawlus added release_note:fix backport:version Backport to applied version labels v9.2.0 labels Aug 8, 2025
@nreese nreese self-requested a review August 8, 2025 20:05
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.

Thanks for opening this issue and a PR to resolve the issue. I think the current solution resolves the issue too high in the call stack, and leaves other consumers of showSaveModal vulnerable to the same problem. For example, if you try to save a map with no network connect, the save model hangs just like this issue.

So instead of solving the problem for one instance in src/platform/plugins/private/links/public/content_management/save_to_library.tsx, lets solve the problem for all users and make showSaveModal more robust by putting the try/catch around onSave in src/platform/plugins/shared/saved_objects/public/save_modal/show_saved_object_save_modal.tsx

const onSaveConfirmed: MinimalSaveModalProps['onSave'] = async (...args) => {
        try {
          const response = await onSave(...args);
          // close modal if we either hit an error or the saved object got an id
          if (Boolean(isSuccess(response) ? response.id : response.error)) {
            closeModal();
          }
          return response;
        } catch(error) {
          closeModal();
          return { error };
        }
      };

Then links only needs one change, in src/platform/plugins/private/links/public/editor/get_editor_flyout.tsx, where runSaveToLibrary is called. onSaveToLibrary should throw if saveResult.error

onSaveToLibrary={async (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => {
        const newState = {
          ...initialState,
          links: newLinks,
          layout: newLayout,
        };
        if (initialState?.savedObjectId) {
          const { savedObjectId, ...updateState } = newState;
          await linksClient.update({
            id: initialState.savedObjectId,
            data: {
              ...updateState,
              links: serializeResolvedLinks(newLinks),
            },
            options: { references: [] },
          });
          onCompleteEdit?.(newState);
          closeFlyout();
        } else {
          const saveResult = await runSaveToLibrary(newState);
          if (saveResult?.error) throw saveResult.error;
          onCompleteEdit?.(saveResult);
          // If saveResult is undefined, the user cancelled the save as modal and we should not close the flyout
          if (saveResult) closeFlyout();
        }
      }}

Then if wanted, try/catch could be removed from src/platform/plugins/private/links/public/content_management/save_to_library.tsx

@olapawlus olapawlus requested a review from a team as a code owner August 11, 2025 14:35
@olapawlus
Copy link
Member Author

Thanks for opening this issue and a PR to resolve the issue. I think the current solution resolves the issue too high in the call stack, and leaves other consumers of showSaveModal vulnerable to the same problem. For example, if you try to save a map with no network connect, the save model hangs just like this issue.

So instead of solving the problem for one instance in src/platform/plugins/private/links/public/content_management/save_to_library.tsx, lets solve the problem for all users and make showSaveModal more robust by putting the try/catch around onSave in src/platform/plugins/shared/saved_objects/public/save_modal/show_saved_object_save_modal.tsx

const onSaveConfirmed: MinimalSaveModalProps['onSave'] = async (...args) => {
        try {
          const response = await onSave(...args);
          // close modal if we either hit an error or the saved object got an id
          if (Boolean(isSuccess(response) ? response.id : response.error)) {
            closeModal();
          }
          return response;
        } catch(error) {
          closeModal();
          return { error };
        }
      };

Then links only needs one change, in src/platform/plugins/private/links/public/editor/get_editor_flyout.tsx, where runSaveToLibrary is called. onSaveToLibrary should throw if saveResult.error

onSaveToLibrary={async (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => {
        const newState = {
          ...initialState,
          links: newLinks,
          layout: newLayout,
        };
        if (initialState?.savedObjectId) {
          const { savedObjectId, ...updateState } = newState;
          await linksClient.update({
            id: initialState.savedObjectId,
            data: {
              ...updateState,
              links: serializeResolvedLinks(newLinks),
            },
            options: { references: [] },
          });
          onCompleteEdit?.(newState);
          closeFlyout();
        } else {
          const saveResult = await runSaveToLibrary(newState);
          if (saveResult?.error) throw saveResult.error;
          onCompleteEdit?.(saveResult);
          // If saveResult is undefined, the user cancelled the save as modal and we should not close the flyout
          if (saveResult) closeFlyout();
        }
      }}

Then if wanted, try/catch could be removed from src/platform/plugins/private/links/public/content_management/save_to_library.tsx

Thanks for opening this issue and a PR to resolve the issue. I think the current solution resolves the issue too high in the call stack, and leaves other consumers of showSaveModal vulnerable to the same problem. For example, if you try to save a map with no network connect, the save model hangs just like this issue.

So instead of solving the problem for one instance in src/platform/plugins/private/links/public/content_management/save_to_library.tsx, lets solve the problem for all users and make showSaveModal more robust by putting the try/catch around onSave in src/platform/plugins/shared/saved_objects/public/save_modal/show_saved_object_save_modal.tsx

const onSaveConfirmed: MinimalSaveModalProps['onSave'] = async (...args) => {
        try {
          const response = await onSave(...args);
          // close modal if we either hit an error or the saved object got an id
          if (Boolean(isSuccess(response) ? response.id : response.error)) {
            closeModal();
          }
          return response;
        } catch(error) {
          closeModal();
          return { error };
        }
      };

Then links only needs one change, in src/platform/plugins/private/links/public/editor/get_editor_flyout.tsx, where runSaveToLibrary is called. onSaveToLibrary should throw if saveResult.error

onSaveToLibrary={async (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => {
        const newState = {
          ...initialState,
          links: newLinks,
          layout: newLayout,
        };
        if (initialState?.savedObjectId) {
          const { savedObjectId, ...updateState } = newState;
          await linksClient.update({
            id: initialState.savedObjectId,
            data: {
              ...updateState,
              links: serializeResolvedLinks(newLinks),
            },
            options: { references: [] },
          });
          onCompleteEdit?.(newState);
          closeFlyout();
        } else {
          const saveResult = await runSaveToLibrary(newState);
          if (saveResult?.error) throw saveResult.error;
          onCompleteEdit?.(saveResult);
          // If saveResult is undefined, the user cancelled the save as modal and we should not close the flyout
          if (saveResult) closeFlyout();
        }
      }}

Then if wanted, try/catch could be removed from src/platform/plugins/private/links/public/content_management/save_to_library.tsx

Thanks so much for the really helpful explanation of the pattern with returning { error } and then throwing it higher up in the code!
I also added the same kind of handling in the checkForDuplicateTitle, because before the toast wasn’t showing if the error happened during the checkForDuplicateTitle function used in saveToLibrary. The toast also didn’t show when I removed the try-catch from saveToLibrary. Now the toast shows correctly.

@elasticmachine
Copy link
Contributor

elasticmachine commented Aug 11, 2025

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should call batchBackfillRuleGaps correctly
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should call batchBackfillRuleGaps correctly
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should ensure the user is authorized for each combination of ruleTypeId and consumer found in the rules
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should ensure the user is authorized for each combination of ruleTypeId and consumer found in the rules
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should log an audit event when the rule fails at the authorization step
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should log an audit event when the rule fails at the authorization step
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should refresh the index of the event client once after scheduling the backfilling of all gaps
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should refresh the index of the event client once after scheduling the backfilling of all gaps
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with errored rules
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with errored rules
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with rules that succeeded
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with rules that succeeded
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with rules that were skipped
  • [job] [logs] Jest Tests #19 / bulkFillGapsByRuleIds should return a list with rules that were skipped
  • [job] [logs] FTR Configs #66 / discover/group6 discover unsaved changes badge should not show a badge after loading a saved search, only after changes

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
links 100.6KB 100.6KB +60.0B

Page load bundle

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

id before after diff
savedObjects 10.5KB 10.5KB +35.0B

History

cc @olapawlus

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
code review and tested in chrome

@olapawlus olapawlus merged commit ab00f4d into elastic:main Aug 13, 2025
13 checks passed
fkanout pushed a commit to fkanout/kibana that referenced this pull request Aug 14, 2025
…anel (elastic#231168)

Closes elastic#231164

When saving a Links panel to the library from the Dashboard, errors
during the save process are not properly caught or displayed to the
user.

This PR fixes the error handling and ensures that an error toast is
shown to the user.

Video Demonstration of the fix:



https://github.com/user-attachments/assets/a376975c-267b-401f-ad25-d493aef0c7e2
@kibanamachine kibanamachine added the backport missing Added to PRs automatically when the are determined to be missing a backport. label Aug 15, 2025
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

1 similar comment
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

NicholasPeretti pushed a commit to NicholasPeretti/kibana that referenced this pull request Aug 18, 2025
…anel (elastic#231168)

Closes elastic#231164

When saving a Links panel to the library from the Dashboard, errors
during the save process are not properly caught or displayed to the
user.

This PR fixes the error handling and ensures that an error toast is
shown to the user.

Video Demonstration of the fix:



https://github.com/user-attachments/assets/a376975c-267b-401f-ad25-d493aef0c7e2
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

5 similar comments
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

qn895 pushed a commit to qn895/kibana that referenced this pull request Aug 26, 2025
…anel (elastic#231168)

Closes elastic#231164

When saving a Links panel to the library from the Dashboard, errors
during the save process are not properly caught or displayed to the
user.

This PR fixes the error handling and ensures that an error toast is
shown to the user.

Video Demonstration of the fix:



https://github.com/user-attachments/assets/a376975c-267b-401f-ad25-d493aef0c7e2
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

14 similar comments
@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@kibanamachine
Copy link
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 231168 locally
cc: @olapawlus

@olapawlus olapawlus added the backport:skip This PR does not require backporting label Sep 17, 2025
@kibanamachine kibanamachine removed the backport missing Added to PRs automatically when the are determined to be missing a backport. label Sep 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 backport:version Backport to applied version labels bug Fixes for quality problems that affect the customer experience Feature:Dashboard Dashboard related features impact:medium Addressing this issue will have a medium level of impact on the quality/strength of our product. loe:small Small Level of Effort release_note:fix Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// v9.2.0

5 participants