Cancel query timeout (#1651)
* cancel query option * query timeout * run prettier * removed comments * fixed npm run compile errors * fixed tests * fixed unit test errors * fixed unit test errors * fixed unit test errors * fixed unit test errors * fixed unit test errors * increased min timeout * added automatican cancel query option * added react string format * npm run format * added unless automatic cancellation has been enabled --------- Co-authored-by: Asier Isayas <aisayas@microsoft.com>
This commit is contained in:
parent
9b032ecae4
commit
94158504a8
|
@ -22133,6 +22133,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
|
||||||
"integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
|
"integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
|
||||||
},
|
},
|
||||||
|
"react-string-format": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/react-string-format/-/react-string-format-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-JyQaRZHqURInBBx64HC3FJBh3AA=",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"react-syntax-highlighter": {
|
"react-syntax-highlighter": {
|
||||||
"version": "12.2.1",
|
"version": "12.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
"react-splitter-layout": "4.0.0",
|
"react-splitter-layout": "4.0.0",
|
||||||
|
"react-string-format": "1.0.1",
|
||||||
"react-youtube": "9.0.1",
|
"react-youtube": "9.0.1",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
import {
|
||||||
|
Checkbox,
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
ISpinButtonStyles,
|
||||||
|
IToggleStyles,
|
||||||
|
Position,
|
||||||
|
SpinButton,
|
||||||
|
Toggle,
|
||||||
|
} from "@fluentui/react";
|
||||||
import * as Constants from "Common/Constants";
|
import * as Constants from "Common/Constants";
|
||||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
|
@ -6,10 +15,10 @@ import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import * as StringUtility from "Shared/StringUtility";
|
import * as StringUtility from "Shared/StringUtility";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
|
||||||
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
|
||||||
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
||||||
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
|
import React, { FunctionComponent, useState } from "react";
|
||||||
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export const SettingsPane: FunctionComponent = () => {
|
export const SettingsPane: FunctionComponent = () => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
@ -19,6 +28,13 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
? Constants.Queries.UnlimitedPageOption
|
? Constants.Queries.UnlimitedPageOption
|
||||||
: Constants.Queries.CustomPageOption,
|
: Constants.Queries.CustomPageOption,
|
||||||
);
|
);
|
||||||
|
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
||||||
|
LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled),
|
||||||
|
);
|
||||||
|
const [queryTimeout, setQueryTimeout] = useState<number>(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout));
|
||||||
|
const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState<boolean>(
|
||||||
|
LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout),
|
||||||
|
);
|
||||||
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
|
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
|
||||||
LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0,
|
LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0,
|
||||||
);
|
);
|
||||||
|
@ -53,7 +69,7 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
||||||
const handlerOnSubmit = (e: MouseEvent<HTMLButtonElement>) => {
|
const handlerOnSubmit = () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryNumber(
|
LocalStorageUtility.setEntryNumber(
|
||||||
|
@ -61,6 +77,7 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
||||||
);
|
);
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||||
|
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
||||||
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
|
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
|
||||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||||
|
@ -73,6 +90,14 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queryTimeoutEnabled) {
|
||||||
|
LocalStorageUtility.setEntryNumber(StorageKey.QueryTimeout, queryTimeout);
|
||||||
|
LocalStorageUtility.setEntryBoolean(
|
||||||
|
StorageKey.AutomaticallyCancelQueryAfterTimeout,
|
||||||
|
automaticallyCancelQueryAfterTimeout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||||
|
@ -97,7 +122,6 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
|
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
|
||||||
);
|
);
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
e.preventDefault();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isCustomPageOptionSelected = () => {
|
const isCustomPageOptionSelected = () => {
|
||||||
|
@ -112,7 +136,7 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
formError: "",
|
formError: "",
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "Apply",
|
submitButtonText: "Apply",
|
||||||
onSubmit: () => handlerOnSubmit(undefined),
|
onSubmit: () => handlerOnSubmit(),
|
||||||
};
|
};
|
||||||
const pageOptionList: IChoiceGroupOption[] = [
|
const pageOptionList: IChoiceGroupOption[] = [
|
||||||
{ key: Constants.Queries.CustomPageOption, text: "Custom" },
|
{ key: Constants.Queries.CustomPageOption, text: "Custom" },
|
||||||
|
@ -140,6 +164,21 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnQueryTimeoutToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
||||||
|
setQueryTimeoutEnabled(checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnAutomaticallyCancelQueryToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
||||||
|
setAutomaticallyCancelQueryAfterTimeout(checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnQueryTimeoutSpinButtonChange = (ev: React.MouseEvent<HTMLElement>, newValue?: string): void => {
|
||||||
|
const queryTimeout = Number(newValue);
|
||||||
|
if (!isNaN(queryTimeout)) {
|
||||||
|
setQueryTimeout(queryTimeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
|
@ -161,6 +200,35 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryTimeoutToggleStyles: IToggleStyles = {
|
||||||
|
label: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 400,
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
root: {},
|
||||||
|
container: {},
|
||||||
|
pill: {},
|
||||||
|
thumb: {},
|
||||||
|
text: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryTimeoutSpinButtonStyles: ISpinButtonStyles = {
|
||||||
|
label: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
paddingBottom: 10,
|
||||||
|
},
|
||||||
|
labelWrapper: {},
|
||||||
|
icon: {},
|
||||||
|
spinButtonWrapper: {},
|
||||||
|
input: {},
|
||||||
|
arrowButtonsContainer: {},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
|
@ -211,6 +279,50 @@ export const SettingsPane: FunctionComponent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{userContext.apiType === "SQL" && (
|
||||||
|
<div className="settingsSection">
|
||||||
|
<div className="settingsSectionPart">
|
||||||
|
<div>
|
||||||
|
<legend id="queryTimeoutLabel" className="settingsSectionLabel legendLabel">
|
||||||
|
Query Timeout
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>
|
||||||
|
When a query reaches a specified time limit, a popup with an option to cancel the query will show
|
||||||
|
unless automatic cancellation has been enabled
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
styles={queryTimeoutToggleStyles}
|
||||||
|
label="Enable query timeout"
|
||||||
|
onChange={handleOnQueryTimeoutToggleChange}
|
||||||
|
defaultChecked={queryTimeoutEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{queryTimeoutEnabled && (
|
||||||
|
<div>
|
||||||
|
<SpinButton
|
||||||
|
label="Query timeout (ms)"
|
||||||
|
labelPosition={Position.top}
|
||||||
|
defaultValue={(queryTimeout || 5000).toString()}
|
||||||
|
min={100}
|
||||||
|
step={1000}
|
||||||
|
onChange={handleOnQueryTimeoutSpinButtonChange}
|
||||||
|
incrementButtonAriaLabel="Increase value by 1000"
|
||||||
|
decrementButtonAriaLabel="Decrease value by 1000"
|
||||||
|
styles={queryTimeoutSpinButtonStyles}
|
||||||
|
/>
|
||||||
|
<Toggle
|
||||||
|
label="Automatically cancel query after timeout"
|
||||||
|
styles={queryTimeoutToggleStyles}
|
||||||
|
onChange={handleOnAutomaticallyCancelQueryToggleChange}
|
||||||
|
defaultChecked={automaticallyCancelQueryAfterTimeout}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
|
|
|
@ -97,6 +97,46 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="settingsSection"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionPart"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<legend
|
||||||
|
className="settingsSectionLabel legendLabel"
|
||||||
|
id="queryTimeoutLabel"
|
||||||
|
>
|
||||||
|
Query Timeout
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>
|
||||||
|
When a query reaches a specified time limit, a popup with an option to cancel the query will show unless automatic cancellation has been enabled
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<StyledToggleBase
|
||||||
|
defaultChecked={false}
|
||||||
|
label="Enable query timeout"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"container": Object {},
|
||||||
|
"label": Object {
|
||||||
|
"display": "block",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontWeight": 400,
|
||||||
|
},
|
||||||
|
"pill": Object {},
|
||||||
|
"root": Object {},
|
||||||
|
"text": Object {},
|
||||||
|
"thumb": Object {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
<button
|
<button
|
||||||
class="filterbtnstyle queryButton"
|
class="filterbtnstyle queryButton"
|
||||||
data-bind="
|
data-bind="
|
||||||
click: refreshDocumentsGrid,
|
click: refreshDocumentsGrid.bind($data, true),
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -176,7 +176,7 @@
|
||||||
<img
|
<img
|
||||||
class="refreshcol"
|
class="refreshcol"
|
||||||
src="/refresh-cosmos.svg"
|
src="/refresh-cosmos.svg"
|
||||||
data-bind="click: refreshDocumentsGrid"
|
data-bind="click: refreshDocumentsGrid.bind($data, false)"
|
||||||
alt="Refresh documents"
|
alt="Refresh documents"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
@ -209,7 +209,10 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="loadMore">
|
<div class="loadMore">
|
||||||
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
|
<a
|
||||||
|
role="button"
|
||||||
|
data-bind="click: loadNextPage.bind($data, false), event: { keypress: onLoadMoreKeyInput }"
|
||||||
|
tabindex="0"
|
||||||
>Load more</a
|
>Load more</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { extractPartitionKey, ItemDefinition, PartitionKeyDefinition, QueryItera
|
||||||
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
|
import { format } from "react-string-format";
|
||||||
|
import { QueryConstants } from "Shared/Constants";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import NewDocumentIcon from "../../../images/NewDocument.svg";
|
import NewDocumentIcon from "../../../images/NewDocument.svg";
|
||||||
|
@ -79,6 +82,7 @@ export default class DocumentsTab extends TabsBase {
|
||||||
private _resourceTokenPartitionKey: string;
|
private _resourceTokenPartitionKey: string;
|
||||||
private _isQueryCopilotSampleContainer: boolean;
|
private _isQueryCopilotSampleContainer: boolean;
|
||||||
private queryAbortController: AbortController;
|
private queryAbortController: AbortController;
|
||||||
|
private cancelQueryTimeoutID: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(options: ViewModels.DocumentsTabOptions) {
|
constructor(options: ViewModels.DocumentsTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -350,11 +354,11 @@ export default class DocumentsTab extends TabsBase {
|
||||||
* Query first page of documents
|
* Query first page of documents
|
||||||
* Select and query first document and display content
|
* Select and query first document and display content
|
||||||
*/
|
*/
|
||||||
private async autoPopulateContent() {
|
private async autoPopulateContent(applyFilterButtonPressed?: boolean) {
|
||||||
// reset iterator
|
// reset iterator
|
||||||
this._documentsIterator = this.createIterator();
|
this._documentsIterator = this.createIterator();
|
||||||
// load documents
|
// load documents
|
||||||
await this.loadNextPage();
|
await this.loadNextPage(applyFilterButtonPressed);
|
||||||
|
|
||||||
// Select first document and load content
|
// Select first document and load content
|
||||||
if (this.documentIds().length > 0) {
|
if (this.documentIds().length > 0) {
|
||||||
|
@ -391,12 +395,14 @@ export default class DocumentsTab extends TabsBase {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
public async refreshDocumentsGrid(): Promise<void> {
|
public async refreshDocumentsGrid(applyFilterButtonPressed?: boolean): Promise<void> {
|
||||||
// clear documents grid
|
// clear documents grid
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.autoPopulateContent();
|
// reset iterator
|
||||||
|
this._documentsIterator = this.createIterator();
|
||||||
|
// load documents
|
||||||
|
await this.autoPopulateContent(applyFilterButtonPressed);
|
||||||
// collapse filter
|
// collapse filter
|
||||||
this.appliedFilter(this.filterContent());
|
this.appliedFilter(this.filterContent());
|
||||||
this.isFilterExpanded(false);
|
this.isFilterExpanded(false);
|
||||||
|
@ -733,9 +739,35 @@ export default class DocumentsTab extends TabsBase {
|
||||||
this.initDocumentEditor(documentId, content);
|
this.initDocumentEditor(documentId, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(applyFilterButtonClicked?: boolean): Q.Promise<any> {
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
|
let automaticallyCancelQueryAfterTimeout: boolean;
|
||||||
|
if (applyFilterButtonClicked && this.queryTimeoutEnabled()) {
|
||||||
|
const queryTimeout: number = LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout);
|
||||||
|
automaticallyCancelQueryAfterTimeout = LocalStorageUtility.getEntryBoolean(
|
||||||
|
StorageKey.AutomaticallyCancelQueryAfterTimeout,
|
||||||
|
);
|
||||||
|
const cancelQueryTimeoutID: NodeJS.Timeout = setTimeout(() => {
|
||||||
|
if (this.isExecuting()) {
|
||||||
|
if (automaticallyCancelQueryAfterTimeout) {
|
||||||
|
this.queryAbortController.abort();
|
||||||
|
} else {
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkCancelModalDialog(
|
||||||
|
QueryConstants.CancelQueryTitle,
|
||||||
|
format(QueryConstants.CancelQuerySubTextTemplate, QueryConstants.CancelQueryTimeoutThresholdReached),
|
||||||
|
"Yes",
|
||||||
|
() => this.queryAbortController.abort(),
|
||||||
|
"No",
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queryTimeout);
|
||||||
|
this.cancelQueryTimeoutID = cancelQueryTimeoutID;
|
||||||
|
}
|
||||||
return this._loadNextPageInternal()
|
return this._loadNextPageInternal()
|
||||||
.then(
|
.then(
|
||||||
(documentsIdsResponse = []) => {
|
(documentsIdsResponse = []) => {
|
||||||
|
@ -791,7 +823,15 @@ export default class DocumentsTab extends TabsBase {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(() => this.isExecuting(false));
|
.finally(() => {
|
||||||
|
this.isExecuting(false);
|
||||||
|
if (applyFilterButtonClicked && this.queryTimeoutEnabled()) {
|
||||||
|
clearTimeout(this.cancelQueryTimeoutID);
|
||||||
|
if (!automaticallyCancelQueryAfterTimeout) {
|
||||||
|
useDialog.getState().closeDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLoadMoreKeyInput = (source: any, event: KeyboardEvent): void => {
|
public onLoadMoreKeyInput = (source: any, event: KeyboardEvent): void => {
|
||||||
|
@ -969,4 +1009,8 @@ export default class DocumentsTab extends TabsBase {
|
||||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private queryTimeoutEnabled(): boolean {
|
||||||
|
return !this.isPreferredApiMongoDB && LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import { FeedOptions } from "@azure/cosmos";
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotResults";
|
import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotResults";
|
||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||||
|
import { QueryConstants } from "Shared/Constants";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import React, { Fragment } from "react";
|
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 { format } from "react-string-format";
|
||||||
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
|
@ -80,6 +84,7 @@ interface IQueryTabStates {
|
||||||
isExecuting: boolean;
|
isExecuting: boolean;
|
||||||
showCopilotSidebar: boolean;
|
showCopilotSidebar: boolean;
|
||||||
queryCopilotGeneratedQuery: string;
|
queryCopilotGeneratedQuery: string;
|
||||||
|
cancelQueryTimeoutID: NodeJS.Timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class QueryTabComponent extends React.Component<IQueryTabComponentProps, IQueryTabStates> {
|
export default class QueryTabComponent extends React.Component<IQueryTabComponentProps, IQueryTabStates> {
|
||||||
|
@ -107,13 +112,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
showCopilotSidebar: useQueryCopilot.getState().showCopilotSidebar,
|
showCopilotSidebar: useQueryCopilot.getState().showCopilotSidebar,
|
||||||
queryCopilotGeneratedQuery: useQueryCopilot.getState().query,
|
queryCopilotGeneratedQuery: useQueryCopilot.getState().query,
|
||||||
|
cancelQueryTimeoutID: undefined,
|
||||||
};
|
};
|
||||||
this.isCloseClicked = false;
|
this.isCloseClicked = false;
|
||||||
this.splitterId = this.props.tabId + "_splitter";
|
this.splitterId = this.props.tabId + "_splitter";
|
||||||
this.queryEditorId = `queryeditor${this.props.tabId}`;
|
this.queryEditorId = `queryeditor${this.props.tabId}`;
|
||||||
this.isPreferredApiMongoDB = this.props.isPreferredApiMongoDB;
|
this.isPreferredApiMongoDB = this.props.isPreferredApiMongoDB;
|
||||||
this.isCopilotTabActive = QueryCopilotSampleDatabaseId === this.props.collection.databaseId;
|
this.isCopilotTabActive = QueryCopilotSampleDatabaseId === this.props.collection.databaseId;
|
||||||
|
|
||||||
this.executeQueryButton = {
|
this.executeQueryButton = {
|
||||||
enabled: !!this.state.sqlQueryEditorContent && this.state.sqlQueryEditorContent.length > 0,
|
enabled: !!this.state.sqlQueryEditorContent && this.state.sqlQueryEditorContent.length > 0,
|
||||||
visible: true,
|
visible: true,
|
||||||
|
@ -250,6 +255,34 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
this.setState({
|
this.setState({
|
||||||
isExecuting: true,
|
isExecuting: true,
|
||||||
});
|
});
|
||||||
|
let automaticallyCancelQueryAfterTimeout: boolean;
|
||||||
|
if (this.queryTimeoutEnabled()) {
|
||||||
|
const queryTimeout: number = LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout);
|
||||||
|
automaticallyCancelQueryAfterTimeout = LocalStorageUtility.getEntryBoolean(
|
||||||
|
StorageKey.AutomaticallyCancelQueryAfterTimeout,
|
||||||
|
);
|
||||||
|
const cancelQueryTimeoutID: NodeJS.Timeout = setTimeout(() => {
|
||||||
|
if (this.state.isExecuting) {
|
||||||
|
if (automaticallyCancelQueryAfterTimeout) {
|
||||||
|
this.queryAbortController.abort();
|
||||||
|
} else {
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkCancelModalDialog(
|
||||||
|
QueryConstants.CancelQueryTitle,
|
||||||
|
format(QueryConstants.CancelQuerySubTextTemplate, QueryConstants.CancelQueryTimeoutThresholdReached),
|
||||||
|
"Yes",
|
||||||
|
() => this.queryAbortController.abort(),
|
||||||
|
"No",
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queryTimeout);
|
||||||
|
this.setState({
|
||||||
|
cancelQueryTimeoutID,
|
||||||
|
});
|
||||||
|
}
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -273,7 +306,14 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
this.props.tabsBaseInstance.isExecuting(false);
|
this.props.tabsBaseInstance.isExecuting(false);
|
||||||
this.setState({
|
this.setState({
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
|
cancelQueryTimeoutID: undefined,
|
||||||
});
|
});
|
||||||
|
if (this.queryTimeoutEnabled()) {
|
||||||
|
clearTimeout(this.state.cancelQueryTimeoutID);
|
||||||
|
if (!automaticallyCancelQueryAfterTimeout) {
|
||||||
|
useDialog.getState().closeDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
this.togglesOnFocus();
|
this.togglesOnFocus();
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
@ -405,6 +445,10 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||||
return this.state.sqlQueryEditorContent;
|
return this.state.sqlQueryEditorContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private queryTimeoutEnabled(): boolean {
|
||||||
|
return !this.isPreferredApiMongoDB && LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
private unsubscribeCopilotSidebar: () => void;
|
private unsubscribeCopilotSidebar: () => void;
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
|
|
@ -208,3 +208,9 @@ export class FreeTierLimits {
|
||||||
public static RU: number = 1000;
|
public static RU: number = 1000;
|
||||||
public static Storage: number = 25;
|
public static Storage: number = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class QueryConstants {
|
||||||
|
public static readonly CancelQueryTitle: string = "Cancel query";
|
||||||
|
public static readonly CancelQuerySubTextTemplate: string = "{0} Do you want to cancel this query?";
|
||||||
|
public static readonly CancelQueryTimeoutThresholdReached: string = "The query timeout threshold has been reached.";
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||||
export { LocalStorageUtility, SessionStorageUtility };
|
export { LocalStorageUtility, SessionStorageUtility };
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
ActualItemPerPage,
|
ActualItemPerPage,
|
||||||
|
QueryTimeoutEnabled,
|
||||||
|
QueryTimeout,
|
||||||
|
AutomaticallyCancelQueryAfterTimeout,
|
||||||
ContainerPaginationEnabled,
|
ContainerPaginationEnabled,
|
||||||
CustomItemPerPage,
|
CustomItemPerPage,
|
||||||
DatabaseAccountId,
|
DatabaseAccountId,
|
||||||
|
|
Loading…
Reference in New Issue