mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-15 04:55:15 +00:00
601 lines
23 KiB
TypeScript
601 lines
23 KiB
TypeScript
import * as ko from "knockout";
|
|
import * as Constants from "../../Common/Constants";
|
|
import * as DataModels from "../../Contracts/DataModels";
|
|
import * as ViewModels from "../../Contracts/ViewModels";
|
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|
import TabsBase from "./TabsBase";
|
|
import { HashMap } from "../../Common/HashMap";
|
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
|
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
|
import { QueryUtils } from "../../Utils/QueryUtils";
|
|
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
|
|
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
|
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
|
|
|
|
enum ToggleState {
|
|
Result,
|
|
QueryMetrics,
|
|
}
|
|
|
|
export default class QueryTab extends TabsBase implements ViewModels.WaitsForTemplate {
|
|
public queryEditorId: string;
|
|
public executeQueryButton: ViewModels.Button;
|
|
public fetchNextPageButton: ViewModels.Button;
|
|
public saveQueryButton: ViewModels.Button;
|
|
public initialEditorContent: ko.Observable<string>;
|
|
public maybeSubQuery: ko.Computed<boolean>;
|
|
public sqlQueryEditorContent: ko.Observable<string>;
|
|
public selectedContent: ko.Observable<string>;
|
|
public sqlStatementToExecute: ko.Observable<string>;
|
|
public queryResults: ko.Observable<string>;
|
|
public error: ko.Observable<string>;
|
|
public statusMessge: ko.Observable<string>;
|
|
public statusIcon: ko.Observable<string>;
|
|
public allResultsMetadata: ko.ObservableArray<ViewModels.QueryResultsMetadata>;
|
|
public showingDocumentsDisplayText: ko.Observable<string>;
|
|
public requestChargeDisplayText: ko.Observable<string>;
|
|
public isTemplateReady: ko.Observable<boolean>;
|
|
public splitterId: string;
|
|
public splitter: Splitter;
|
|
public isPreferredApiMongoDB: boolean;
|
|
|
|
public queryMetrics: ko.Observable<HashMap<DataModels.QueryMetrics>>;
|
|
public aggregatedQueryMetrics: ko.Observable<DataModels.QueryMetrics>;
|
|
public activityId: ko.Observable<string>;
|
|
public roundTrips: ko.Observable<number>;
|
|
public toggleState: ko.Observable<ToggleState>;
|
|
public isQueryMetricsEnabled: ko.Computed<boolean>;
|
|
|
|
protected monacoSettings: ViewModels.MonacoEditorSettings;
|
|
private _executeQueryButtonTitle: ko.Observable<string>;
|
|
protected _iterator: MinimalQueryIterator;
|
|
private _isSaveQueriesEnabled: ko.Computed<boolean>;
|
|
private _resourceTokenPartitionKey: string;
|
|
|
|
_partitionKey: DataModels.PartitionKey;
|
|
|
|
constructor(options: ViewModels.QueryTabOptions) {
|
|
super(options);
|
|
this.queryEditorId = `queryeditor${this.tabId}`;
|
|
this.showingDocumentsDisplayText = ko.observable<string>();
|
|
this.requestChargeDisplayText = ko.observable<string>();
|
|
const defaultQueryText = options.queryText != void 0 ? options.queryText : "SELECT * FROM c";
|
|
this.initialEditorContent = ko.observable<string>(defaultQueryText);
|
|
this.sqlQueryEditorContent = ko.observable<string>(defaultQueryText);
|
|
this._executeQueryButtonTitle = ko.observable<string>("Execute Query");
|
|
this.selectedContent = ko.observable<string>();
|
|
this.selectedContent.subscribe((selectedContent: string) => {
|
|
if (!selectedContent.trim()) {
|
|
this._executeQueryButtonTitle("Execute Query");
|
|
} else {
|
|
this._executeQueryButtonTitle("Execute Selection");
|
|
}
|
|
});
|
|
this.sqlStatementToExecute = ko.observable<string>("");
|
|
this.queryResults = ko.observable<string>("");
|
|
this.statusMessge = ko.observable<string>();
|
|
this.statusIcon = ko.observable<string>();
|
|
this.allResultsMetadata = ko.observableArray<ViewModels.QueryResultsMetadata>([]);
|
|
this.error = ko.observable<string>();
|
|
this._partitionKey = options.partitionKey;
|
|
this._resourceTokenPartitionKey = options.resourceTokenPartitionKey;
|
|
this.splitterId = this.tabId + "_splitter";
|
|
this.isPreferredApiMongoDB = false;
|
|
this.aggregatedQueryMetrics = ko.observable<DataModels.QueryMetrics>();
|
|
this._resetAggregateQueryMetrics();
|
|
this.queryMetrics = ko.observable<HashMap<DataModels.QueryMetrics>>(new HashMap<DataModels.QueryMetrics>());
|
|
this.queryMetrics.subscribe((metrics: HashMap<DataModels.QueryMetrics>) =>
|
|
this.aggregatedQueryMetrics(this._aggregateQueryMetrics(metrics))
|
|
);
|
|
this.isQueryMetricsEnabled = ko.computed<boolean>(() => {
|
|
return (
|
|
(this.collection && this.collection.container && this.collection.container.isPreferredApiDocumentDB()) || false
|
|
);
|
|
});
|
|
this.activityId = ko.observable<string>();
|
|
this.roundTrips = ko.observable<number>();
|
|
this.toggleState = ko.observable<ToggleState>(ToggleState.Result);
|
|
|
|
this.monacoSettings = new ViewModels.MonacoEditorSettings("sql", false);
|
|
|
|
this.executeQueryButton = {
|
|
enabled: ko.computed<boolean>(() => {
|
|
return !!this.sqlQueryEditorContent() && this.sqlQueryEditorContent().length > 0;
|
|
}),
|
|
|
|
visible: ko.computed<boolean>(() => {
|
|
return true;
|
|
}),
|
|
};
|
|
|
|
this._isSaveQueriesEnabled = ko.computed<boolean>(() => {
|
|
const container = this.collection && this.collection.container;
|
|
return (container && (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph())) || false;
|
|
});
|
|
|
|
this.maybeSubQuery = ko.computed<boolean>(function () {
|
|
const sql = this.sqlQueryEditorContent();
|
|
return sql && /.*\(.*SELECT.*\)/i.test(sql);
|
|
}, this);
|
|
|
|
this.saveQueryButton = {
|
|
enabled: this._isSaveQueriesEnabled,
|
|
visible: this._isSaveQueriesEnabled,
|
|
};
|
|
|
|
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
if (isTemplateReady) {
|
|
const splitterBounds: SplitterBounds = {
|
|
min: Constants.Queries.QueryEditorMinHeightRatio * window.innerHeight,
|
|
max: $("#" + this.tabId).height() - Constants.Queries.QueryEditorMaxHeightRatio * window.innerHeight,
|
|
};
|
|
this.splitter = new Splitter({
|
|
splitterId: this.splitterId,
|
|
leftId: this.queryEditorId,
|
|
bounds: splitterBounds,
|
|
direction: SplitterDirection.Horizontal,
|
|
});
|
|
}
|
|
});
|
|
|
|
this.fetchNextPageButton = {
|
|
enabled: ko.computed<boolean>(() => {
|
|
const allResultsMetadata = this.allResultsMetadata() || [];
|
|
const numberOfResultsMetadata = allResultsMetadata.length;
|
|
|
|
if (numberOfResultsMetadata === 0) {
|
|
return false;
|
|
}
|
|
|
|
if (allResultsMetadata[numberOfResultsMetadata - 1].hasMoreResults) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}),
|
|
|
|
visible: ko.computed<boolean>(() => {
|
|
return true;
|
|
}),
|
|
};
|
|
|
|
this._buildCommandBarOptions();
|
|
}
|
|
|
|
public onTabClick(): void {
|
|
super.onTabClick();
|
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
|
}
|
|
|
|
public onExecuteQueryClick = async (): Promise<void> => {
|
|
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
|
this.sqlStatementToExecute(sqlStatement);
|
|
this.allResultsMetadata([]);
|
|
this.queryResults("");
|
|
this._iterator = undefined;
|
|
|
|
await this._executeQueryDocumentsPage(0);
|
|
};
|
|
|
|
public onLoadQueryClick = (): void => {
|
|
this.collection && this.collection.container && this.collection.container.loadQueryPane.open();
|
|
};
|
|
|
|
public onSaveQueryClick = (): void => {
|
|
this.collection && this.collection.container && this.collection.container.saveQueryPane.open();
|
|
};
|
|
|
|
public onSavedQueriesClick = (): void => {
|
|
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
|
};
|
|
|
|
public async onFetchNextPageClick(): Promise<void> {
|
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
|
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
|
const itemCount: number = (metadata && Number(metadata.itemCount)) || 0;
|
|
|
|
await this._executeQueryDocumentsPage(firstResultIndex + itemCount - 1);
|
|
}
|
|
|
|
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
|
this.collection && this.collection.container.expandConsole();
|
|
|
|
return false;
|
|
};
|
|
|
|
public onErrorDetailsKeyPress = (src: any, event: KeyboardEvent): boolean => {
|
|
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
|
this.onErrorDetailsClick(src, null);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
public toggleResult(): void {
|
|
this.toggleState(ToggleState.Result);
|
|
this.queryResults.valueHasMutated(); // needed to refresh the json-editor component
|
|
}
|
|
|
|
public toggleMetrics(): void {
|
|
this.toggleState(ToggleState.QueryMetrics);
|
|
}
|
|
|
|
public onToggleKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
if (event.keyCode === Constants.KeyCodes.LeftArrow) {
|
|
this.toggleResult();
|
|
event.stopPropagation();
|
|
return false;
|
|
} else if (event.keyCode === Constants.KeyCodes.RightArrow) {
|
|
this.toggleMetrics();
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
public togglesOnFocus(): void {
|
|
const focusElement = document.getElementById("execute-query-toggles");
|
|
focusElement && focusElement.focus();
|
|
}
|
|
|
|
public isResultToggled(): boolean {
|
|
return this.toggleState() === ToggleState.Result;
|
|
}
|
|
|
|
public isMetricsToggled(): boolean {
|
|
return this.toggleState() === ToggleState.QueryMetrics;
|
|
}
|
|
|
|
public onDownloadQueryMetricsCsvClick = (source: any, event: MouseEvent): boolean => {
|
|
this._downloadQueryMetricsCsvData();
|
|
return false;
|
|
};
|
|
|
|
public onDownloadQueryMetricsCsvKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
|
if (event.keyCode === Constants.KeyCodes.Space || Constants.KeyCodes.Enter) {
|
|
this._downloadQueryMetricsCsvData();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
private async _executeQueryDocumentsPage(firstItemIndex: number): Promise<any> {
|
|
this.error("");
|
|
this.roundTrips(undefined);
|
|
if (this._iterator === undefined) {
|
|
this._initIterator();
|
|
}
|
|
|
|
await this._queryDocumentsPage(firstItemIndex);
|
|
}
|
|
|
|
// TODO: Position and enable spinner when request is in progress
|
|
private async _queryDocumentsPage(firstItemIndex: number): Promise<void> {
|
|
this.isExecutionError(false);
|
|
this._resetAggregateQueryMetrics();
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
tabTitle: this.tabTitle(),
|
|
});
|
|
let options: any = {};
|
|
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
|
|
|
const queryDocuments = async (firstItemIndex: number) =>
|
|
await queryDocumentsPage(this.collection && this.collection.id(), this._iterator, firstItemIndex);
|
|
this.isExecuting(true);
|
|
|
|
try {
|
|
const queryResults: ViewModels.QueryResults = await QueryUtils.queryPagesUntilContentPresent(
|
|
firstItemIndex,
|
|
queryDocuments
|
|
);
|
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
|
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
|
hasMoreResults: queryResults.hasMoreResults,
|
|
itemCount: queryResults.itemCount,
|
|
firstItemIndex: queryResults.firstItemIndex,
|
|
lastItemIndex: queryResults.lastItemIndex,
|
|
};
|
|
this.allResultsMetadata.push(resultsMetadata);
|
|
this.activityId(queryResults.activityId);
|
|
this.roundTrips(queryResults.roundTrips);
|
|
|
|
this._updateQueryMetricsMap(queryResults.headers[Constants.HttpHeaders.queryMetrics]);
|
|
|
|
if (queryResults.itemCount == 0 && metadata != null && metadata.itemCount >= 0) {
|
|
// we let users query for the next page because the SDK sometimes specifies there are more elements
|
|
// even though there aren't any so we should not update the prior query results.
|
|
return;
|
|
}
|
|
|
|
const documents: any[] = queryResults.documents;
|
|
const results = this.renderObjectForEditor(documents, null, 4);
|
|
|
|
const resultsDisplay: string =
|
|
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
|
|
this.showingDocumentsDisplayText(resultsDisplay);
|
|
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
|
|
this.queryResults(results);
|
|
|
|
TelemetryProcessor.traceSuccess(
|
|
Action.ExecuteQuery,
|
|
{
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
tabTitle: this.tabTitle(),
|
|
},
|
|
startKey
|
|
);
|
|
} catch (error) {
|
|
this.isExecutionError(true);
|
|
const errorMessage = getErrorMessage(error);
|
|
this.error(errorMessage);
|
|
TelemetryProcessor.traceFailure(
|
|
Action.ExecuteQuery,
|
|
{
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
tabTitle: this.tabTitle(),
|
|
error: errorMessage,
|
|
errorStack: getErrorStack(error),
|
|
},
|
|
startKey
|
|
);
|
|
document.getElementById("error-display").focus();
|
|
} finally {
|
|
this.isExecuting(false);
|
|
this.togglesOnFocus();
|
|
}
|
|
}
|
|
|
|
private _updateQueryMetricsMap(metricsMap: { [partitionKeyRange: string]: DataModels.QueryMetrics }): void {
|
|
if (!metricsMap) {
|
|
return;
|
|
}
|
|
|
|
Object.keys(metricsMap).forEach((key: string) => {
|
|
this.queryMetrics().set(key, metricsMap[key]);
|
|
});
|
|
this.queryMetrics.valueHasMutated();
|
|
}
|
|
|
|
private _aggregateQueryMetrics(metricsMap: HashMap<DataModels.QueryMetrics>): DataModels.QueryMetrics {
|
|
if (!metricsMap) {
|
|
return null;
|
|
}
|
|
|
|
const aggregatedMetrics: DataModels.QueryMetrics = this.aggregatedQueryMetrics();
|
|
metricsMap.forEach((partitionKeyRangeId: string, queryMetrics: DataModels.QueryMetrics) => {
|
|
if (queryMetrics) {
|
|
aggregatedMetrics.documentLoadTime =
|
|
queryMetrics.documentLoadTime &&
|
|
this._normalize(queryMetrics.documentLoadTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.documentLoadTime);
|
|
aggregatedMetrics.documentWriteTime =
|
|
queryMetrics.documentWriteTime &&
|
|
this._normalize(queryMetrics.documentWriteTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.documentWriteTime);
|
|
aggregatedMetrics.indexHitDocumentCount =
|
|
queryMetrics.indexHitDocumentCount &&
|
|
this._normalize(queryMetrics.indexHitDocumentCount) +
|
|
this._normalize(aggregatedMetrics.indexHitDocumentCount);
|
|
aggregatedMetrics.outputDocumentCount =
|
|
queryMetrics.outputDocumentCount &&
|
|
this._normalize(queryMetrics.outputDocumentCount) + this._normalize(aggregatedMetrics.outputDocumentCount);
|
|
aggregatedMetrics.outputDocumentSize =
|
|
queryMetrics.outputDocumentSize &&
|
|
this._normalize(queryMetrics.outputDocumentSize) + this._normalize(aggregatedMetrics.outputDocumentSize);
|
|
aggregatedMetrics.indexLookupTime =
|
|
queryMetrics.indexLookupTime &&
|
|
this._normalize(queryMetrics.indexLookupTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.indexLookupTime);
|
|
aggregatedMetrics.retrievedDocumentCount =
|
|
queryMetrics.retrievedDocumentCount &&
|
|
this._normalize(queryMetrics.retrievedDocumentCount) +
|
|
this._normalize(aggregatedMetrics.retrievedDocumentCount);
|
|
aggregatedMetrics.retrievedDocumentSize =
|
|
queryMetrics.retrievedDocumentSize &&
|
|
this._normalize(queryMetrics.retrievedDocumentSize) +
|
|
this._normalize(aggregatedMetrics.retrievedDocumentSize);
|
|
aggregatedMetrics.vmExecutionTime =
|
|
queryMetrics.vmExecutionTime &&
|
|
this._normalize(queryMetrics.vmExecutionTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.vmExecutionTime);
|
|
aggregatedMetrics.totalQueryExecutionTime =
|
|
queryMetrics.totalQueryExecutionTime &&
|
|
this._normalize(queryMetrics.totalQueryExecutionTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.totalQueryExecutionTime);
|
|
|
|
aggregatedMetrics.runtimeExecutionTimes.queryEngineExecutionTime =
|
|
aggregatedMetrics.runtimeExecutionTimes &&
|
|
this._normalize(queryMetrics.runtimeExecutionTimes.queryEngineExecutionTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.runtimeExecutionTimes.queryEngineExecutionTime);
|
|
aggregatedMetrics.runtimeExecutionTimes.systemFunctionExecutionTime =
|
|
aggregatedMetrics.runtimeExecutionTimes &&
|
|
this._normalize(queryMetrics.runtimeExecutionTimes.systemFunctionExecutionTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.runtimeExecutionTimes.systemFunctionExecutionTime);
|
|
aggregatedMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime =
|
|
aggregatedMetrics.runtimeExecutionTimes &&
|
|
this._normalize(queryMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime.totalMilliseconds()) +
|
|
this._normalize(aggregatedMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime);
|
|
}
|
|
});
|
|
|
|
return aggregatedMetrics;
|
|
}
|
|
|
|
public _downloadQueryMetricsCsvData(): void {
|
|
const csvData: string = this._generateQueryMetricsCsvData();
|
|
if (!csvData) {
|
|
return;
|
|
}
|
|
|
|
if (navigator.msSaveBlob) {
|
|
// for IE and Edge
|
|
navigator.msSaveBlob(
|
|
new Blob([csvData], { type: "data:text/csv;charset=utf-8" }),
|
|
"PerPartitionQueryMetrics.csv"
|
|
);
|
|
} else {
|
|
const downloadLink: HTMLAnchorElement = document.createElement("a");
|
|
downloadLink.href = "data:text/csv;charset=utf-8," + encodeURI(csvData);
|
|
downloadLink.target = "_self";
|
|
downloadLink.download = "QueryMetricsPerPartition.csv";
|
|
|
|
// for some reason, FF displays the download prompt only when
|
|
// the link is added to the dom so we add and remove it
|
|
document.body.appendChild(downloadLink);
|
|
downloadLink.click();
|
|
downloadLink.remove();
|
|
}
|
|
}
|
|
|
|
protected _initIterator(): void {
|
|
const options: any = QueryTab.getIteratorOptions(this.collection);
|
|
if (this._resourceTokenPartitionKey) {
|
|
options.partitionKey = this._resourceTokenPartitionKey;
|
|
}
|
|
|
|
this._iterator = queryDocuments(
|
|
this.collection.databaseId,
|
|
this.collection.id(),
|
|
this.sqlStatementToExecute(),
|
|
options
|
|
);
|
|
}
|
|
|
|
public static getIteratorOptions(container: ViewModels.CollectionBase): any {
|
|
let options: any = {};
|
|
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
|
return options;
|
|
}
|
|
|
|
private _normalize(value: number): number {
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private _resetAggregateQueryMetrics(): void {
|
|
this.aggregatedQueryMetrics({
|
|
clientSideMetrics: {},
|
|
documentLoadTime: undefined,
|
|
documentWriteTime: undefined,
|
|
indexHitDocumentCount: undefined,
|
|
outputDocumentCount: undefined,
|
|
outputDocumentSize: undefined,
|
|
indexLookupTime: undefined,
|
|
retrievedDocumentCount: undefined,
|
|
retrievedDocumentSize: undefined,
|
|
vmExecutionTime: undefined,
|
|
queryPreparationTimes: undefined,
|
|
runtimeExecutionTimes: {
|
|
queryEngineExecutionTime: undefined,
|
|
systemFunctionExecutionTime: undefined,
|
|
userDefinedFunctionExecutionTime: undefined,
|
|
},
|
|
totalQueryExecutionTime: undefined,
|
|
});
|
|
}
|
|
|
|
private _generateQueryMetricsCsvData(): string {
|
|
if (!this.queryMetrics()) {
|
|
return null;
|
|
}
|
|
|
|
const queryMetrics: HashMap<DataModels.QueryMetrics> = this.queryMetrics();
|
|
let csvData: string = "";
|
|
const columnHeaders: string =
|
|
[
|
|
"Partition key range id",
|
|
"Retrieved document count",
|
|
"Retrieved document size (in bytes)",
|
|
"Output document count",
|
|
"Output document size (in bytes)",
|
|
"Index hit document count",
|
|
"Index lookup time (ms)",
|
|
"Document load time (ms)",
|
|
"Query engine execution time (ms)",
|
|
"System function execution time (ms)",
|
|
"User defined function execution time (ms)",
|
|
"Document write time (ms)",
|
|
].join(",") + "\n";
|
|
csvData = csvData + columnHeaders;
|
|
queryMetrics.forEach((partitionKeyRangeId: string, queryMetric: DataModels.QueryMetrics) => {
|
|
const partitionKeyRangeData: string =
|
|
[
|
|
partitionKeyRangeId,
|
|
queryMetric.retrievedDocumentCount,
|
|
queryMetric.retrievedDocumentSize,
|
|
queryMetric.outputDocumentCount,
|
|
queryMetric.outputDocumentSize,
|
|
queryMetric.indexHitDocumentCount,
|
|
queryMetric.indexLookupTime && queryMetric.indexLookupTime.totalMilliseconds(),
|
|
queryMetric.documentLoadTime && queryMetric.documentLoadTime.totalMilliseconds(),
|
|
queryMetric.runtimeExecutionTimes &&
|
|
queryMetric.runtimeExecutionTimes.queryEngineExecutionTime &&
|
|
queryMetric.runtimeExecutionTimes.queryEngineExecutionTime.totalMilliseconds(),
|
|
queryMetric.runtimeExecutionTimes &&
|
|
queryMetric.runtimeExecutionTimes.systemFunctionExecutionTime &&
|
|
queryMetric.runtimeExecutionTimes.systemFunctionExecutionTime.totalMilliseconds(),
|
|
queryMetric.runtimeExecutionTimes &&
|
|
queryMetric.runtimeExecutionTimes.userDefinedFunctionExecutionTime &&
|
|
queryMetric.runtimeExecutionTimes.userDefinedFunctionExecutionTime.totalMilliseconds(),
|
|
queryMetric.documentWriteTime && queryMetric.documentWriteTime.totalMilliseconds(),
|
|
].join(",") + "\n";
|
|
csvData = csvData + partitionKeyRangeData;
|
|
});
|
|
|
|
return csvData;
|
|
}
|
|
|
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
|
const buttons: CommandButtonComponentProps[] = [];
|
|
if (this.executeQueryButton.visible()) {
|
|
const label = this._executeQueryButtonTitle();
|
|
buttons.push({
|
|
iconSrc: ExecuteQueryIcon,
|
|
iconAlt: label,
|
|
onCommandClick: this.onExecuteQueryClick,
|
|
commandButtonLabel: label,
|
|
ariaLabel: label,
|
|
hasPopup: false,
|
|
disabled: !this.executeQueryButton.enabled(),
|
|
});
|
|
}
|
|
|
|
if (this.saveQueryButton.visible()) {
|
|
const label = "Save Query";
|
|
buttons.push({
|
|
iconSrc: SaveQueryIcon,
|
|
iconAlt: label,
|
|
onCommandClick: this.onSaveQueryClick,
|
|
commandButtonLabel: label,
|
|
ariaLabel: label,
|
|
hasPopup: false,
|
|
disabled: !this.saveQueryButton.enabled(),
|
|
});
|
|
}
|
|
|
|
return buttons;
|
|
}
|
|
|
|
private _buildCommandBarOptions(): void {
|
|
ko.computed(() =>
|
|
ko.toJSON([this.executeQueryButton.visible, this.executeQueryButton.enabled, this._executeQueryButtonTitle])
|
|
).subscribe(() => this.updateNavbarWithTabsButtons());
|
|
this.updateNavbarWithTabsButtons();
|
|
}
|
|
}
|