Cancel Query Option (#1646)

* cancel query option
---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
This commit is contained in:
Asier Isayas 2023-10-05 10:37:59 -04:00 committed by GitHub
parent ca861a0d77
commit 3754d2c32c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 2 deletions

View File

@ -51,6 +51,11 @@ const replaceKnownError = (errorMessage: string): string => {
return "Database throughput is not supported for internal subscriptions."; return "Database throughput is not supported for internal subscriptions.";
} else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) { } 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."; 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; return errorMessage;

View File

@ -106,6 +106,18 @@
Apply Filter Apply Filter
</button> </button>
</span> </span>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
visible: !isPreferredApiMongoDB && isExecuting,
click: onAbortQueryClick"
aria-label="Cancel Query"
tabindex="0"
>
Cancel Query
</button>
</span>
<span <span
class="filterclose" class="filterclose"
role="button" role="button"

View File

@ -78,6 +78,7 @@ export default class DocumentsTab extends TabsBase {
private _documentsIterator: QueryIterator<ItemDefinition & Resource>; private _documentsIterator: QueryIterator<ItemDefinition & Resource>;
private _resourceTokenPartitionKey: string; private _resourceTokenPartitionKey: string;
private _isQueryCopilotSampleContainer: boolean; private _isQueryCopilotSampleContainer: boolean;
private queryAbortController: AbortController;
constructor(options: ViewModels.DocumentsTabOptions) { constructor(options: ViewModels.DocumentsTabOptions) {
super(options); super(options);
@ -401,6 +402,10 @@ export default class DocumentsTab extends TabsBase {
return true; return true;
}; };
public onAbortQueryClick(): void {
this.queryAbortController.abort();
}
public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise<any> { public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise<any> {
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) { if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
return Q(); return Q();
@ -688,6 +693,7 @@ export default class DocumentsTab extends TabsBase {
} }
public createIterator(): QueryIterator<ItemDefinition & Resource> { public createIterator(): QueryIterator<ItemDefinition & Resource> {
this.queryAbortController = new AbortController();
const filter: string = this.filterContent().trim(); const filter: string = this.filterContent().trim();
const query: string = this.buildQuery(filter); const query: string = this.buildQuery(filter);
let options: any = {}; let options: any = {};
@ -696,7 +702,7 @@ export default class DocumentsTab extends TabsBase {
if (this._resourceTokenPartitionKey) { if (this._resourceTokenPartitionKey) {
options.partitionKey = this._resourceTokenPartitionKey; options.partitionKey = this._resourceTokenPartitionKey;
} }
options.abortSignal = this.queryAbortController.signal;
return this._isQueryCopilotSampleContainer return this._isQueryCopilotSampleContainer
? querySampleDocuments(query, options) ? querySampleDocuments(query, options)
: queryDocuments(this.collection.databaseId, this.collection.id(), query, options); : queryDocuments(this.collection.databaseId, this.collection.id(), query, options);

View File

@ -8,6 +8,7 @@ import React, { Fragment } from "react";
import SplitterLayout from "react-splitter-layout"; import SplitterLayout from "react-splitter-layout";
import "react-splitter-layout/lib/index.css"; import "react-splitter-layout/lib/index.css";
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
import SaveQueryIcon from "../../../../images/save-cosmos.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg";
import { NormalizedEventKey, QueryCopilotSampleDatabaseId } from "../../../Common/Constants"; import { NormalizedEventKey, QueryCopilotSampleDatabaseId } from "../../../Common/Constants";
@ -91,6 +92,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
public isCloseClicked: boolean; public isCloseClicked: boolean;
public isCopilotTabActive: boolean; public isCopilotTabActive: boolean;
private _iterator: MinimalQueryIterator; private _iterator: MinimalQueryIterator;
private queryAbortController: AbortController;
constructor(props: IQueryTabComponentProps) { constructor(props: IQueryTabComponentProps) {
super(props); super(props);
@ -214,6 +216,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
} }
private async _executeQueryDocumentsPage(firstItemIndex: number): Promise<void> { private async _executeQueryDocumentsPage(firstItemIndex: number): Promise<void> {
this.queryAbortController = new AbortController();
if (this._iterator === undefined) { if (this._iterator === undefined) {
this._iterator = this.props.isPreferredApiMongoDB this._iterator = this.props.isPreferredApiMongoDB
? queryIterator( ? queryIterator(
@ -225,7 +228,10 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
this.props.collection.databaseId, this.props.collection.databaseId,
this.props.collection.id(), this.props.collection.id(),
this.state.selectedContent || this.state.sqlQueryEditorContent, this.state.selectedContent || this.state.sqlQueryEditorContent,
{ enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey() } as FeedOptions, {
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey(),
abortSignal: this.queryAbortController.signal,
} as FeedOptions,
); );
} }
@ -244,6 +250,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
this.setState({ this.setState({
isExecuting: true, isExecuting: true,
}); });
useCommandBar.getState().setContextButtons(this.getTabsButtons());
try { try {
const queryResults: ViewModels.QueryResults = await QueryUtils.queryPagesUntilContentPresent( const queryResults: ViewModels.QueryResults = await QueryUtils.queryPagesUntilContentPresent(
@ -268,6 +275,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
isExecuting: false, isExecuting: false,
}); });
this.togglesOnFocus(); this.togglesOnFocus();
useCommandBar.getState().setContextButtons(this.getTabsButtons());
} }
} }
@ -332,6 +340,18 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
buttons.push(launchCopilotButton); buttons.push(launchCopilotButton);
} }
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
const label = "Cancel query";
buttons.push({
iconSrc: CancelQueryIcon,
iconAlt: label,
onCommandClick: () => this.queryAbortController.abort(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
});
}
return buttons; return buttons;
} }