Skip to content

Commit 2fed1fd

Browse files
authored
feat(tree-explorer): add delete document context menu item VSCODE-349 (#452)
1 parent 36cebd0 commit 2fed1fd

File tree

10 files changed

+197
-17
lines changed

10 files changed

+197
-17
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ If you use Terraform to manage your infrastructure, MongoDB for VS Code helps yo
6767
- `mdb.show`: Show or hide the MongoDB view.
6868
- `mdb.defaultLimit`: The number of documents to fetch when viewing documents from a collection.
6969
- `mdb.confirmRunAll`: Show a confirmation message before running commands in a playground.
70+
- `mdb.confirmDeleteDocument`: Show a confirmation message before deleting a document in the tree view.
7071
- `mdb.excludeFromPlaygroundsSearch`: Exclude files and folders while searching for playground in the the current workspace.
7172
- `mdb.connectionSaving.hideOptionToChooseWhereToSaveNewConnections`: When a connection is added, a prompt is shown that let's the user decide where the new connection should be saved. When this setting is checked, the prompt is not shown and the default connection saving location setting is used.
7273
- `mdb.connectionSaving.defaultConnectionSavingLocation`: When the setting that hides the option to choose where to save new connections is checked, this setting sets if and where new connections are saved.

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,10 @@
412412
{
413413
"command": "mdb.copyDocumentContentsFromTreeView",
414414
"title": "Copy Document"
415+
},
416+
{
417+
"command": "mdb.deleteDocumentFromTreeView",
418+
"title": "Delete Document..."
415419
}
416420
],
417421
"menus": {
@@ -608,6 +612,11 @@
608612
"command": "mdb.copyDocumentContentsFromTreeView",
609613
"when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem",
610614
"group": "2@1"
615+
},
616+
{
617+
"command": "mdb.deleteDocumentFromTreeView",
618+
"when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem",
619+
"group": "3@1"
611620
}
612621
],
613622
"editor/title": [
@@ -781,6 +790,10 @@
781790
{
782791
"command": "mdb.copyDocumentContentsFromTreeView",
783792
"when": "false"
793+
},
794+
{
795+
"command": "mdb.deleteDocumentFromTreeView",
796+
"when": "false"
784797
}
785798
]
786799
},
@@ -904,6 +917,11 @@
904917
"default": true,
905918
"description": "Show a confirmation message before running commands in a playground."
906919
},
920+
"mdb.confirmDeleteDocument": {
921+
"type": "boolean",
922+
"default": true,
923+
"description": "Show a confirmation message before deleting a document from the tree view."
924+
},
907925
"mdb.sendTelemetry": {
908926
"type": "boolean",
909927
"default": true,

src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ enum EXTENSION_COMMANDS {
6161
MDB_INSERT_OBJECTID_TO_EDITOR = 'mdb.insertObjectIdToEditor',
6262
MDB_GENERATE_OBJECTID_TO_CLIPBOARD = 'mdb.generateObjectIdToClipboard',
6363
MDB_COPY_DOCUMENT_CONTENTS_FROM_TREE_VIEW = 'mdb.copyDocumentContentsFromTreeView',
64+
MDB_DELETE_DOCUMENT_FROM_TREE_VIEW = 'mdb.deleteDocumentFromTreeView',
6465
}
6566

6667
export default EXTENSION_COMMANDS;

src/connectionController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ export default class ConnectionController {
819819
return connectionString;
820820
}
821821

822-
getActiveDataService(): DataService | null {
822+
getActiveDataService() {
823823
return this._activeDataService;
824824
}
825825

src/explorer/documentListTreeItem.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ export default class DocumentListTreeItem
175175
(pastTreeItem as DocumentTreeItem).document,
176176
this.namespace,
177177
index,
178-
this._dataService
178+
this._dataService,
179+
() => this.resetCache()
179180
)
180181
);
181182
});
@@ -223,7 +224,8 @@ export default class DocumentListTreeItem
223224
document,
224225
this.namespace,
225226
index,
226-
this._dataService
227+
this._dataService,
228+
() => this.resetCache()
227229
)
228230
);
229231
});

src/explorer/documentTreeItem.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ export default class DocumentTreeItem
1717
dataService: DataService;
1818
document: Document;
1919
documentId: EJSON.SerializableTypes;
20+
resetDocumentListCache: () => Promise<void>;
2021

