Skip to content
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

feat(tree-explorer): add delete document context menu item VSCODE-349 #452

Merged
merged 2 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ If you use Terraform to manage your infrastructure, MongoDB for VS Code helps yo
- `mdb.show`: Show or hide the MongoDB view.
- `mdb.defaultLimit`: The number of documents to fetch when viewing documents from a collection.
- `mdb.confirmRunAll`: Show a confirmation message before running commands in a playground.
- `mdb.confirmDeleteDocument`: Show a confirmation message before deleting a document in the tree view.
- `mdb.excludeFromPlaygroundsSearch`: Exclude files and folders while searching for playground in the the current workspace.
- `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.
- `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.
Expand Down
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@
{
"command": "mdb.copyDocumentContentsFromTreeView",
"title": "Copy Document"
},
{
"command": "mdb.deleteDocumentFromTreeView",
"title": "Delete Document..."
}
],
"menus": {
Expand Down Expand Up @@ -608,6 +612,11 @@
"command": "mdb.copyDocumentContentsFromTreeView",
"when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem",
"group": "2@1"
},
{
"command": "mdb.deleteDocumentFromTreeView",
"when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem",
"group": "3@1"
}
],
"editor/title": [
Expand Down Expand Up @@ -781,6 +790,10 @@
{
"command": "mdb.copyDocumentContentsFromTreeView",
"when": "false"
},
{
"command": "mdb.deleteDocumentFromTreeView",
"when": "false"
}
]
},
Expand Down Expand Up @@ -904,6 +917,11 @@
"default": true,
"description": "Show a confirmation message before running commands in a playground."
},
"mdb.confirmDeleteDocument": {
"type": "boolean",
"default": true,
"description": "Show a confirmation message before deleting a document from the tree view."
},
"mdb.sendTelemetry": {
"type": "boolean",
"default": true,
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ enum EXTENSION_COMMANDS {
MDB_INSERT_OBJECTID_TO_EDITOR = 'mdb.insertObjectIdToEditor',
MDB_GENERATE_OBJECTID_TO_CLIPBOARD = 'mdb.generateObjectIdToClipboard',
MDB_COPY_DOCUMENT_CONTENTS_FROM_TREE_VIEW = 'mdb.copyDocumentContentsFromTreeView',
MDB_DELETE_DOCUMENT_FROM_TREE_VIEW = 'mdb.deleteDocumentFromTreeView',
}

export default EXTENSION_COMMANDS;
2 changes: 1 addition & 1 deletion src/connectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ export default class ConnectionController {
return connectionString;
}

getActiveDataService(): DataService | null {
getActiveDataService() {
return this._activeDataService;
}

Expand Down
6 changes: 4 additions & 2 deletions src/explorer/documentListTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ export default class DocumentListTreeItem
(pastTreeItem as DocumentTreeItem).document,
this.namespace,
index,
this._dataService
this._dataService,
() => this.resetCache()
)
);
});
Expand Down Expand Up @@ -223,7 +224,8 @@ export default class DocumentListTreeItem
document,
this.namespace,
index,
this._dataService
this._dataService,
() => this.resetCache()
)
);
});
Expand Down
46 changes: 45 additions & 1 deletion src/explorer/documentTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ export default class DocumentTreeItem
dataService: DataService;
document: Document;
documentId: EJSON.SerializableTypes;
resetDocumentListCache: () => Promise<void>;

constructor(
document: Document,
namespace: string,
documentIndexInTree: number,
dataService: DataService
dataService: DataService,
resetDocumentListCache: () => Promise<void>
) {
// A document can not have a `_id` when it is in a view. In this instance
// we just show the document's index in the tree.
Expand All @@ -41,6 +43,7 @@ export default class DocumentTreeItem
this.document = document;
this.documentId = document._id;
this.namespace = namespace;
this.resetDocumentListCache = resetDocumentListCache;

this.tooltip = documentLabel;
}
Expand Down Expand Up @@ -71,4 +74,45 @@ export default class DocumentTreeItem
throw new Error(formatError(error).message);
}
}

