diff --git a/src/Common/ErrorHandlingUtils.ts b/src/Common/ErrorHandlingUtils.ts index 8a86bdf32..3a537683c 100644 --- a/src/Common/ErrorHandlingUtils.ts +++ b/src/Common/ErrorHandlingUtils.ts @@ -51,6 +51,11 @@ const replaceKnownError = (errorMessage: string): string => { return "Database throughput is not supported for internal subscriptions."; } else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) { return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character."; + } else if ( + errorMessage?.indexOf("The user aborted a request") >= 0 || + errorMessage?.indexOf("The operation was aborted") >= 0 + ) { + return "User aborted query."; } return errorMessage; diff --git a/src/Explorer/Tabs/DocumentsTab.html b/src/Explorer/Tabs/DocumentsTab.html index 096db482e..094c18e10 100644 --- a/src/Explorer/Tabs/DocumentsTab.html +++ b/src/Explorer/Tabs/DocumentsTab.html @@ -106,6 +106,18 @@ Apply Filter + + + ; private _resourceTokenPartitionKey: string; private _isQueryCopilotSampleContainer: boolean; + private queryAbortController: AbortController; constructor(options: ViewModels.DocumentsTabOptions) { super(options); @@ -401,6 +402,10 @@ export default class DocumentsTab extends TabsBase { return true; }; + public onAbortQueryClick(): void { + this.queryAbortController.abort(); + } + public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise { if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) { return Q(); @@ -688,6 +693,7 @@ export default class DocumentsTab extends TabsBase { } public createIterator(): QueryIterator { + this.queryAbortController = new AbortController(); const filter: string = this.filterContent().trim(); const query: string = this.buildQuery(filter); let options: any = {}; @@ -696,7 +702,7 @@ export default class DocumentsTab extends TabsBase { if (this._resourceTokenPartitionKey) { options.partitionKey = this._resourceTokenPartitionKey; } - + options.abortSignal = this.queryAbortController.signal; return this._isQueryCopilotSampleContainer ? querySampleDocuments(query, options) : queryDocuments(this.collection.databaseId, this.collection.id(), query, options); diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index e7b9bb07a..98384d92e 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -8,6 +8,7 @@ import React, { Fragment } from "react"; import SplitterLayout from "react-splitter-layout"; import "react-splitter-layout/lib/index.css"; import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; +import CancelQueryIcon from "../../../../images/Entity_cancel.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg"; import { NormalizedEventKey, QueryCopilotSampleDatabaseId } from "../../../Common/Constants"; @@ -91,6 +92,7 @@ export default class QueryTabComponent extends React.Component { + this.queryAbortController = new AbortController(); if (this._iterator === undefined) { this._iterator = this.props.isPreferredApiMongoDB ? queryIterator( @@ -225,7 +228,10 @@ export default class QueryTabComponent extends React.Component this.queryAbortController.abort(), + commandButtonLabel: label, + ariaLabel: label, + hasPopup: false, + }); + } + return buttons; }