diff --git a/changelogs/fragments/6756.yml b/changelogs/fragments/6756.yml new file mode 100644 index 000000000000..9cad7bffc99d --- /dev/null +++ b/changelogs/fragments/6756.yml @@ -0,0 +1,2 @@ +fix: +- Show error toast when fail to delete saved objects ([#6756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6756)) \ No newline at end of file diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index fdff7b9d913b..6c56eed79fe4 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -74,6 +74,146 @@ exports[`SavedObjectsTable delete should show a confirm modal 1`] = ` `; +exports[`SavedObjectsTable delete should show error toast when failing to delete saved objects 1`] = ` + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } +> +

+ +

+ +
+`; + +exports[`SavedObjectsTable delete should show error toast when failing to delete saved objects 2`] = ` + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } +> +

+ +

+ +
+`; + exports[`SavedObjectsTable export should allow the user to choose when exporting all 1`] = ` { // Set some as selected component.instance().onSelectionChanged(mockSelectedSavedObjects); + await component.instance().onDelete(); + component.update(); + expect(component.state('isShowingDeleteConfirmModal')).toBe(true); await component.instance().delete(); @@ -612,6 +615,53 @@ describe('SavedObjectsTable', () => { { force: true } ); expect(component.state('selectedSavedObjects').length).toBe(0); + expect(notifications.toasts.addDanger).not.toHaveBeenCalled(); + expect(component.state('isDeleting')).toBe(false); + expect(component.state('isShowingDeleteConfirmModal')).toBe(false); + }); + + it('should show error toast when failing to delete saved objects', async () => { + const mockSelectedSavedObjects = [ + { id: '1', type: 'index-pattern' }, + ] as SavedObjectWithMetadata[]; + + const mockSavedObjects = mockSelectedSavedObjects.map((obj) => ({ + id: obj.id, + type: obj.type, + source: {}, + })); + + const mockSavedObjectsClient = { + ...defaultProps.savedObjectsClient, + bulkGet: jest.fn().mockImplementation(() => ({ + savedObjects: mockSavedObjects, + })), + delete: jest.fn().mockImplementation(() => { + throw new Error('Unable to delete saved objects'); + }), + }; + + const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient }); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + // Set some as selected + component.instance().onSelectionChanged(mockSelectedSavedObjects); + await component.instance().onDelete(); + component.update(); + expect(component.state('isShowingDeleteConfirmModal')).toBe(true); + expect(component.find('EuiConfirmModal')).toMatchSnapshot(); + + await component.instance().delete(); + component.update(); + expect(notifications.toasts.addDanger).toHaveBeenCalled(); + // If user fail to delete the saved objects, the delete modal will continue to display + expect(component.state('isShowingDeleteConfirmModal')).toBe(true); + expect(component.find('EuiConfirmModal')).toMatchSnapshot(); + expect(component.state('isDeleting')).toBe(false); }); }); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 56e7950efeea..04d7a46d24d2 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -589,7 +589,7 @@ export class SavedObjectsTable extends Component { - const { savedObjectsClient } = this.props; + const { savedObjectsClient, notifications } = this.props; const { selectedSavedObjects, isDeleting } = this.state; if (isDeleting) { @@ -599,30 +599,38 @@ export class SavedObjectsTable extends Component object.type === 'index-pattern'); - if (indexPatterns.length) { - await this.props.indexPatterns.clearCache(); - } - - const objects = await savedObjectsClient.bulkGet(selectedSavedObjects); - const deletes = objects.savedObjects.map((object) => - savedObjectsClient.delete(object.type, object.id, { force: true }) - ); - await Promise.all(deletes); - // Unset this - this.setState({ - selectedSavedObjects: [], - }); + try { + if (indexPatterns.length) { + await this.props.indexPatterns.clearCache(); + } + const objects = await savedObjectsClient.bulkGet(selectedSavedObjects); + const deletes = objects.savedObjects.map((object) => + savedObjectsClient.delete(object.type, object.id, { force: true }) + ); + await Promise.all(deletes); + // Unset this + this.setState({ + selectedSavedObjects: [], + }); + // Fetching all data + await this.fetchSavedObjects(); + await this.fetchCounts(); - // Fetching all data - await this.fetchSavedObjects(); - await this.fetchCounts(); + // Allow the user to interact with the table once the saved objects have been re-fetched. + // If the user fails to delete the saved objects, the delete modal will continue to display. + this.setState({ isShowingDeleteConfirmModal: false }); + } catch (error) { + notifications.toasts.addDanger({ + title: i18n.translate( + 'savedObjectsManagement.objectsTable.unableDeleteSavedObjectsNotificationMessage', + { defaultMessage: 'Unable to delete saved objects' } + ), + text: `${error}`, + }); + } - // Allow the user to interact with the table once the saved objects have been re-fetched. - this.setState({ - isShowingDeleteConfirmModal: false, - isDeleting: false, - }); + this.setState({ isDeleting: false }); }; getRelationships = async (type: string, id: string) => {