Skip to content

Commit 3505a35

Browse files
authored
Add saved object check for delete the workspace (#1858)
Signed-off-by: Owen Wang <[email protected]>
1 parent 3a709fd commit 3505a35

File tree

1 file changed

+317
-4
lines changed

1 file changed

+317
-4
lines changed

cypress/utils/dashboards/workspace-plugin/test-cases/mds_workspace_delete.cases.js

Lines changed: 317 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library';
7+
import { BASE_PATH } from '../../../base_constants';
78

89
export const WorkspaceDeleteTestCases = () => {
910
const miscUtils = new MiscUtils(cy);
@@ -87,7 +88,7 @@ export const WorkspaceDeleteTestCases = () => {
8788
expect(interception.response.statusCode).to.equal(200);
8889
});
8990
cy.location('pathname').should('include', 'app/workspace_list');
90-
cy.contains('Delete workspace successfully').should('be.visible');
91+
cy.contains('workspaces deleted successfully').should('be.visible');
9192
cy.contains(workspace1Name).should('not.exist');
9293
});
9394
});
@@ -109,7 +110,7 @@ export const WorkspaceDeleteTestCases = () => {
109110
expect(interception.response.statusCode).to.equal(200);
110111
});
111112
cy.location('pathname').should('include', 'app/workspace_list');
112-
cy.contains('Delete workspace successfully').should('be.visible');
113+
cy.contains('workspaces deleted successfully').should('be.visible');
113114
cy.contains(workspace1Name).should('not.exist');
114115
});
115116