2122
constructor(
2223
document: Document,
2324
namespace: string,
2425
documentIndexInTree: number,
25-
dataService: DataService
26+
dataService: DataService,
27+
resetDocumentListCache: () => Promise<void>
2628
) {
2729
// A document can not have a `_id` when it is in a view. In this instance
2830
// we just show the document's index in the tree.
@@ -41,6 +43,7 @@ export default class DocumentTreeItem
4143
this.document = document;
4244
this.documentId = document._id;
4345
this.namespace = namespace;
46+
this.resetDocumentListCache = resetDocumentListCache;
4447

4548
this.tooltip = documentLabel;
4649
}
@@ -71,4 +74,45 @@ export default class DocumentTreeItem
7174
throw new Error(formatError(error).message);
7275
}
7376
}
77+
78+
async onDeleteDocumentClicked(): Promise<boolean> {
79+
const shouldConfirmDeleteDocument = vscode.workspace
80+
.getConfiguration('mdb')
81+
.get('confirmDeleteDocument');
82+
83+
if (shouldConfirmDeleteDocument === true) {
84+
const confirmationResult = await vscode.window.showInformationMessage(
85+
`Are you sure you wish to drop this document "${this.tooltip}"? This confirmation can be disabled in the extension settings.`,
86+
{
87+
modal: true,
88+
},
89+
'Yes'
90+
);
91+
92+
if (confirmationResult !== 'Yes') {
93+
return false;
94+
}
95+
}
96+
97+
try {
98+
const deleteOne = promisify(
99+
this.dataService.deleteOne.bind(this.dataService)
100+
);
101+
const deleteResult = await deleteOne(
102+
this.namespace,
103+
{ _id: this.documentId },
104+
{}
105+
);
106+
107+
if (deleteResult.deletedCount !== 1) {
108+
throw new Error('document not found');
109+
}
110+
111+
await this.resetDocumentListCache();
112+
113+
return true;
114+
} catch (error) {
115+
throw new Error(formatError(error).message);
116+
}
117+
}
74118
}

