Implement bulk delete for nosql

This commit is contained in:
Laurent Nguyen 2024-04-29 18:26:46 +02:00
parent 0887998461
commit 299672435e
4 changed files with 132 additions and 37 deletions

View File

@ -1,9 +1,9 @@
import { userContext } from "../UserContext";
export const getEntityName = (): string => {
export const getEntityName = (multiple?: boolean): string => {
if (userContext.apiType === "Mongo") {
return "document";
return multiple ? "documents" : "document";
}
return "item";
return multiple ? "items" : "item";
};

View File

@ -1,3 +1,4 @@
import { BulkOperationType, OperationInput, PartitionKey } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@ -24,3 +25,42 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
clearMessage();
}
};
/**
* Bulk delete documents
* @param collection
* @param documentId
* @returns array of ids that were successfully deleted
*/
export const deleteDocuments = async (
collection: CollectionBase,
documentIds: {
id: string;
partitionKey?: PartitionKey;
}[],
): Promise<string[]> => {
const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`);
try {
const v2Container = await client().database(collection.databaseId).container(collection.id());
const operations: OperationInput[] = documentIds.map((documentId) => ({
...documentId,
operationType: BulkOperationType.Delete,
}));
const bulkResult = await v2Container.items.bulk(operations);
const result: string[] = [];
documentIds.forEach((documentId, index) => {
if (bulkResult[index].statusCode === 204) {
result.push(documentId.id);
}
});
logConsoleInfo(`Successfully deleted ${getEntityName(true)}: ${result.length} out of ${documentIds.length}`);
// TODO: handle case result.length != documentIds.length
return result;
} catch (error) {
handleError(error, "DeleteDocuments", `Error while deleting ${documentIds.length} ${getEntityName(true)}`);
throw error;
} finally {
clearMessage();
}
};

View File

@ -7,7 +7,7 @@ import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import MongoUtility from "Common/MongoUtility";
import { StyleConstants } from "Common/StyleConstants";
import { createDocument } from "Common/dataAccess/createDocument";
import { deleteDocument } from "Common/dataAccess/deleteDocument";
import { deleteDocuments as deleteNoSqlDocuments } from "Common/dataAccess/deleteDocument";
import { queryDocuments } from "Common/dataAccess/queryDocuments";
import { readDocument } from "Common/dataAccess/readDocument";
import { updateDocument } from "Common/dataAccess/updateDocument";
@ -734,37 +734,41 @@ const DocumentsTabComponent: React.FunctionComponent<{
// setEditorState,
]);
let __deleteDocument = useCallback(
(documentId: DocumentId): Promise<void> => deleteDocument(_collection, documentId),
[_collection],
);
const _deleteDocuments = useCallback(
(documentId: DocumentId): Promise<DocumentId> => {
/**
* Implementation using bulk delete
*/
let _deleteDocuments = useCallback(
async (toDeleteDocumentIds: DocumentId[]): Promise<string[]> => {
onExecutionErrorChange(false);
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocuments, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
});
setIsExecuting(true);
return __deleteDocument(documentId)
return deleteNoSqlDocuments(
_collection,
toDeleteDocumentIds.map((id) => ({
id: id.id(),
partitionKey: id.partitionKeyValue,
})),
)
.then(
() => {
(deletedIds) => {
TelemetryProcessor.traceSuccess(
Action.DeleteDocument,
Action.DeleteDocuments,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
},
startKey,
);
return documentId;
return deletedIds;
},
(error) => {
onExecutionErrorChange(true);
console.error(error);
TelemetryProcessor.traceFailure(
Action.DeleteDocument,
Action.DeleteDocuments,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
@ -778,21 +782,20 @@ const DocumentsTabComponent: React.FunctionComponent<{
)
.finally(() => setIsExecuting(false));
},
[__deleteDocument, onExecutionErrorChange, tabTitle],
[_collection, onExecutionErrorChange, tabTitle],
);
const deleteDocuments = useCallback(
(toDeleteDocumentIds: DocumentId[]): void => {
onExecutionErrorChange(false);
setIsExecuting(true);
const promises = toDeleteDocumentIds.map((documentId) => _deleteDocuments(documentId));
Promise.all(promises)
.then((deletedDocumentIds: DocumentId[]) => {
_deleteDocuments(toDeleteDocumentIds)
.then((deletedIds: string[]) => {
const newDocumentIds = [...documentIds];
deletedDocumentIds.forEach((deletedDocumentId) => {
if (deletedDocumentId !== undefined) {
deletedIds.forEach((deletedId) => {
if (deletedId !== undefined) {
// documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
const index = toDeleteDocumentIds.findIndex((documentId) => documentId.rid === deletedDocumentId.rid);
const index = toDeleteDocumentIds.findIndex((documentId) => documentId.rid === deletedId);
if (index !== -1) {
newDocumentIds.splice(index, 1);
}
@ -1335,9 +1338,60 @@ const DocumentsTabComponent: React.FunctionComponent<{
return partitionKeyProperty;
});
__deleteDocument = (documentId: DocumentId): Promise<void> =>
/**
* Mongo implementation
* TODO: update proxy to use mongo driver deleteMany
*/
_deleteDocuments = (toDeleteDocumentIds: DocumentId[]): Promise<string[]> => {
const promises = toDeleteDocumentIds.map((documentId) => _deleteDocument(documentId));
return Promise.all(promises);
};
const __deleteDocument = (documentId: DocumentId): Promise<void> =>
MongoProxyClient.deleteDocument(_collection.databaseId, _collection as ViewModels.Collection, documentId);
const _deleteDocument = useCallback(
(documentId: DocumentId): Promise<string> => {
onExecutionErrorChange(false);
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
});
setIsExecuting(true);
return __deleteDocument(documentId)
.then(
() => {
TelemetryProcessor.traceSuccess(
Action.DeleteDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
},
startKey,
);
return documentId.rid;
},
(error) => {
onExecutionErrorChange(true);
console.error(error);
TelemetryProcessor.traceFailure(
Action.DeleteDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey,
);
return undefined;
},
)
.finally(() => setIsExecuting(false));
},
[__deleteDocument, onExecutionErrorChange, tabTitle],
);
onSaveNewDocumentClick = useCallback((): Promise<unknown> => {
const documentContent = JSON.parse(selectedDocumentContent);
// this.displayedError("");
@ -1615,12 +1669,12 @@ const DocumentsTabComponent: React.FunctionComponent<{
<div
className="tab-pane active"
/* data-bind="
setTemplateReady: true,
attr:{
id: tabId
},
visible: isActive"
*/
setTemplateReady: true,
attr:{
id: tabId
},
visible: isActive"
*/
role="tabpanel"
style={{ display: "flex" }}
>
@ -1709,9 +1763,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
onClick={() => refreshDocumentsGrid(true)}
disabled={!applyFilterButton.enabled}
/* data-bind="
click: refreshDocumentsGrid.bind($data, true),
enable: applyFilterButton.enabled"
*/
click: refreshDocumentsGrid.bind($data, true),
enable: applyFilterButton.enabled"
*/
aria-label="Apply filter"
tabIndex={0}
>
@ -1723,9 +1777,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
<button
className="filterbtnstyle queryButton"
/* data-bind="
visible: !isPreferredApiMongoDB && isExecuting,
click: onAbortQueryClick"
*/
visible: !isPreferredApiMongoDB && isExecuting,
click: onAbortQueryClick"
*/
aria-label="Cancel Query"
onClick={() => queryAbortController.abort()}
tabIndex={0}

View File

@ -138,6 +138,7 @@ export enum Action {
QueryGenerationFromCopilotPrompt,
QueryEdited,
ExecuteQueryGeneratedFromQueryCopilot,
DeleteDocuments,
}
export const ActionModifiers = {