@@ -131,7 +132,7 @@ export const WorkspaceDeleteTestCases = () => {
131132
expect(interception.response.statusCode).to.equal(200);
132133
});
133134
cy.location('pathname').should('include', 'app/workspace_list');
134-
cy.contains('Delete workspace successfully').should('be.visible');
135+
cy.contains('workspaces deleted successfully').should('be.visible');
135136
cy.contains(workspace1Name).should('not.exist');
136137
cy.contains(workspace2Name).should('not.exist');
137138
});
@@ -176,10 +177,322 @@ export const WorkspaceDeleteTestCases = () => {
176177
cy.wait('@deleteWorkspace1Request').then((interception) => {
177178
expect(interception.response.statusCode).to.equal(200);
178179
});
179-
cy.contains('Delete workspace successfully').should('be.visible');
180+
cy.contains('workspaces deleted successfully').should('be.visible');
180181
cy.location('pathname').should('include', 'app/workspace_list');
181182
cy.contains(workspace1Name).should('not.exist');
182183
});
183184
});
185+
186+
describe('Delete workspace with saved objects cleanup verification', () => {
187+
const testWorkspaceName = 'test_workspace_saved_objects_cleanup';
188+
let testWorkspaceId;
189+
let datasourceId = '';
190+
191+
before(() => {
192+
cy.deleteWorkspaceByName(testWorkspaceName);
193+
194+
if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) {
195+
cy.createDataSourceNoAuth().then((result) => {
196+
datasourceId = result[0];
197+
expect(datasourceId).to.be.a('string').that.is.not.empty;
198+
});
199+
}
200+
});
201+
202+
beforeEach(() => {
203+
cy.createWorkspace({
204+
name: testWorkspaceName,
205+
description: 'Test workspace for saved objects cleanup verification',
206+
features: ['use-case-observability'],
207+
settings: {
208+
permissions: {
209+
library_write: { users: ['%me%'] },
210+
write: { users: ['%me%'] },
211+
},
212+
...(datasourceId ? { dataSources: [datasourceId] } : {}),
213+
},
214+
}).then((value) => {
215+
testWorkspaceId = value;
216+
});
217+
});
218+
219+
afterEach(() => {
220+
cy.deleteWorkspaceByName(testWorkspaceName);
221+
});
222+
223+
after(() => {
224+
if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED') && datasourceId) {
225+
cy.deleteDataSource(datasourceId);
226+
}
227+
});
228+
229+
it('should automatically delete saved objects when workspace is deleted', () => {
230+
// Add sample data to create saved objects in the workspace
231+
cy.loadSampleDataForWorkspace(
232+
'ecommerce',
233+
testWorkspaceId,
234+
datasourceId
235+
);
236+
237+
// Verify saved objects exist in the workspace
238+
cy.visit(`${BASE_PATH}/w/${testWorkspaceId}/app/objects`);
239+
cy.getElementByTestId('savedObjectsTableRowTitle').should('exist');
240+
241+
// Search for ecommerce sample data objects
242+
cy.getElementByTestId('savedObjectSearchBar')
243+
.type('opensearch_dashboards_sample_data_ecommerce{enter}')
244+
.trigger('search');
245+
246+
// Verify we have saved objects
247+
cy.getElementByTestId('savedObjectsTableRowTitle').should(
248+
'have.length.greaterThan',
249+
0
250+
);
251+
252+
// Get the saved object IDs that are associated with this workspace
253+
cy.request({
254+
url: `${BASE_PATH}/api/opensearch-dashboards/management/saved_objects/_find?workspaces=${testWorkspaceId}&page=1&perPage=100&type=index-pattern&type=visualization&type=dashboard&type=search&type=config`,
255+
headers: {
256+
'Osd-Xsrf': 'osd-fetch',
257+
},
258+
}).then((resp) => {
259+
const savedObjectsInWorkspace = resp.body.saved_objects;
260+
expect(savedObjectsInWorkspace).to.have.length.greaterThan(0);
261+
262+
// Store the saved object IDs for verification after deletion
263+
const savedObjectIds = savedObjectsInWorkspace.map((obj) => ({
264+
type: obj.type,
265+
id: obj.id,
266+
}));
267+
268+
// Delete the workspace
269+
cy.visit(`${BASE_PATH}/app/workspace_list`);
270+
cy.contains(testWorkspaceName).should('be.visible');
271+
272+
// Find and click the delete button for this workspace
273+
cy.getElementByTestId(`checkboxSelectRow-${testWorkspaceId}`)
274+
.parents('tr')
275+
.within(() => {
276+
cy.getElementByTestId('euiCollapsedItemActionsButton').click();
277+
});
278+
279+
cy.getElementByTestId('workspace-list-delete-icon').should(
280+
'be.visible'
281+
);
282+
cy.getElementByTestId('workspace-list-delete-icon').click();
283+
284+
// Confirm deletion
285+
cy.contains('Delete workspace').should('be.visible');
286+
cy.getElementByTestId('delete-workspace-modal-input').type('delete');
287+
cy.getElementByTestId('delete-workspace-modal-confirm').click();
288+
289+
// Wait for deletion to complete
290+
cy.contains('workspaces deleted successfully').should('be.visible');
291+
cy.contains(testWorkspaceName).should('not.exist');
292+
293+
// Check each saved object to ensure it no longer exists
294+
savedObjectIds.forEach(({ type, id }) => {
295+
cy.request({
296+
method: 'GET',
297+
url: `${BASE_PATH}/api/saved_objects/${type}/${id}`,
298+
failOnStatusCode: false,
299+
headers: {
300+
'Osd-Xsrf': 'osd-fetch',
301+
},
302+
}).then((response) => {
303+
// The saved object should be deleted (404) or not accessible
304+
expect(response.status).to.be.oneOf([404, 403]);
305+
});
306+
});
307+
308+
// Verify saved objects are not visible in global saved objects management
309+
cy.visit(`${BASE_PATH}/app/objects`);
310+
311+
// Search for the ecommerce sample data that was in the deleted workspace
312+
cy.getElementByTestId('savedObjectSearchBar')
313+
.clear()
314+
.type('opensearch_dashboards_sample_data_ecommerce{enter}')
315+
.trigger('search');
316+
317+
// Should not find any saved objects from the deleted workspace
318+
// (or significantly fewer if some were shared with other workspaces)
319+
cy.get('body').then(($body) => {
320+
if (
321+
$body.find('[data-test-subj="savedObjectsTableRowTitle"]')
322+
.length > 0
323+
) {
324+
// If there are still some objects, they should not belong to the deleted workspace
325+
cy.getElementByTestId('savedObjectsTableRowTitle').each(($el) => {
326+
// Check that none of these objects have the deleted workspace ID
327+
cy.wrap($el).should('not.contain', testWorkspaceId);
328+
});
329+
} else {
330+
// No objects found, which is expected
331+
cy.get('[data-test-subj="savedObjectsTableRowTitle"]').should(
332+
'not.exist'
333+
);
334+
}
335+
});
336+
});
337+
});
338+
339+
it('should delete workspace with multiple types of saved objects', () => {
340+
// Create an index pattern
341+
cy.request({
342+
method: 'POST',
343+
url: `${BASE_PATH}/w/${testWorkspaceId}/api/saved_objects/index-pattern`,
344+
headers: {
345+
'content-type': 'application/json;charset=UTF-8',
346+
'osd-xsrf': true,
347+
},
348+
body: JSON.stringify({
349+
attributes: {
350+
title: 'test-index-pattern-*',
351+
timeFieldName: '@timestamp',
352+
},
353+
references: [],
354+
}),
355+
}).then((indexPatternResp) => {
356+
const indexPatternId = indexPatternResp.body.id;
357+
358+
// Create a visualization
359+
cy.request({
360+
method: 'POST',
361+
url: `${BASE_PATH}/w/${testWorkspaceId}/api/saved_objects/visualization`,
362+
headers: {
363+
'content-type': 'application/json;charset=UTF-8',
364+
'osd-xsrf': true,
365+
},
366+
body: JSON.stringify({
367+
attributes: {
368+
title: 'Test Visualization',
369+
visState: JSON.stringify({
370+
title: 'Test Visualization',
371+
type: 'histogram',
372+
params: {},
373+
aggs: [],
374+
}),
375+
uiStateJSON: '{}',
376+
description: '',
377+
version: 1,
378+
kibanaSavedObjectMeta: {
379+
searchSourceJSON: JSON.stringify({
380+
index: indexPatternId,
381+
query: { match_all: {} },
382+
}),
383+
},
384+
},
385+
references: [
386+
{
387+
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
388+
type: 'index-pattern',
389+
id: indexPatternId,
390+
},
391+
],
392+
}),
393+
}).then((visualizationResp) => {
394+
const visualizationId = visualizationResp.body.id;
395+
396+
// Create a dashboard
397+
cy.request({
398+
method: 'POST',
399+
url: `${BASE_PATH}/w/${testWorkspaceId}/api/saved_objects/dashboard`,
400+
headers: {
401+
'content-type': 'application/json;charset=UTF-8',
402+
'osd-xsrf': true,
403+
},
404+
body: JSON.stringify({
405+
attributes: {
406+
title: 'Test Dashboard',
407+
hits: 0,
408+
description: 'Test dashboard for workspace deletion',
409+
panelsJSON: JSON.stringify([
410+
{
411+
version: '7.9.0',
412+
gridData: { x: 0, y: 0, w: 24, h: 15, i: '1' },
413+
panelIndex: '1',
414+
embeddableConfig: {},
415+
panelRefName: 'panel_1',
416+
},
417+
]),
418+
optionsJSON: JSON.stringify({
419+
useMargins: true,
420+
syncColors: false,
421+
hidePanelTitles: false,
422+
}),
423+
version: 1,
424+
timeRestore: false,
425+
kibanaSavedObjectMeta: {
426+
searchSourceJSON: JSON.stringify({
427+
query: { query: '', language: 'kuery' },
428+
filter: [],
429+
}),
430+
},
431+
},
432+
references: [
433+
{
434+
name: 'panel_1',
435+
type: 'visualization',
436+
id: visualizationId,
437+
},
438+
],
439+
}),
440+
}).then((dashboardResp) => {
441+
const dashboardId = dashboardResp.body.id;
442+
443+
// Verify all saved objects exist in the workspace
444+
cy.visit(`${BASE_PATH}/w/${testWorkspaceId}/app/objects`);
445+
cy.getElementByTestId('savedObjectsTableRowTitle').should(
446+
'have.length.greaterThan',
447+
2
448+
);
449+
450+
// Delete the workspace
451+
cy.visit(`${BASE_PATH}/app/workspace_list`);
452+
cy.contains(testWorkspaceName).should('be.visible');
453+
454+
cy.getElementByTestId(`checkboxSelectRow-${testWorkspaceId}`)
455+
.parents('tr')
456+
.within(() => {
457+
cy.getElementByTestId(
458+
'euiCollapsedItemActionsButton'
459+
).click();
460+
});
461+
462+
cy.getElementByTestId('workspace-list-delete-icon').click();
463+
cy.contains('Delete workspace').should('be.visible');
464+
cy.getElementByTestId('delete-workspace-modal-input').type(
465+
'delete'
466+
);
467+
cy.getElementByTestId('delete-workspace-modal-confirm').click();
468+
469+
cy.contains('workspaces deleted successfully').should(
470+
'be.visible'
471+
);
472+
473+
// Verify all saved objects are deleted
474+
const savedObjectsToCheck = [
475+
{ type: 'index-pattern', id: indexPatternId },
476+
{ type: 'visualization', id: visualizationId },
477+
{ type: 'dashboard', id: dashboardId },
478+
];
479+
480+
savedObjectsToCheck.forEach(({ type, id }) => {
481+
cy.request({
482+
method: 'GET',
483+
url: `${BASE_PATH}/api/saved_objects/${type}/${id}`,
484+
failOnStatusCode: false,
485+
headers: {
486+
'Osd-Xsrf': 'osd-fetch',
487+
},
488+
}).then((response) => {
489+
expect(response.status).to.be.oneOf([404, 403]);
490+
});
491+
});
492+
});
493+
});
494+
});
495+
});
496+
});
184497
}
185498
};

0 commit comments

Comments
 (0)