src/mdbExtensionController.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,25 @@ export default class MDBExtensionController implements vscode.Disposable {
561561
return true;
562562
}
563563
);
564+
this.registerCommand(
565+
EXTENSION_COMMANDS.MDB_DELETE_DOCUMENT_FROM_TREE_VIEW,
566+
async (documentTreeItem: DocumentTreeItem): Promise<boolean> => {
567+
const successfullyDropped =
568+
await documentTreeItem.onDeleteDocumentClicked();
569+
570+
if (successfullyDropped) {
571+
void vscode.window.showInformationMessage(
572+
'Document successfully deleted.'
573+
);
574+
575+
// When we successfully drop a document, we need
576+
// to update the explorer view.
577+
this._explorerController.refresh();
578+
}
579+
580+
return successfullyDropped;
581+
}
582+
);
564583
this.registerCommand(
565584
EXTENSION_COMMANDS.MDB_INSERT_OBJECTID_TO_EDITOR,
566585
async (): Promise<boolean> => {

src/test/suite/explorer/documentTreeItem.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ suite('DocumentTreeItem Test Suite', () => {
1616
mockDocument,
1717
'namespace',
1818
1,
19-
{} as any
19+
{} as any,
20+
() => Promise.resolve()
2021
);
2122

2223
const documentTreeItemLabel = testCollectionTreeItem.label;
@@ -40,7 +41,8 @@ suite('DocumentTreeItem Test Suite', () => {
4041
mockDocument,
4142
'namespace',
4243
1,
43-
mockDataService
44+
mockDataService,
45+
() => Promise.resolve()
4446
);
4547

4648
const documentTreeItemLabel = testCollectionTreeItem.label;
@@ -61,7 +63,8 @@ suite('DocumentTreeItem Test Suite', () => {
6163
mockDocument,
6264
'namespace',
6365
1,
64-
mockDataService
66+
mockDataService,
67+
() => Promise.resolve()
6568
);
6669

6770
const documentTreeItemLabel = testCollectionTreeItem.label;

src/test/suite/extension.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ suite('Extension Test Suite', () => {
5555
'mdb.openMongoDBDocumentFromTree',
5656
'mdb.openMongoDBDocumentFromCodeLens',
5757
'mdb.copyDocumentContentsFromTreeView',
58+
'mdb.deleteDocumentFromTreeView',
5859

5960
// Editor commands.
6061
'mdb.codeLens.showMoreDocumentsClicked',

src/test/suite/mdbExtensionController.test.ts

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,14 @@ suite('MDBExtensionController Test Suite', function () {
2929
this.timeout(10000);
3030

3131
const sandbox: any = sinon.createSandbox();
32-
const fakeShowInformationMessage: any = sinon.fake();
32+
let fakeShowInformationMessage: sinon.SinonStub;
3333

3434
beforeEach(() => {
3535
// Here we stub the showInformationMessage process because it is too much
3636
// for the render process and leads to crashes while testing.
37-
sinon.replace(
37+
fakeShowInformationMessage = sinon.stub(
3838
vscode.window,
39-
'showInformationMessage',
40-
fakeShowInformationMessage
39+
'showInformationMessage'
4140
);
4241
});
4342

@@ -1151,7 +1150,8 @@ suite('MDBExtensionController Test Suite', function () {
11511150
mockDocument,
11521151
'waffle.house',
11531152
0,
1154-
{} as any as DataService
1153+
{} as any as DataService,
1154+
() => Promise.resolve()
11551155
);
11561156

11571157
await vscode.commands.executeCommand(
@@ -1180,9 +1180,9 @@ suite('MDBExtensionController Test Suite', function () {
11801180
const expectedMessage =
11811181
"The document was saved successfully to 'waffle.house'";
11821182

1183-
assert(
1184-
fakeShowInformationMessage.firstArg === expectedMessage,
1185-
`Expected an error message "${expectedMessage}" to be shown when attempting to add a database to a not connected connection found "${fakeShowInformationMessage.firstArg}"`
1183+
assert.strictEqual(
1184+
fakeShowInformationMessage.firstCall.firstArg,
1185+
expectedMessage
11861186
);
11871187
});
11881188

@@ -1198,7 +1198,8 @@ suite('MDBExtensionController Test Suite', function () {
11981198
mockDocument,
11991199
'waffle.house',
12001200
0,
1201-
{} as any as DataService
1201+
{} as any as DataService,
1202+
() => Promise.resolve()
12021203
);
12031204

12041205
const mockFetchDocument: any = sinon.fake.resolves(null);
@@ -1539,7 +1540,8 @@ suite('MDBExtensionController Test Suite', function () {
15391540
mockDocument,
15401541
'waffle.house',
15411542
0,
1542-
mockDataService
1543+
mockDataService,
1544+
() => Promise.resolve()
15431545
);
15441546

15451547
const mockCopyToClipboard: any = sinon.fake();
@@ -1565,6 +1567,95 @@ suite('MDBExtensionController Test Suite', function () {
15651567
assert.strictEqual(namespaceUsed, 'waffle.house');
15661568
});
15671569

1570+
test('mdb.deleteDocumentFromTreeView should not delete a document when the confirmation is cancelled', async () => {
1571+
const mockDocument = {
1572+
_id: 'pancakes',
1573+
time: {
1574+
$time: '12345',
1575+
},
1576+
};
1577+
1578+
let calledDelete = false;
1579+
1580+
const mockDataService: DataService = {
1581+
deleteOne: (
1582+
namespace: string,
1583+
_id: any,
1584+
options: object,
1585+
callback: (error: Error | undefined, documents: object[]) => void
1586+
) => {
1587+
calledDelete = true;
1588+
callback(undefined, [mockDocument]);
1589+
},
1590+
} as any;
1591+
1592+
const documentTreeItem = new DocumentTreeItem(
1593+
mockDocument,
1594+
'waffle.house',
1595+
0,
1596+
mockDataService,
1597+
() => Promise.resolve()
1598+
);
1599+
1600+
const result = await vscode.commands.executeCommand(
1601+
'mdb.deleteDocumentFromTreeView',
1602+
documentTreeItem
1603+
);
1604+
1605+
assert.strictEqual(result, false);
1606+
assert.strictEqual(calledDelete, false);
1607+
});
1608+
1609+
test('mdb.deleteDocumentFromTreeView deletes a document after confirmation', async () => {
1610+
fakeShowInformationMessage.resolves('Yes');
1611+
1612+
const mockDocument = {
1613+
_id: 'pancakes',
1614+
time: {
1615+
$time: '12345',
1616+
},
1617+
};
1618+
1619+
let namespaceUsed = '';
1620+
let _idUsed;
1621+
1622+
const mockDataService: DataService = {
1623+
deleteOne: (
1624+
namespace: string,
1625+
query: any,
1626+
options: object,
1627+
callback: (
1628+
error: Error | undefined,
1629+
result: { deletedCount: number }
1630+
) => void
1631+
) => {
1632+
_idUsed = query;
1633+
namespaceUsed = namespace;
1634+
callback(undefined, {
1635+
deletedCount: 1,
1636+
});
1637+
},
1638+
} as any;
1639+
1640+
const documentTreeItem = new DocumentTreeItem(
1641+
mockDocument,
1642+
'waffle.house',
1643+
0,
1644+
mockDataService,
1645+
() => Promise.resolve()
1646+
);
1647+
1648+
const result = await vscode.commands.executeCommand(
1649+
'mdb.deleteDocumentFromTreeView',
1650+
documentTreeItem
1651+
);
1652+
assert.deepStrictEqual(_idUsed, {
1653+
_id: 'pancakes',
1654+
});
1655+
assert.strictEqual(namespaceUsed, 'waffle.house');
1656+
assert.strictEqual(result, true);
1657+
});
1658+
15681659
suite(
15691660
'when a user hasnt been shown the initial overview page yet and they have no connections saved',
15701661
() => {

0 commit comments

Comments
 (0)