async onDeleteDocumentClicked(): Promise<boolean> {
const shouldConfirmDeleteDocument = vscode.workspace
.getConfiguration('mdb')
.get('confirmDeleteDocument');

if (shouldConfirmDeleteDocument === true) {
const confirmationResult = await vscode.window.showInformationMessage(
`Are you sure you wish to drop this document "${this.tooltip}"? This confirmation can be disabled in the extension settings.`,
{
modal: true,
},
'Yes'
);

if (confirmationResult !== 'Yes') {
return false;
}
}

try {
const deleteOne = promisify(
this.dataService.deleteOne.bind(this.dataService)
);
const deleteResult = await deleteOne(
this.namespace,
{ _id: this.documentId },
{}
);

if (deleteResult.deletedCount !== 1) {
throw new Error('document not found');
}

await this.resetDocumentListCache();

return true;
} catch (error) {
throw new Error(formatError(error).message);
}
}
}
19 changes: 19 additions & 0 deletions src/mdbExtensionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,25 @@ export default class MDBExtensionController implements vscode.Disposable {
return true;
}
);
this.registerCommand(
EXTENSION_COMMANDS.MDB_DELETE_DOCUMENT_FROM_TREE_VIEW,
async (documentTreeItem: DocumentTreeItem): Promise<boolean> => {
const successfullyDropped =
await documentTreeItem.onDeleteDocumentClicked();

if (successfullyDropped) {
void vscode.window.showInformationMessage(
'Document successfully deleted.'
);

// When we successfully drop a document, we need
// to update the explorer view.
this._explorerController.refresh();
}

return successfullyDropped;
}
);
this.registerCommand(
EXTENSION_COMMANDS.MDB_INSERT_OBJECTID_TO_EDITOR,
async (): Promise<boolean> => {
Expand Down
9 changes: 6 additions & 3 deletions src/test/suite/explorer/documentTreeItem.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ suite('DocumentTreeItem Test Suite', () => {
mockDocument,
'namespace',
1,
{} as any
{} as any,
() => Promise.resolve()
);

const documentTreeItemLabel = testCollectionTreeItem.label;
Expand All @@ -40,7 +41,8 @@ suite('DocumentTreeItem Test Suite', () => {
mockDocument,
'namespace',
1,
mockDataService
mockDataService,
() => Promise.resolve()
);

const documentTreeItemLabel = testCollectionTreeItem.label;
Expand All @@ -61,7 +63,8 @@ suite('DocumentTreeItem Test Suite', () => {
mockDocument,
'namespace',
1,
mockDataService
mockDataService,
() => Promise.resolve()
);

const documentTreeItemLabel = testCollectionTreeItem.label;
Expand Down
1 change: 1 addition & 0 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ suite('Extension Test Suite', () => {
'mdb.openMongoDBDocumentFromTree',
'mdb.openMongoDBDocumentFromCodeLens',
'mdb.copyDocumentContentsFromTreeView',
'mdb.deleteDocumentFromTreeView',

// Editor commands.
'mdb.codeLens.showMoreDocumentsClicked',
Expand Down
111 changes: 101 additions & 10 deletions src/test/suite/mdbExtensionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ suite('MDBExtensionController Test Suite', function () {
this.timeout(10000);

const sandbox: any = sinon.createSandbox();
const fakeShowInformationMessage: any = sinon.fake();
let fakeShowInformationMessage: sinon.SinonStub;

beforeEach(() => {
// Here we stub the showInformationMessage process because it is too much
// for the render process and leads to crashes while testing.
sinon.replace(
fakeShowInformationMessage = sinon.stub(
vscode.window,
'showInformationMessage',
fakeShowInformationMessage
'showInformationMessage'
);
});

Expand Down Expand Up @@ -1151,7 +1150,8 @@ suite('MDBExtensionController Test Suite', function () {
mockDocument,
'waffle.house',
0,
{} as any as DataService
{} as any as DataService,
() => Promise.resolve()
);

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

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

Expand All @@ -1198,7 +1198,8 @@ suite('MDBExtensionController Test Suite', function () {
mockDocument,
'waffle.house',
0,
{} as any as DataService
{} as any as DataService,
() => Promise.resolve()
);

const mockFetchDocument: any = sinon.fake.resolves(null);
Expand Down Expand Up @@ -1539,7 +1540,8 @@ suite('MDBExtensionController Test Suite', function () {
mockDocument,
'waffle.house',
0,
mockDataService
mockDataService,
() => Promise.resolve()
);

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

test('mdb.deleteDocumentFromTreeView should not delete a document when the confirmation is cancelled', async () => {
const mockDocument = {
_id: 'pancakes',
time: {
$time: '12345',
},
};

let calledDelete = false;

const mockDataService: DataService = {
deleteOne: (
namespace: string,
_id: any,
options: object,
callback: (error: Error | undefined, documents: object[]) => void
) => {
calledDelete = true;
callback(undefined, [mockDocument]);
},
} as any;

const documentTreeItem = new DocumentTreeItem(
mockDocument,
'waffle.house',
0,
mockDataService,
() => Promise.resolve()
);

const result = await vscode.commands.executeCommand(
'mdb.deleteDocumentFromTreeView',
documentTreeItem
);

assert.strictEqual(result, false);
assert.strictEqual(calledDelete, false);
});

test('mdb.deleteDocumentFromTreeView deletes a document after confirmation', async () => {
fakeShowInformationMessage.resolves('Yes');

const mockDocument = {
_id: 'pancakes',
time: {
$time: '12345',
},
};

let namespaceUsed = '';
let _idUsed;

const mockDataService: DataService = {
deleteOne: (
namespace: string,
query: any,
options: object,
callback: (
error: Error | undefined,
result: { deletedCount: number }
) => void
) => {
_idUsed = query;
namespaceUsed = namespace;
callback(undefined, {
deletedCount: 1,
});
},
} as any;

const documentTreeItem = new DocumentTreeItem(
mockDocument,
'waffle.house',
0,
mockDataService,
() => Promise.resolve()
);

const result = await vscode.commands.executeCommand(
'mdb.deleteDocumentFromTreeView',
documentTreeItem
);
assert.deepStrictEqual(_idUsed, {
_id: 'pancakes',
});
assert.strictEqual(namespaceUsed, 'waffle.house');
assert.strictEqual(result, true);
});

suite(
'when a user hasnt been shown the initial overview page yet and they have no connections saved',
() => {
Expand Down