mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 17:06:58 +00:00
Implement bulk delete for nosql
This commit is contained in:
parent
0887998461
commit
299672435e
@ -1,9 +1,9 @@
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
export const getEntityName = (): string => {
|
export const getEntityName = (multiple?: boolean): string => {
|
||||||
if (userContext.apiType === "Mongo") {
|
if (userContext.apiType === "Mongo") {
|
||||||
return "document";
|
return multiple ? "documents" : "document";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "item";
|
return multiple ? "items" : "item";
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { BulkOperationType, OperationInput, PartitionKey } from "@azure/cosmos";
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
@ -24,3 +25,42 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
|||||||
clearMessage();
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
|||||||
import MongoUtility from "Common/MongoUtility";
|
import MongoUtility from "Common/MongoUtility";
|
||||||
import { StyleConstants } from "Common/StyleConstants";
|
import { StyleConstants } from "Common/StyleConstants";
|
||||||
import { createDocument } from "Common/dataAccess/createDocument";
|
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 { queryDocuments } from "Common/dataAccess/queryDocuments";
|
||||||
import { readDocument } from "Common/dataAccess/readDocument";
|
import { readDocument } from "Common/dataAccess/readDocument";
|
||||||
import { updateDocument } from "Common/dataAccess/updateDocument";
|
import { updateDocument } from "Common/dataAccess/updateDocument";
|
||||||
@ -734,37 +734,41 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
// setEditorState,
|
// setEditorState,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let __deleteDocument = useCallback(
|
/**
|
||||||
(documentId: DocumentId): Promise<void> => deleteDocument(_collection, documentId),
|
* Implementation using bulk delete
|
||||||
[_collection],
|
*/
|
||||||
);
|
let _deleteDocuments = useCallback(
|
||||||
|
async (toDeleteDocumentIds: DocumentId[]): Promise<string[]> => {
|
||||||
const _deleteDocuments = useCallback(
|
|
||||||
(documentId: DocumentId): Promise<DocumentId> => {
|
|
||||||
onExecutionErrorChange(false);
|
onExecutionErrorChange(false);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocuments, {
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle,
|
tabTitle,
|
||||||
});
|
});
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
return __deleteDocument(documentId)
|
return deleteNoSqlDocuments(
|
||||||
|
_collection,
|
||||||
|
toDeleteDocumentIds.map((id) => ({
|
||||||
|
id: id.id(),
|
||||||
|
partitionKey: id.partitionKeyValue,
|
||||||
|
})),
|
||||||
|
)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
(deletedIds) => {
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.DeleteDocument,
|
Action.DeleteDocuments,
|
||||||
{
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle,
|
tabTitle,
|
||||||
},
|
},
|
||||||
startKey,
|
startKey,
|
||||||
);
|
);
|
||||||
return documentId;
|
return deletedIds;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
onExecutionErrorChange(true);
|
onExecutionErrorChange(true);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.DeleteDocument,
|
Action.DeleteDocuments,
|
||||||
{
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle,
|
tabTitle,
|
||||||
@ -778,21 +782,20 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
)
|
)
|
||||||
.finally(() => setIsExecuting(false));
|
.finally(() => setIsExecuting(false));
|
||||||
},
|
},
|
||||||
[__deleteDocument, onExecutionErrorChange, tabTitle],
|
[_collection, onExecutionErrorChange, tabTitle],
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteDocuments = useCallback(
|
const deleteDocuments = useCallback(
|
||||||
(toDeleteDocumentIds: DocumentId[]): void => {
|
(toDeleteDocumentIds: DocumentId[]): void => {
|
||||||
onExecutionErrorChange(false);
|
onExecutionErrorChange(false);
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
const promises = toDeleteDocumentIds.map((documentId) => _deleteDocuments(documentId));
|
_deleteDocuments(toDeleteDocumentIds)
|
||||||
Promise.all(promises)
|
.then((deletedIds: string[]) => {
|
||||||
.then((deletedDocumentIds: DocumentId[]) => {
|
|
||||||
const newDocumentIds = [...documentIds];
|
const newDocumentIds = [...documentIds];
|
||||||
deletedDocumentIds.forEach((deletedDocumentId) => {
|
deletedIds.forEach((deletedId) => {
|
||||||
if (deletedDocumentId !== undefined) {
|
if (deletedId !== undefined) {
|
||||||
// documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
|
// 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) {
|
if (index !== -1) {
|
||||||
newDocumentIds.splice(index, 1);
|
newDocumentIds.splice(index, 1);
|
||||||
}
|
}
|
||||||
@ -1335,9 +1338,60 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
return partitionKeyProperty;
|
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);
|
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> => {
|
onSaveNewDocumentClick = useCallback((): Promise<unknown> => {
|
||||||
const documentContent = JSON.parse(selectedDocumentContent);
|
const documentContent = JSON.parse(selectedDocumentContent);
|
||||||
// this.displayedError("");
|
// this.displayedError("");
|
||||||
@ -1615,12 +1669,12 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
<div
|
<div
|
||||||
className="tab-pane active"
|
className="tab-pane active"
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
setTemplateReady: true,
|
setTemplateReady: true,
|
||||||
attr:{
|
attr:{
|
||||||
id: tabId
|
id: tabId
|
||||||
},
|
},
|
||||||
visible: isActive"
|
visible: isActive"
|
||||||
*/
|
*/
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
style={{ display: "flex" }}
|
style={{ display: "flex" }}
|
||||||
>
|
>
|
||||||
@ -1709,9 +1763,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
onClick={() => refreshDocumentsGrid(true)}
|
onClick={() => refreshDocumentsGrid(true)}
|
||||||
disabled={!applyFilterButton.enabled}
|
disabled={!applyFilterButton.enabled}
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
click: refreshDocumentsGrid.bind($data, true),
|
click: refreshDocumentsGrid.bind($data, true),
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
*/
|
*/
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
@ -1723,9 +1777,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
<button
|
<button
|
||||||
className="filterbtnstyle queryButton"
|
className="filterbtnstyle queryButton"
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
visible: !isPreferredApiMongoDB && isExecuting,
|
visible: !isPreferredApiMongoDB && isExecuting,
|
||||||
click: onAbortQueryClick"
|
click: onAbortQueryClick"
|
||||||
*/
|
*/
|
||||||
aria-label="Cancel Query"
|
aria-label="Cancel Query"
|
||||||
onClick={() => queryAbortController.abort()}
|
onClick={() => queryAbortController.abort()}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -138,6 +138,7 @@ export enum Action {
|
|||||||
QueryGenerationFromCopilotPrompt,
|
QueryGenerationFromCopilotPrompt,
|
||||||
QueryEdited,
|
QueryEdited,
|
||||||
ExecuteQueryGeneratedFromQueryCopilot,
|
ExecuteQueryGeneratedFromQueryCopilot,
|
||||||
|
DeleteDocuments,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
Loading…
Reference in New Issue
Block a user