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
[Dashboard] Prevent unnecessary loss of dashboard unsaved state #167707
[Dashboard] Prevent unnecessary loss of dashboard unsaved state #167707
Conversation
25b1bca
to
192923a
Compare
107f3e8
to
c8daaf3
Compare
c8daaf3
to
484495e
Compare
92c1e6a
to
c23d21c
Compare
c23d21c
to
19cdc32
Compare
if (result.error.statusCode === 404) { | ||
// Save object not found error | ||
dashboardBackup.clearState(result.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ultimately what fixed this flakiness 🎉 We should only clear the state specifically if the CM service returned an "Object not found" error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good that you used the status code directly rather than testing against the error message!
return isEqual(oldDashboardsWithUnsavedChanges, dashboardsWithUnsavedChanges) | ||
? oldDashboardsWithUnsavedChanges | ||
: dashboardsWithUnsavedChanges; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only change the array reference if the contents of the array have changed - this prevents an infinite React loop that would happen when an error was caught in the DashboardUnsavedListing
component.
try { | ||
const response = await contentManagement.client.get< | ||
DashboardCrudTypes['GetIn'], | ||
DashboardCrudTypes['GetOut'] | ||
>({ | ||
contentTypeId: DASHBOARD_CONTENT_ID, | ||
id, | ||
}) | ||
.then((result) => { | ||
dashboardContentManagementCache.addDashboard(result); | ||
return { id, status: 'success', attributes: result.item.attributes }; | ||
}) | ||
.catch((e) => ({ status: 'error', error: e.body, id })); | ||
}); | ||
if (response.item.error) { | ||
throw response.item.error; | ||
} | ||
|
||
return response as FindDashboardsByIdResponse; | ||
dashboardContentManagementCache.addDashboard(response); | ||
return { | ||
id, | ||
status: 'success', | ||
attributes: response.item.attributes, | ||
references: response.item.references, | ||
}; | ||
} catch (e) { | ||
return { | ||
status: 'error', | ||
error: e.body || e.message, | ||
id, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to refactor this - for some reason, the old code wasn't catching errors being thrown in contentManagement.client.get
. Now, no matter where the error is thrown, it should (hopefully) be caught 🫠
@@ -39,7 +39,7 @@ export interface DashboardContentManagementRequiredServices { | |||
|
|||
export interface DashboardContentManagementService { | |||
findDashboards: FindDashboardsService; | |||
deleteDashboards: (ids: string[]) => void; | |||
deleteDashboards: (ids: string[]) => Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated, but noticed during my investigation: this method was async
but, since the return value type didn't match, you would get a TS warning when awaiting
it.
if (await PageObjects.dashboard.onDashboardLandingPage()) { | ||
await testSubjects.existOrFail('unsavedDashboardsCallout'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this so that, if this failure happens again, it will be clearer why the failure is happening; because the failure (and the corresponding failure screenshot) happened on the dashboard itself, it took me awhile to confirm that the failure was happening due to the session storage being cleared. Failing on the listing page makes this clearer.
Pinging @elastic/kibana-presentation (Team:Presentation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to see this opened, it was quite a journey to understand this flaky test. Changes LGTM! Left one nit.
src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts
Outdated
Show resolved
Hide resolved
💔 Build FailedFailed CI Steps
Test Failures
Metrics [docs]Async chunks
History
To update your PR or re-run it, just comment with: |
…tic#167707) Closes elastic#167661 ## Summary After a whole bunch of investigation, I ultimately realized that the attached test was flaky because the dashboard session storage was being cleared in the `DashboardUnsavedListing` component. When loading the unsaved dashboards, we used to remove the unsaved state for dashboards that returned **any** error from the CM service - this was designed so that, if a dashboard was deleted, we would remove it from the unsaved dashboard listing callout. However, as an unintended consequence, **other** errors, which should **not** cause the unsaved state to be lost, also caused it to be cleared. Since I could only replicate **some** of the possible CM errors locally, it was impossible to narrow down exactly what error was being thrown in the attached flaky test since the FTR does not provide console logs. Therefore, rather than **preventing** that specific error from clearing the session storage, I instead made it so that **only** `404` errors (i.e. `"Saved object not found"` errors) cause the session storage to be cleared - this will guarantee that we only remove the unsaved state from the session storage if we know **for sure** that the dashboard has been deleted. Any other errors that are thrown by the CM will **not** cause the unsaved state to be unnecessarily lost. Also, in my attempt to solve the above flaky test, I discovered and fixed the following: 1. Previously, when an error was thrown and caught in the `DashboardUnsavedListing` component, the `refreshUnsavedDashboards` would cause a `useEffect` infinite loop because the reference for the `unsavedDashboardIds` array would always be different even if the contents of the array were identical. This PR fixes that by ensuring the array reference **only** changes if the contents change. 2. Our previous way of catching errors in the `findDashboardById` method was not reliable, and did not catch errors that were thrown in, for example, the CM client `get` method. I refactored this so that all errors should now be caught. ### [Flaky Test Runner](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3554) ![image](https://github.com/elastic/kibana/assets/8698078/1bcd9d6a-0c37-43ee-b5d6-f418cf878b41) ### 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 ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
…tic#167707) Closes elastic#167661 ## Summary After a whole bunch of investigation, I ultimately realized that the attached test was flaky because the dashboard session storage was being cleared in the `DashboardUnsavedListing` component. When loading the unsaved dashboards, we used to remove the unsaved state for dashboards that returned **any** error from the CM service - this was designed so that, if a dashboard was deleted, we would remove it from the unsaved dashboard listing callout. However, as an unintended consequence, **other** errors, which should **not** cause the unsaved state to be lost, also caused it to be cleared. Since I could only replicate **some** of the possible CM errors locally, it was impossible to narrow down exactly what error was being thrown in the attached flaky test since the FTR does not provide console logs. Therefore, rather than **preventing** that specific error from clearing the session storage, I instead made it so that **only** `404` errors (i.e. `"Saved object not found"` errors) cause the session storage to be cleared - this will guarantee that we only remove the unsaved state from the session storage if we know **for sure** that the dashboard has been deleted. Any other errors that are thrown by the CM will **not** cause the unsaved state to be unnecessarily lost. Also, in my attempt to solve the above flaky test, I discovered and fixed the following: 1. Previously, when an error was thrown and caught in the `DashboardUnsavedListing` component, the `refreshUnsavedDashboards` would cause a `useEffect` infinite loop because the reference for the `unsavedDashboardIds` array would always be different even if the contents of the array were identical. This PR fixes that by ensuring the array reference **only** changes if the contents change. 2. Our previous way of catching errors in the `findDashboardById` method was not reliable, and did not catch errors that were thrown in, for example, the CM client `get` method. I refactored this so that all errors should now be caught. ### [Flaky Test Runner](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3554) ![image](https://github.com/elastic/kibana/assets/8698078/1bcd9d6a-0c37-43ee-b5d6-f418cf878b41) ### 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 ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
Closes #167661
Summary
After a whole bunch of investigation, I ultimately realized that the attached test was flaky because the dashboard session storage was being cleared in the
DashboardUnsavedListing
component. When loading the unsaved dashboards, we used to remove the unsaved state for dashboards that returned any error from the CM service - this was designed so that, if a dashboard was deleted, we would remove it from the unsaved dashboard listing callout. However, as an unintended consequence, other errors, which should not cause the unsaved state to be lost, also caused it to be cleared.Since I could only replicate some of the possible CM errors locally, it was impossible to narrow down exactly what error was being thrown in the attached flaky test since the FTR does not provide console logs. Therefore, rather than preventing that specific error from clearing the session storage, I instead made it so that only
404
errors (i.e."Saved object not found"
errors) cause the session storage to be cleared - this will guarantee that we only remove the unsaved state from the session storage if we know for sure that the dashboard has been deleted. Any other errors that are thrown by the CM will not cause the unsaved state to be unnecessarily lost.Also, in my attempt to solve the above flaky test, I discovered and fixed the following:
DashboardUnsavedListing
component, therefreshUnsavedDashboards
would cause auseEffect
infinite loop because the reference for theunsavedDashboardIds
array would always be different even if the contents of the array were identical. This PR fixes that by ensuring the array reference only changes if the contents change.findDashboardById
method was not reliable, and did not catch errors that were thrown in, for example, the CM clientget
method. I refactored this so that all errors should now be caught.Flaky Test Runner
Checklist
For maintainers