Prettier 2.0 (#393)

This commit is contained in:
Steve Faulkner
2021-01-20 09:15:01 -06:00
committed by GitHub
parent c1937ca464
commit 4be53284b5
500 changed files with 41927 additions and 41838 deletions

View File

@@ -74,10 +74,7 @@ export default class ConflictsTab extends TabsBase {
this.partitionKeyPropertyHeader =
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
this.partitionKeyProperty = !!this.partitionKeyPropertyHeader
? this.partitionKeyPropertyHeader
.replace(/[/]+/g, ".")
.substr(1)
.replace(/[']+/g, "")
? this.partitionKeyPropertyHeader.replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "")
: null;
this.dataContentsGridScrollHeight = ko.observable<string>(null);
@@ -88,13 +85,13 @@ export default class ConflictsTab extends TabsBase {
const tabContainer: HTMLElement = document.getElementById("content");
const splitterBounds: SplitterBounds = {
min: Constants.DocumentsGridMetrics.DocumentEditorMinWidthRatio * tabContainer.clientWidth,
max: Constants.DocumentsGridMetrics.DocumentEditorMaxWidthRatio * tabContainer.clientWidth
max: Constants.DocumentsGridMetrics.DocumentEditorMaxWidthRatio * tabContainer.clientWidth,
};
this.splitter = new Splitter({
splitterId: "h_splitter2",
leftId: this.documentContentsContainerId,
bounds: splitterBounds,
direction: SplitterDirection.Vertical
direction: SplitterDirection.Vertical,
});
}
});
@@ -150,7 +147,7 @@ export default class ConflictsTab extends TabsBase {
visible: ko.computed<boolean>(() => {
return this.conflictOperation() !== Constants.ConflictOperationType.Delete || !!this.selectedConflictContent();
})
}),
};
this.discardButton = {
@@ -166,7 +163,7 @@ export default class ConflictsTab extends TabsBase {
visible: ko.computed<boolean>(() => {
return this.conflictOperation() !== Constants.ConflictOperationType.Delete || !!this.selectedConflictContent();
})
}),
};
this.deleteButton = {
@@ -182,7 +179,7 @@ export default class ConflictsTab extends TabsBase {
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
this.buildCommandBarOptions();
@@ -270,7 +267,7 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId
conflictResourceId: selectedConflict.resourceId,
});
try {
@@ -317,7 +314,7 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId
conflictResourceId: selectedConflict.resourceId,
},
startKey
);
@@ -336,7 +333,7 @@ export default class ConflictsTab extends TabsBase {
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId,
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -358,7 +355,7 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId
conflictResourceId: selectedConflict.resourceId,
});
try {
@@ -377,7 +374,7 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId
conflictResourceId: selectedConflict.resourceId,
},
startKey
);
@@ -396,7 +393,7 @@ export default class ConflictsTab extends TabsBase {
conflictOperationType: selectedConflict.operationType,
conflictResourceId: selectedConflict.resourceId,
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -453,7 +450,7 @@ export default class ConflictsTab extends TabsBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
this.onLoadStartKey
);
@@ -467,7 +464,7 @@ export default class ConflictsTab extends TabsBase {
// TODO: Conflict Feed does not allow filtering atm
const query: string = undefined;
const options = {
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey()
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey(),
};
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options as FeedOptions);
}
@@ -479,7 +476,7 @@ export default class ConflictsTab extends TabsBase {
.then(
(conflictIdsResponse: DataModels.ConflictId[]) => {
const currentConflicts = this.conflictIds();
const currentDocumentsRids = currentConflicts.map(currentConflict => currentConflict.rid);
const currentDocumentsRids = currentConflicts.map((currentConflict) => currentConflict.rid);
const nextConflictIds = conflictIdsResponse
// filter documents already loaded in observable
.filter((d: any) => {
@@ -501,14 +498,14 @@ export default class ConflictsTab extends TabsBase {
collectionName: this.collection.id(),
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
this.onLoadStartKey
);
this.onLoadStartKey = null;
}
},
error => {
(error) => {
this.isExecutionError(true);
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
TelemetryProcessor.traceFailure(
@@ -521,7 +518,7 @@ export default class ConflictsTab extends TabsBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
this.onLoadStartKey
);
@@ -541,7 +538,7 @@ export default class ConflictsTab extends TabsBase {
};
protected _loadNextPageInternal(): Q.Promise<DataModels.ConflictId[]> {
return Q(this._documentsIterator.fetchNext().then(response => response.resources));
return Q(this._documentsIterator.fetchNext().then((response) => response.resources));
}
protected _onEditorContentChange(newContent: string) {
@@ -615,7 +612,7 @@ export default class ConflictsTab extends TabsBase {
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.acceptChangesButton.enabled()
disabled: !this.acceptChangesButton.enabled(),
});
}
@@ -628,7 +625,7 @@ export default class ConflictsTab extends TabsBase {
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.discardButton.enabled()
disabled: !this.discardButton.enabled(),
});
}
@@ -641,7 +638,7 @@ export default class ConflictsTab extends TabsBase {
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.deleteButton.enabled()
disabled: !this.deleteButton.enabled(),
});
}
return buttons;
@@ -656,7 +653,7 @@ export default class ConflictsTab extends TabsBase {
this.discardButton.visible,
this.discardButton.enabled,
this.deleteButton.visible,
this.deleteButton.enabled
this.deleteButton.enabled,
])
).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();

View File

@@ -10,11 +10,11 @@
<div class="warningErrorContainer scaleWarningContainer" data-bind="visible: shouldShowStatusBar">
<div>
<div class="warningErrorContent" data-bind="visible: shouldShowNotificationStatusPrompt">
<span><img src="/info_color.svg" alt="Info"/></span>
<span><img src="/info_color.svg" alt="Info" /></span>
<span class="warningErrorDetailsLinkContainer" data-bind="html: notificationStatusInfo"></span>
</div>
<div class="warningErrorContent" data-bind="visible: !shouldShowNotificationStatusPrompt()">
<span><img src="/warning.svg" alt="Warning"/></span>
<span><img src="/warning.svg" alt="Warning" /></span>
<span class="warningErrorDetailsLinkContainer" data-bind="html: warningMessage"></span>
</div>
</div>
@@ -24,7 +24,7 @@
<span class="scaleSettingTitle">Scale</span>
</div>
<div class="freeTierInfoBanner" data-bind="visible: isFreeTierAccount">
<span class="freeTierInfoIcon"><img src="/info_color.svg" alt="Info"/></span>
<span class="freeTierInfoIcon"><img src="/info_color.svg" alt="Info" /></span>
<span class="freeTierInfoMessage"
>With free tier, you'll get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
account free, keep the total RU/s across all resources in the account to 400 RU/s.

View File

@@ -336,7 +336,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
this.discardSettingsChangesButton = {
@@ -356,7 +356,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
this.isTemplateReady = ko.observable<boolean>(false);
@@ -384,7 +384,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
const headerOptions: RequestOptions = { initialHeaders: {} };
@@ -394,7 +394,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
databaseId: this.database.id(),
currentOffer: this.database.offer(),
autopilotThroughput: this.isAutoPilotSelected() ? this.autoPilotThroughput() : undefined,
manualThroughput: this.isAutoPilotSelected() ? undefined : this.throughput()
manualThroughput: this.isAutoPilotSelected() ? undefined : this.throughput(),
};
if (this._hasProvisioningTypeChanged()) {
@@ -425,7 +425,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -467,7 +467,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.saveSettingsButton.enabled()
disabled: !this.saveSettingsButton.enabled(),
});
}
@@ -480,7 +480,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.discardSettingsChangesButton.enabled()
disabled: !this.discardSettingsChangesButton.enabled(),
});
}
return buttons;
@@ -492,7 +492,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.saveSettingsButton.visible,
this.saveSettingsButton.enabled,
this.discardSettingsChangesButton.visible,
this.discardSettingsChangesButton.enabled
this.discardSettingsChangesButton.enabled,
])
).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();

View File

@@ -1,226 +1,221 @@
<div
class="tab-pane active tabdocuments flexContainer"
data-bind="
setTemplateReady: true,
attr:{
id: tabId
},
visible: isActive"
role="tabpanel"
>
<!-- ko if: false -->
<!-- Messagebox Ok Cancel- Start -->
<div class="messagebox-background">
<div class="messagebox">
<h2 class="messagebox-title">Title</h2>
<div class="messagebox-text" tabindex="0">Text</div>
<div class="messagebox-buttons">
<div class="messagebox-buttons-container">
<button value="ok" class="messagebox-button-primary">Ok</button>
<button value="cancel" class="messagebox-button-default">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Messagebox OK Cancel - End -->
<!-- /ko -->
<!-- Filter - Start -->
<div class="filterdivs" data-bind="visible: isFilterCreated">
<!-- Read-only Filter - Start -->
<div class="filterDocCollapsed" data-bind="visible: !isFilterExpanded() && !isPreferredApiMongoDB">
<span class="selectQuery">SELECT * FROM c</span>
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
<button class="filterbtnstyle queryButton" data-bind="click: onShowFilterClick">
Edit Filter
</button>
</div>
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && isPreferredApiMongoDB"
>
<span
class="selectQuery"
data-bind="
visible: appliedFilter().length > 0"
>Filter :
</span>
<span
class="noFilterApplied"
data-bind="
visible: !appliedFilter().length > 0"
>No filter applied</span
>
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<!-- Read-only Filter - End -->
<!-- Editable Filter - start -->
<div
class="filterDocExpanded"
data-bind="
visible: isFilterExpanded"
>
<div>
<div class="editFilterContainer">
<span class="filterspan" data-bind="visible: !isPreferredApiMongoDB">
SELECT * FROM c
</span>
<input
type="text"
list="filtersList"
class="querydropdown"
title="Type a query predicate or choose one from the list."
data-bind="
attr:{
placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
},
css: { placeholderVisible: filterContent().length === 0 },
textInput: filterContent"
/>
<datalist
id="filtersList"
data-bind="
foreach: lastFilterContents"
>
<option
data-bind="
value: $data"
>
</option>
</datalist>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
click: refreshDocumentsGrid,
enable: applyFilterButton.enabled"
aria-label="Apply filter"
tabindex="0"
>
Apply Filter
</button>
</span>
<span
class="filterclose"
role="button"
aria-label="close filter"
tabindex="0"
data-bind="
click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"
>
<img src="/close-black.svg" style="height: 14px; width: 14px;" alt="Hide filter" />
</span>
</div>
</div>
</div>
<!-- Editable Filter - End -->
</div>
<!-- Filter - End -->
<!-- Ids and Editor - Start -->
<div class="documentsTabGridAndEditor">
<div class="documentsContainerWithSplitter" , data-bind="attr: { id: documentContentsContainerId }">
<div class="flexContainer">
<!-- Document Ids - Start -->
<div
class="documentsGridHeaderContainer tabdocuments scrollable"
data-bind="
attr: {
id: documentContentsGridId,
tabindex: documentIds().length <= 0 ? -1 : 0
},
style: { height: dataContentsGridScrollHeight },
event: { keydown: accessibleDocumentList.onKeyDown }"
>
<table id="tabsTable" class="table table-hover can-select dataTable">
<thead id="theadcontent">
<tr>
<th class="documentsGridHeader" data-bind="text: idHeader" tabindex="0"></th>
<!-- ko if: showPartitionKey -->
<th
class="documentsGridHeader documentsGridPartition evenlySpacedHeader"
data-bind="
attr: {
title: partitionKeyPropertyHeader
},
text: partitionKeyPropertyHeader"
tabindex="0"
></th>
<!-- /ko -->
<th
class="refreshColHeader"
role="button"
aria-label="Refresh documents"
data-bind="event: { keydown: onRefreshButtonKeyDown }"
>
<img
class="refreshcol"
src="/refresh-cosmos.svg"
data-bind="click: refreshDocumentsGrid"
alt="Refresh documents"
tabindex="0"
/>
</th>
</tr>
</thead>
<tbody id="tbodycontent">
<!-- ko foreach: documentIds -->
<tr
class="pointer accessibleListElement"
data-bind="
click: $data.click,
css: {
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
}"
tabindex="0"
>
<td class="tabdocumentsGridElement"><a data-bind="text: $data.id, attr: { title: $data.id }"></a></td>
<!-- ko if: $data.partitionKeyProperty -->
<td class="tabdocumentsGridElement" colspan="2">
<a
data-bind="text: $data.stringPartitionKeyValue, attr: { title: $data.stringPartitionKeyValue }"
></a>
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
</div>
<div class="loadMore">
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
>Load more</a
>
</div>
<!-- Document Ids - End -->
<!-- Splitter -->
</div>
<div class="splitter ui-resizable-handle ui-resizable-e colResizePointer" id="h_splitter2"></div>
</div>
<div class="documentWaterMark" data-bind="visible: !shouldShowEditor()">
<p><img src="/DocumentWaterMark.svg" alt="Document WaterMark" /></p>
<p class="documentWaterMarkText">Create new or work with existing document(s).</p>
</div>
<!-- Editor - Start -->
<json-editor
class="editorDivContent"
data-bind="visible: shouldShowEditor, css: { mongoDocumentEditor: isPreferredApiMongoDB }"
params="{content: initialDocumentContent, isReadOnly: false,lineNumbers: 'on',ariaLabel: 'Document editor',
updatedContent: selectedDocumentContent}"
></json-editor>
<!-- Editor - End -->
</div>
<!-- Ids and Editor - End -->
</div>
<div
class="tab-pane active tabdocuments flexContainer"
data-bind="
setTemplateReady: true,
attr:{
id: tabId
},
visible: isActive"
role="tabpanel"
>
<!-- ko if: false -->
<!-- Messagebox Ok Cancel- Start -->
<div class="messagebox-background">
<div class="messagebox">
<h2 class="messagebox-title">Title</h2>
<div class="messagebox-text" tabindex="0">Text</div>
<div class="messagebox-buttons">
<div class="messagebox-buttons-container">
<button value="ok" class="messagebox-button-primary">Ok</button>
<button value="cancel" class="messagebox-button-default">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Messagebox OK Cancel - End -->
<!-- /ko -->
<!-- Filter - Start -->
<div class="filterdivs" data-bind="visible: isFilterCreated">
<!-- Read-only Filter - Start -->
<div class="filterDocCollapsed" data-bind="visible: !isFilterExpanded() && !isPreferredApiMongoDB">
<span class="selectQuery">SELECT * FROM c</span>
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
<button class="filterbtnstyle queryButton" data-bind="click: onShowFilterClick">Edit Filter</button>
</div>
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && isPreferredApiMongoDB"
>
<span
class="selectQuery"
data-bind="
visible: appliedFilter().length > 0"
>Filter :
</span>
<span
class="noFilterApplied"
data-bind="
visible: !appliedFilter().length > 0"
>No filter applied</span
>
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<!-- Read-only Filter - End -->
<!-- Editable Filter - start -->
<div
class="filterDocExpanded"
data-bind="
visible: isFilterExpanded"
>
<div>
<div class="editFilterContainer">
<span class="filterspan" data-bind="visible: !isPreferredApiMongoDB"> SELECT * FROM c </span>
<input
type="text"
list="filtersList"
class="querydropdown"
title="Type a query predicate or choose one from the list."
data-bind="
attr:{
placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
},
css: { placeholderVisible: filterContent().length === 0 },
textInput: filterContent"
/>
<datalist
id="filtersList"
data-bind="
foreach: lastFilterContents"
>
<option
data-bind="
value: $data"
></option>
</datalist>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
click: refreshDocumentsGrid,
enable: applyFilterButton.enabled"
aria-label="Apply filter"
tabindex="0"
>
Apply Filter
</button>
</span>
<span
class="filterclose"
role="button"
aria-label="close filter"
tabindex="0"
data-bind="
click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"
>
<img src="/close-black.svg" style="height: 14px; width: 14px" alt="Hide filter" />
</span>
</div>
</div>
</div>
<!-- Editable Filter - End -->
</div>
<!-- Filter - End -->
<!-- Ids and Editor - Start -->
<div class="documentsTabGridAndEditor">
<div class="documentsContainerWithSplitter" , data-bind="attr: { id: documentContentsContainerId }">
<div class="flexContainer">
<!-- Document Ids - Start -->
<div
class="documentsGridHeaderContainer tabdocuments scrollable"
data-bind="
attr: {
id: documentContentsGridId,
tabindex: documentIds().length <= 0 ? -1 : 0
},
style: { height: dataContentsGridScrollHeight },
event: { keydown: accessibleDocumentList.onKeyDown }"
>
<table id="tabsTable" class="table table-hover can-select dataTable">
<thead id="theadcontent">
<tr>
<th class="documentsGridHeader" data-bind="text: idHeader" tabindex="0"></th>
<!-- ko if: showPartitionKey -->
<th
class="documentsGridHeader documentsGridPartition evenlySpacedHeader"
data-bind="
attr: {
title: partitionKeyPropertyHeader
},
text: partitionKeyPropertyHeader"
tabindex="0"
></th>
<!-- /ko -->
<th
class="refreshColHeader"
role="button"
aria-label="Refresh documents"
data-bind="event: { keydown: onRefreshButtonKeyDown }"
>
<img
class="refreshcol"
src="/refresh-cosmos.svg"
data-bind="click: refreshDocumentsGrid"
alt="Refresh documents"
tabindex="0"
/>
</th>
</tr>
</thead>
<tbody id="tbodycontent">
<!-- ko foreach: documentIds -->
<tr
class="pointer accessibleListElement"
data-bind="
click: $data.click,
css: {
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
}"
tabindex="0"
>
<td class="tabdocumentsGridElement"><a data-bind="text: $data.id, attr: { title: $data.id }"></a></td>
<!-- ko if: $data.partitionKeyProperty -->
<td class="tabdocumentsGridElement" colspan="2">
<a
data-bind="text: $data.stringPartitionKeyValue, attr: { title: $data.stringPartitionKeyValue }"
></a>
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
</div>
<div class="loadMore">
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
>Load more</a
>
</div>
<!-- Document Ids - End -->
<!-- Splitter -->
</div>
<div class="splitter ui-resizable-handle ui-resizable-e colResizePointer" id="h_splitter2"></div>
</div>
<div class="documentWaterMark" data-bind="visible: !shouldShowEditor()">
<p><img src="/DocumentWaterMark.svg" alt="Document WaterMark" /></p>
<p class="documentWaterMarkText">Create new or work with existing document(s).</p>
</div>
<!-- Editor - Start -->
<json-editor
class="editorDivContent"
data-bind="visible: shouldShowEditor, css: { mongoDocumentEditor: isPreferredApiMongoDB }"
params="{content: initialDocumentContent, isReadOnly: false,lineNumbers: 'on',ariaLabel: 'Document editor',
updatedContent: selectedDocumentContent}"
></json-editor>
<!-- Editor - End -->
</div>
<!-- Ids and Editor - End -->
</div>

View File

@@ -1,168 +1,168 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import DocumentsTab from "./DocumentsTab";
import Explorer from "../Explorer";
import DocumentId from "../Tree/DocumentId";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
describe("Documents tab", () => {
describe("buildQuery", () => {
it("should generate the right select query for SQL API", () => {
const documentsTab = new DocumentsTab({
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.buildQuery("")).toContain("select");
});
});
describe("showPartitionKey", () => {
const explorer = new Explorer();
const mongoExplorer = new Explorer();
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo")
},
container: explorer
});
const collectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo")
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: true
},
container: explorer
});
const collectionWithNonSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo")
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: false
},
container: explorer
});
const mongoCollectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo")
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: true
},
container: mongoExplorer
});
it("should be false for null or undefined collection", () => {
const documentsTab = new DocumentsTab({
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be false for null or undefined partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithoutPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be true for non-Mongo accounts with system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(true);
});
it("should be false for Mongo accounts with system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: mongoCollectionWithSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be true for non-system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithNonSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(true);
});
});
});
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import DocumentsTab from "./DocumentsTab";
import Explorer from "../Explorer";
import DocumentId from "../Tree/DocumentId";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
describe("Documents tab", () => {
describe("buildQuery", () => {
it("should generate the right select query for SQL API", () => {
const documentsTab = new DocumentsTab({
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.buildQuery("")).toContain("select");
});
});
describe("showPartitionKey", () => {
const explorer = new Explorer();
const mongoExplorer = new Explorer();
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo"),
},
container: explorer,
});
const collectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo"),
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: true,
},
container: explorer,
});
const collectionWithNonSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo"),
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: false,
},
container: explorer,
});
const mongoCollectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
id: ko.observable<string>("foo"),
database: {
id: ko.observable<string>("foo"),
},
partitionKey: {
paths: ["/foo"],
kind: "Hash",
version: 2,
systemKey: true,
},
container: mongoExplorer,
});
it("should be false for null or undefined collection", () => {
const documentsTab = new DocumentsTab({
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be false for null or undefined partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithoutPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be true for non-Mongo accounts with system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(true);
});
it("should be false for Mongo accounts with system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: mongoCollectionWithSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be true for non-system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithNonSystemPartitionKey,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(true);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@ export default class GalleryTab extends TabsBase {
isFavorite: options.isFavorite,
selectedTab: GalleryViewerTab.OfficialSamples,
sortBy: SortBy.MostViewed,
searchText: undefined
searchText: undefined,
};
this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(props);

View File

@@ -1,241 +1,241 @@
import * as ko from "knockout";
import * as Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer";
import NewVertexIcon from "../../../images/NewVertex.svg";
import StyleIcon from "../../../images/Style.svg";
import GraphStylingPane from "../Panes/GraphStylingPane";
import NewVertexPane from "../Panes/NewVertexPane";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
export interface GraphIconMap {
[key: string]: { data: string; format: string };
}
export interface GraphConfig {
nodeColor: ko.Observable<string>;
nodeColorKey: ko.Observable<string>; // map property to node color. Takes precedence over nodeColor unless null
linkColor: ko.Observable<string>;
showNeighborType: ko.Observable<ViewModels.NeighborType>;
nodeCaption: ko.Observable<string>;
nodeSize: ko.Observable<number>;
linkWidth: ko.Observable<number>;
nodeIconKey: ko.Observable<string>;
iconsMap: ko.Observable<GraphIconMap>;
}
interface GraphTabOptions extends ViewModels.TabOptions {
account: DatabaseAccount;
masterKey: string;
collectionId: string;
databaseId: string;
collectionPartitionKeyProperty: string;
}
export default class GraphTab extends TabsBase {
// Graph default configuration
public static readonly DEFAULT_NODE_CAPTION = "id";
private static readonly LINK_COLOR = "#aaa";
private static readonly NODE_SIZE = 10;
private static readonly NODE_COLOR = "orange";
private static readonly LINK_WIDTH = 1;
private graphExplorerAdapter: GraphExplorerAdapter;
private isNewVertexDisabled: ko.Observable<boolean>;
private isPropertyEditing: ko.Observable<boolean>;
private isGraphDisplayed: ko.Observable<boolean>;
private graphAccessor: GraphAccessor;
private graphConfig: GraphConfig;
private graphConfigUiData: ViewModels.GraphConfigUiData;
private isFilterQueryLoading: ko.Observable<boolean>;
private isValidQuery: ko.Observable<boolean>;
private newVertexPane: NewVertexPane;
private graphStylingPane: GraphStylingPane;
private collectionPartitionKeyProperty: string;
constructor(options: GraphTabOptions) {
super(options);
this.newVertexPane = options.collection && options.collection.container.newVertexPane;
this.graphStylingPane = options.collection && options.collection.container.graphStylingPane;
this.collectionPartitionKeyProperty = options.collectionPartitionKeyProperty;
this.isNewVertexDisabled = ko.observable(false);
this.isPropertyEditing = ko.observable(false);
this.isGraphDisplayed = ko.observable(false);
this.graphAccessor = null;
this.graphConfig = GraphTab.createGraphConfig();
// TODO Merge this with this.graphConfig
this.graphConfigUiData = GraphTab.createGraphConfigUiData(this.graphConfig);
this.graphExplorerAdapter = new GraphExplorerAdapter({
onGraphAccessorCreated: (instance: GraphAccessor): void => {
this.graphAccessor = instance;
},
onIsNewVertexDisabledChange: (isDisabled: boolean): void => {
this.isNewVertexDisabled(isDisabled);
this.updateNavbarWithTabsButtons();
},
onIsPropertyEditing: (isEditing: boolean) => {
this.isPropertyEditing(isEditing);
this.updateNavbarWithTabsButtons();
},
onIsGraphDisplayed: (isDisplayed: boolean) => {
this.isGraphDisplayed(isDisplayed);
this.updateNavbarWithTabsButtons();
},
onResetDefaultGraphConfigValues: () => this.setDefaultGraphConfigValues(),
graphConfig: this.graphConfig,
graphConfigUiData: this.graphConfigUiData,
onIsFilterQueryLoading: (isFilterQueryLoading: boolean): void => this.isFilterQueryLoading(isFilterQueryLoading),
onIsValidQuery: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
collectionPartitionKeyProperty: options.collectionPartitionKeyProperty,
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
databaseId: options.databaseId,
collectionId: options.collectionId,
masterKey: options.masterKey,
onLoadStartKey: options.onLoadStartKey,
onLoadStartKeyChange: (onLoadStartKey: number): void => {
if (onLoadStartKey == null) {
this.onLoadStartKey = null;
}
},
resourceId: options.account.id
});
this.isFilterQueryLoading = ko.observable(false);
this.isValidQuery = ko.observable(true);
}
public static getGremlinEndpoint(account: DatabaseAccount): string {
return account.properties.gremlinEndpoint
? GraphTab.sanitizeHost(account.properties.gremlinEndpoint)
: `${account.name}.graphs.azure.com:443/`;
}
public onTabClick(): void {
super.onTabClick();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
}
/**
* Removing leading http|https and remove trailing /
* @param url
* @return
*/
private static sanitizeHost(url: string): string {
if (!url) {
return url;
}
return url.replace(/^(http|https):\/\//, "").replace(/\/$/, "");
}
/* Command bar */
private showNewVertexEditor(): void {
this.newVertexPane.open();
this.newVertexPane.setPartitionKeyProperty(this.collectionPartitionKeyProperty);
// TODO Must update GraphExplorer properties
this.newVertexPane.subscribeOnSubmitCreate((result: ViewModels.NewVertexData) => {
this.newVertexPane.formErrors(null);
this.newVertexPane.formErrorsDetails(null);
this.graphAccessor.addVertex(result).then(
() => {
this.newVertexPane.cancel();
},
(error: GraphExplorerError) => {
this.newVertexPane.formErrors(error.title);
if (!!error.details) {
this.newVertexPane.formErrorsDetails(error.details);
}
}
);
});
}
public openStyling() {
this.setDefaultGraphConfigValues();
// Update the styling pane with this instance
this.graphStylingPane.setData(this.graphConfigUiData);
this.graphStylingPane.open();
}
public static createGraphConfig(): GraphConfig {
return {
nodeColor: ko.observable(GraphTab.NODE_COLOR),
nodeColorKey: ko.observable(null),
linkColor: ko.observable(GraphTab.LINK_COLOR),
showNeighborType: ko.observable(ViewModels.NeighborType.TARGETS_ONLY),
nodeCaption: ko.observable(GraphTab.DEFAULT_NODE_CAPTION),
nodeSize: ko.observable(GraphTab.NODE_SIZE),
linkWidth: ko.observable(GraphTab.LINK_WIDTH),
nodeIconKey: ko.observable(null),
iconsMap: ko.observable({})
};
}
public static createGraphConfigUiData(graphConfig: GraphConfig): ViewModels.GraphConfigUiData {
return {
showNeighborType: ko.observable(graphConfig.showNeighborType()),
nodeProperties: ko.observableArray([]),
nodePropertiesWithNone: ko.observableArray([]),
nodeCaptionChoice: ko.observable(graphConfig.nodeCaption()),
nodeColorKeyChoice: ko.observable(graphConfig.nodeColorKey()),
nodeIconChoice: ko.observable(graphConfig.nodeIconKey()),
nodeIconSet: ko.observable(null)
};
}
/**
* Make sure graph config values are not null
*/
private setDefaultGraphConfigValues() {
// Assign default values if null
if (this.graphConfigUiData.nodeCaptionChoice() === null && this.graphConfigUiData.nodeProperties().length > 1) {
this.graphConfigUiData.nodeCaptionChoice(this.graphConfigUiData.nodeProperties()[0]);
}
if (
this.graphConfigUiData.nodeColorKeyChoice() === null &&
this.graphConfigUiData.nodePropertiesWithNone().length > 1
) {
this.graphConfigUiData.nodeColorKeyChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]);
}
if (
this.graphConfigUiData.nodeIconChoice() === null &&
this.graphConfigUiData.nodePropertiesWithNone().length > 1
) {
this.graphConfigUiData.nodeIconChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]);
}
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const label = "New Vertex";
const buttons: CommandButtonComponentProps[] = [
{
iconSrc: NewVertexIcon,
iconAlt: label,
onCommandClick: () => this.showNewVertexEditor(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isNewVertexDisabled()
}
];
buttons.push();
if (this.isGraphDisplayed()) {
const label = "Style";
buttons.push({
iconSrc: StyleIcon,
iconAlt: label,
onCommandClick: () => this.openStyling(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isPropertyEditing()
});
}
return buttons;
}
protected buildCommandBarOptions(): void {
ko.computed(() => ko.toJSON([this.isNewVertexDisabled])).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();
}
}
import * as ko from "knockout";
import * as Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer";
import NewVertexIcon from "../../../images/NewVertex.svg";
import StyleIcon from "../../../images/Style.svg";
import GraphStylingPane from "../Panes/GraphStylingPane";
import NewVertexPane from "../Panes/NewVertexPane";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
export interface GraphIconMap {
[key: string]: { data: string; format: string };
}
export interface GraphConfig {
nodeColor: ko.Observable<string>;
nodeColorKey: ko.Observable<string>; // map property to node color. Takes precedence over nodeColor unless null
linkColor: ko.Observable<string>;
showNeighborType: ko.Observable<ViewModels.NeighborType>;
nodeCaption: ko.Observable<string>;
nodeSize: ko.Observable<number>;
linkWidth: ko.Observable<number>;
nodeIconKey: ko.Observable<string>;
iconsMap: ko.Observable<GraphIconMap>;
}
interface GraphTabOptions extends ViewModels.TabOptions {
account: DatabaseAccount;
masterKey: string;
collectionId: string;
databaseId: string;
collectionPartitionKeyProperty: string;
}
export default class GraphTab extends TabsBase {
// Graph default configuration
public static readonly DEFAULT_NODE_CAPTION = "id";
private static readonly LINK_COLOR = "#aaa";
private static readonly NODE_SIZE = 10;
private static readonly NODE_COLOR = "orange";
private static readonly LINK_WIDTH = 1;
private graphExplorerAdapter: GraphExplorerAdapter;
private isNewVertexDisabled: ko.Observable<boolean>;
private isPropertyEditing: ko.Observable<boolean>;
private isGraphDisplayed: ko.Observable<boolean>;
private graphAccessor: GraphAccessor;
private graphConfig: GraphConfig;
private graphConfigUiData: ViewModels.GraphConfigUiData;
private isFilterQueryLoading: ko.Observable<boolean>;
private isValidQuery: ko.Observable<boolean>;
private newVertexPane: NewVertexPane;
private graphStylingPane: GraphStylingPane;
private collectionPartitionKeyProperty: string;
constructor(options: GraphTabOptions) {
super(options);
this.newVertexPane = options.collection && options.collection.container.newVertexPane;
this.graphStylingPane = options.collection && options.collection.container.graphStylingPane;
this.collectionPartitionKeyProperty = options.collectionPartitionKeyProperty;
this.isNewVertexDisabled = ko.observable(false);
this.isPropertyEditing = ko.observable(false);
this.isGraphDisplayed = ko.observable(false);
this.graphAccessor = null;
this.graphConfig = GraphTab.createGraphConfig();
// TODO Merge this with this.graphConfig
this.graphConfigUiData = GraphTab.createGraphConfigUiData(this.graphConfig);
this.graphExplorerAdapter = new GraphExplorerAdapter({
onGraphAccessorCreated: (instance: GraphAccessor): void => {
this.graphAccessor = instance;
},
onIsNewVertexDisabledChange: (isDisabled: boolean): void => {
this.isNewVertexDisabled(isDisabled);
this.updateNavbarWithTabsButtons();
},
onIsPropertyEditing: (isEditing: boolean) => {
this.isPropertyEditing(isEditing);
this.updateNavbarWithTabsButtons();
},
onIsGraphDisplayed: (isDisplayed: boolean) => {
this.isGraphDisplayed(isDisplayed);
this.updateNavbarWithTabsButtons();
},
onResetDefaultGraphConfigValues: () => this.setDefaultGraphConfigValues(),
graphConfig: this.graphConfig,
graphConfigUiData: this.graphConfigUiData,
onIsFilterQueryLoading: (isFilterQueryLoading: boolean): void => this.isFilterQueryLoading(isFilterQueryLoading),
onIsValidQuery: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
collectionPartitionKeyProperty: options.collectionPartitionKeyProperty,
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
databaseId: options.databaseId,
collectionId: options.collectionId,
masterKey: options.masterKey,
onLoadStartKey: options.onLoadStartKey,
onLoadStartKeyChange: (onLoadStartKey: number): void => {
if (onLoadStartKey == null) {
this.onLoadStartKey = null;
}
},
resourceId: options.account.id,
});
this.isFilterQueryLoading = ko.observable(false);
this.isValidQuery = ko.observable(true);
}
public static getGremlinEndpoint(account: DatabaseAccount): string {
return account.properties.gremlinEndpoint
? GraphTab.sanitizeHost(account.properties.gremlinEndpoint)
: `${account.name}.graphs.azure.com:443/`;
}
public onTabClick(): void {
super.onTabClick();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
}
/**
* Removing leading http|https and remove trailing /
* @param url
* @return
*/
private static sanitizeHost(url: string): string {
if (!url) {
return url;
}
return url.replace(/^(http|https):\/\//, "").replace(/\/$/, "");
}
/* Command bar */
private showNewVertexEditor(): void {
this.newVertexPane.open();
this.newVertexPane.setPartitionKeyProperty(this.collectionPartitionKeyProperty);
// TODO Must update GraphExplorer properties
this.newVertexPane.subscribeOnSubmitCreate((result: ViewModels.NewVertexData) => {
this.newVertexPane.formErrors(null);
this.newVertexPane.formErrorsDetails(null);
this.graphAccessor.addVertex(result).then(
() => {
this.newVertexPane.cancel();
},
(error: GraphExplorerError) => {
this.newVertexPane.formErrors(error.title);
if (!!error.details) {
this.newVertexPane.formErrorsDetails(error.details);
}
}
);
});
}
public openStyling() {
this.setDefaultGraphConfigValues();
// Update the styling pane with this instance
this.graphStylingPane.setData(this.graphConfigUiData);
this.graphStylingPane.open();
}
public static createGraphConfig(): GraphConfig {
return {
nodeColor: ko.observable(GraphTab.NODE_COLOR),
nodeColorKey: ko.observable(null),
linkColor: ko.observable(GraphTab.LINK_COLOR),
showNeighborType: ko.observable(ViewModels.NeighborType.TARGETS_ONLY),
nodeCaption: ko.observable(GraphTab.DEFAULT_NODE_CAPTION),
nodeSize: ko.observable(GraphTab.NODE_SIZE),
linkWidth: ko.observable(GraphTab.LINK_WIDTH),
nodeIconKey: ko.observable(null),
iconsMap: ko.observable({}),
};
}
public static createGraphConfigUiData(graphConfig: GraphConfig): ViewModels.GraphConfigUiData {
return {
showNeighborType: ko.observable(graphConfig.showNeighborType()),
nodeProperties: ko.observableArray([]),
nodePropertiesWithNone: ko.observableArray([]),
nodeCaptionChoice: ko.observable(graphConfig.nodeCaption()),
nodeColorKeyChoice: ko.observable(graphConfig.nodeColorKey()),
nodeIconChoice: ko.observable(graphConfig.nodeIconKey()),
nodeIconSet: ko.observable(null),
};
}
/**
* Make sure graph config values are not null
*/
private setDefaultGraphConfigValues() {
// Assign default values if null
if (this.graphConfigUiData.nodeCaptionChoice() === null && this.graphConfigUiData.nodeProperties().length > 1) {
this.graphConfigUiData.nodeCaptionChoice(this.graphConfigUiData.nodeProperties()[0]);
}
if (
this.graphConfigUiData.nodeColorKeyChoice() === null &&
this.graphConfigUiData.nodePropertiesWithNone().length > 1
) {
this.graphConfigUiData.nodeColorKeyChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]);
}
if (
this.graphConfigUiData.nodeIconChoice() === null &&
this.graphConfigUiData.nodePropertiesWithNone().length > 1
) {
this.graphConfigUiData.nodeIconChoice(this.graphConfigUiData.nodePropertiesWithNone()[0]);
}
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const label = "New Vertex";
const buttons: CommandButtonComponentProps[] = [
{
iconSrc: NewVertexIcon,
iconAlt: label,
onCommandClick: () => this.showNewVertexEditor(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isNewVertexDisabled(),
},
];
buttons.push();
if (this.isGraphDisplayed()) {
const label = "Style";
buttons.push({
iconSrc: StyleIcon,
iconAlt: label,
onCommandClick: () => this.openStyling(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isPropertyEditing(),
});
}
return buttons;
}
protected buildCommandBarOptions(): void {
ko.computed(() => ko.toJSON([this.isNewVertexDisabled])).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();
}
}

View File

@@ -1,417 +1,426 @@
<div
class="tab-pane active tabdocuments flexContainer"
data-bind="
attr:{
id: tabId
},
visible: isActive"
role="tabpanel"
>
<!-- Documents Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton documentMenu">
<!-- New Document - Start -->
<span
class="commandButton"
data-bind="
click: onNewDocumentClick,
visible: newDocumentButton.visible() && newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc.svg" />New Document
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: newDocumentButton.visible() && !newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc-disabled.svg" />New Document
</span>
<!-- New Document - End -->
<!-- New Query - Start -->
<span
class="commandButton"
data-bind="
visible: !$root.isPreferredApiMongoDB,
click: collection.onNewQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New SQL Query
</span>
<span
class="commandButton"
data-bind="
visible: $root.isPreferredApiMongoDB,
click: collection.onNewMongoQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New Query
</span>
<!-- New Query - End -->
<!-- Save New - Start -->
<span
class="commandButton"
data-bind="
click: onSaveNewDocumentClick,
visible: saveNewDocumentButton.visible() && saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Save
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveNewDocumentButton.visible() && !saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Save
</span>
<!-- Save New - End -->
<!-- Discard New - Start -->
<span
class="commandButton"
data-bind="
click: onRevertNewDocumentClick,
visible: discardNewDocumentChangesButton.visible() && discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardNewDocumentChangesButton.visible() && !discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard New - End -->
<!-- Save Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onSaveExisitingDocumentClick,
visible: saveExisitingDocumentButton.visible() && saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Update
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveExisitingDocumentButton.visible() && !saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Update
</span>
<!-- Save Exisiting - End -->
<!-- Discard Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onRevertExisitingDocumentClick,
visible: discardExisitingDocumentChangesButton.visible() && discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardExisitingDocumentChangesButton.visible() && !discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard Exisiting - End -->
<!-- Delete Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onDeleteExisitingDocumentClick,
visible: deleteExisitingDocumentButton.visible() && deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete.svg" />Delete
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: deleteExisitingDocumentButton.visible() && !deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete-disabled.svg" />Delete
</span>
<!-- Delete Exisiting - End -->
</div>
</div>
<script type="text/html" id="toolbarItemTemplate">
<!-- ko if: type === "action" -->
<div class="toolbar-group" data-bind="visible: visible">
<button class="toolbar-group-button" data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "toggle" -->
<div class="toolbar-group" data-bind="visible: visible">
<button class="toolbar-group-button toggle-button" data-bind="hasFocus: focused, attr: {id: id, title: title}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon" data-bind="css: { 'toggle-checked': checked }">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "dropdown" -->
<div class="toolbar-group" data-bind="visible: visible">
<div class="dropdown" data-bind="attr: {id: (id + '-dropdown')}">
<button role="menu" class="toolbar-group-button" data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled">
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
</div>
<!-- /ko -->
<!-- ko if: type === "separator" -->
<div class="toolbar-group vertical-separator" data-bind="visible: visible"></div>
<!-- /ko -->
</script>
<!-- Documents Tab Command Bar - End -->
<!-- ko if: false -->
<!-- Messagebox Ok Cancel- Start -->
<div class="messagebox-background">
<div class="messagebox">
<h2 class="messagebox-title">Title</h2>
<div class="messagebox-text" tabindex="0">Text</div>
<div class="messagebox-buttons">
<div class="messagebox-buttons-container">
<button value="ok" class="messagebox-button-primary">Ok</button>
<button value="cancel" class="messagebox-button-default">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Messagebox OK Cancel - End -->
<!-- /ko -->
<!-- Filter - Start -->
<div class="filterdivs">
<!-- Read-only Filter - Start -->
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && $root.isPreferredApiMongoDB()"
>
<span
data-bind="
visible: appliedFilter().length > 0"
>Filter :
</span>
<span
data-bind="
visible: !appliedFilter().length > 0"
>No filter applied</span
>
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<!-- Read-only Filter - End -->
<!-- Editable Filter - start -->
<div
class="filterDocExpanded"
data-bind="
visible: isFilterExpanded"
>
<div>
<div>
<span
class="filterspan"
data-bind="
visible: !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
</span>
<input
type="text"
list="filtersList"
class="querydropdown"
title="Type a query predicate or choose one from the list."
data-bind="
attr:{
placeholder:$root.isPreferredApiMongoDB()?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
},
textInput: filterContent"
/>
<datalist
id="filtersList"
data-bind="
foreach: lastFilterContents"
>
<option
data-bind="
value: $data"
>
</option>
</datalist>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
click: refreshDocumentsGrid,
enable: applyFilterButton.enabled"
>
Apply Filter
</button>
</span>
<span
class="filterclose"
data-bind="
click: onHideFilterClick"
>
<img src="/close-black.svg" style="height: 14px; width: 14px;" />
</span>
</div>
</div>
</div>
<!-- Editable Filter - End -->
</div>
<!-- Filter - End -->
<!-- Ids and Editor - Start -->
<div>
<div class="row rowoverride documentsTabGridAndEditor">
<div class="documentsGridHeaderContainer documentsContainer">
<!-- ko if: !partitionKeyProperty -->
<table>
<tbody>
<tr>
<!-- ko if: $root.isPreferredApiMongoDB -->
<td class="documentsGridHeader">_id</td>
<!-- /ko -->
<!-- ko if: !$root.isPreferredApiMongoDB() -->
<td class="documentsGridHeader">id</td>
<!-- /ko -->
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
<!-- ko if: partitionKeyProperty -->
<table>
<tbody>
<tr>
<td class="documentsGridHeader fixedWidthHeader">_id</td>
<td
class="documentsGridHeader documentsGridPartition"
data-bind="
attr: {
title: partitionKeyPropertyHeader
},
text: partitionKeyPropertyHeader"
></td>
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
</div>
<!-- Document Ids - Start -->
<div
class="tabdocuments scrollable"
data-bind="
attr: {
id: documentContentsGridId,
tabindex: collection.documentIds().length <= 0 ? -1 : 0
},
style: { height: dataContentsGridScrollHeight },
event: { keydown: accessibleDocumentList.onKeyDown }"
>
<table class="table can-select table-hover dataTable">
<tbody id="tbodycontent">
<!-- ko foreach: documentIds -->
<tr
class="pointer accessibleListElement"
data-bind="
click: $data.click,
css: {
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
}"
>
<td style="width:82px;">
<a
data-bind="
text: $data.id, attr: { title: $data.id }"
></a>
</td>
<!-- ko if: $data.partitionKeyProperty -->
<td><a data-bind="text: $data.partitionKeyValue, attr: { title: $data.partitionKeyValue }"></a></td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
<div class="loadMore">
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
>Load more</a
>
</div>
</div>
<!-- Document Ids - End -->
<!-- Editor - Start -->
<div id="divcontent" style="float: left; width: calc(100% - 200px);">
<div
style="height:100vh;border-left :1px solid #d6d7d8; float: initial; display: flow-root!important;"
data-bind="
attr: {
id: documentEditorId
},
css: {
mongoDocumentEditor:$root.isPreferredApiMongoDB()
}"
></div>
</div>
<!-- Editor - End -->
</div>
</div>
<!-- Ids and Editor - End -->
</div>
<div
class="tab-pane active tabdocuments flexContainer"
data-bind="
attr:{
id: tabId
},
visible: isActive"
role="tabpanel"
>
<!-- Documents Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton documentMenu">
<!-- New Document - Start -->
<span
class="commandButton"
data-bind="
click: onNewDocumentClick,
visible: newDocumentButton.visible() && newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc.svg" />New Document
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: newDocumentButton.visible() && !newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc-disabled.svg" />New Document
</span>
<!-- New Document - End -->
<!-- New Query - Start -->
<span
class="commandButton"
data-bind="
visible: !$root.isPreferredApiMongoDB,
click: collection.onNewQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New SQL Query
</span>
<span
class="commandButton"
data-bind="
visible: $root.isPreferredApiMongoDB,
click: collection.onNewMongoQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New Query
</span>
<!-- New Query - End -->
<!-- Save New - Start -->
<span
class="commandButton"
data-bind="
click: onSaveNewDocumentClick,
visible: saveNewDocumentButton.visible() && saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Save
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveNewDocumentButton.visible() && !saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Save
</span>
<!-- Save New - End -->
<!-- Discard New - Start -->
<span
class="commandButton"
data-bind="
click: onRevertNewDocumentClick,
visible: discardNewDocumentChangesButton.visible() && discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardNewDocumentChangesButton.visible() && !discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard New - End -->
<!-- Save Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onSaveExisitingDocumentClick,
visible: saveExisitingDocumentButton.visible() && saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Update
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveExisitingDocumentButton.visible() && !saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Update
</span>
<!-- Save Exisiting - End -->
<!-- Discard Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onRevertExisitingDocumentClick,
visible: discardExisitingDocumentChangesButton.visible() && discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardExisitingDocumentChangesButton.visible() && !discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard Exisiting - End -->
<!-- Delete Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onDeleteExisitingDocumentClick,
visible: deleteExisitingDocumentButton.visible() && deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete.svg" />Delete
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: deleteExisitingDocumentButton.visible() && !deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete-disabled.svg" />Delete
</span>
<!-- Delete Exisiting - End -->
</div>
</div>
<script type="text/html" id="toolbarItemTemplate">
<!-- ko if: type === "action" -->
<div class="toolbar-group" data-bind="visible: visible">
<button
class="toolbar-group-button"
data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "toggle" -->
<div class="toolbar-group" data-bind="visible: visible">
<button
class="toolbar-group-button toggle-button"
data-bind="hasFocus: focused, attr: {id: id, title: title}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon" data-bind="css: { 'toggle-checked': checked }">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "dropdown" -->
<div class="toolbar-group" data-bind="visible: visible">
<div class="dropdown" data-bind="attr: {id: (id + '-dropdown')}">
<button
role="menu"
class="toolbar-group-button"
data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
</div>
<!-- /ko -->
<!-- ko if: type === "separator" -->
<div class="toolbar-group vertical-separator" data-bind="visible: visible"></div>
<!-- /ko -->
</script>
<!-- Documents Tab Command Bar - End -->
<!-- ko if: false -->
<!-- Messagebox Ok Cancel- Start -->
<div class="messagebox-background">
<div class="messagebox">
<h2 class="messagebox-title">Title</h2>
<div class="messagebox-text" tabindex="0">Text</div>
<div class="messagebox-buttons">
<div class="messagebox-buttons-container">
<button value="ok" class="messagebox-button-primary">Ok</button>
<button value="cancel" class="messagebox-button-default">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Messagebox OK Cancel - End -->
<!-- /ko -->
<!-- Filter - Start -->
<div class="filterdivs">
<!-- Read-only Filter - Start -->
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && $root.isPreferredApiMongoDB()"
>
<span
data-bind="
visible: appliedFilter().length > 0"
>Filter :
</span>
<span
data-bind="
visible: !appliedFilter().length > 0"
>No filter applied</span
>
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<!-- Read-only Filter - End -->
<!-- Editable Filter - start -->
<div
class="filterDocExpanded"
data-bind="
visible: isFilterExpanded"
>
<div>
<div>
<span
class="filterspan"
data-bind="
visible: !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
</span>
<input
type="text"
list="filtersList"
class="querydropdown"
title="Type a query predicate or choose one from the list."
data-bind="
attr:{
placeholder:$root.isPreferredApiMongoDB()?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
},
textInput: filterContent"
/>
<datalist
id="filtersList"
data-bind="
foreach: lastFilterContents"
>
<option
data-bind="
value: $data"
></option>
</datalist>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
click: refreshDocumentsGrid,
enable: applyFilterButton.enabled"
>
Apply Filter
</button>
</span>
<span
class="filterclose"
data-bind="
click: onHideFilterClick"
>
<img src="/close-black.svg" style="height: 14px; width: 14px" />
</span>
</div>
</div>
</div>
<!-- Editable Filter - End -->
</div>
<!-- Filter - End -->
<!-- Ids and Editor - Start -->
<div>
<div class="row rowoverride documentsTabGridAndEditor">
<div class="documentsGridHeaderContainer documentsContainer">
<!-- ko if: !partitionKeyProperty -->
<table>
<tbody>
<tr>
<!-- ko if: $root.isPreferredApiMongoDB -->
<td class="documentsGridHeader">_id</td>
<!-- /ko -->
<!-- ko if: !$root.isPreferredApiMongoDB() -->
<td class="documentsGridHeader">id</td>
<!-- /ko -->
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
<!-- ko if: partitionKeyProperty -->
<table>
<tbody>
<tr>
<td class="documentsGridHeader fixedWidthHeader">_id</td>
<td
class="documentsGridHeader documentsGridPartition"
data-bind="
attr: {
title: partitionKeyPropertyHeader
},
text: partitionKeyPropertyHeader"
></td>
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
</div>
<!-- Document Ids - Start -->
<div
class="tabdocuments scrollable"
data-bind="
attr: {
id: documentContentsGridId,
tabindex: collection.documentIds().length <= 0 ? -1 : 0
},
style: { height: dataContentsGridScrollHeight },
event: { keydown: accessibleDocumentList.onKeyDown }"
>
<table class="table can-select table-hover dataTable">
<tbody id="tbodycontent">
<!-- ko foreach: documentIds -->
<tr
class="pointer accessibleListElement"
data-bind="
click: $data.click,
css: {
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
}"
>
<td style="width: 82px">
<a
data-bind="
text: $data.id, attr: { title: $data.id }"
></a>
</td>
<!-- ko if: $data.partitionKeyProperty -->
<td><a data-bind="text: $data.partitionKeyValue, attr: { title: $data.partitionKeyValue }"></a></td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
<div class="loadMore">
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
>Load more</a
>
</div>
</div>
<!-- Document Ids - End -->
<!-- Editor - Start -->
<div id="divcontent" style="float: left; width: calc(100% - 200px)">
<div
style="height: 100vh; border-left: 1px solid #d6d7d8; float: initial; display: flow-root !important"
data-bind="
attr: {
id: documentEditorId
},
css: {
mongoDocumentEditor:$root.isPreferredApiMongoDB()
}"
></div>
</div>
<!-- Editor - End -->
</div>
</div>
<!-- Ids and Editor - End -->
</div>

View File

@@ -14,7 +14,7 @@ import {
deleteDocument,
queryDocuments,
readDocument,
updateDocument
updateDocument,
} from "../../Common/MongoProxyClient";
import { extractPartitionKey } from "@azure/cosmos";
import * as Logger from "../../Common/Logger";
@@ -51,7 +51,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
if (
@@ -73,7 +73,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: message
error: message,
},
startKey
);
@@ -110,12 +110,12 @@ export default class MongoDocumentsTab extends DocumentsTab {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
},
error => {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
@@ -127,7 +127,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -145,7 +145,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
return updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent)
@@ -174,12 +174,12 @@ export default class MongoDocumentsTab extends DocumentsTab {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
},
error => {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
@@ -191,7 +191,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -221,7 +221,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
({ continuationToken, documents }) => {
this.continuationToken = continuationToken;
let currentDocuments = this.documentIds();
const currentDocumentsRids = currentDocuments.map(currentDocument => currentDocument.rid);
const currentDocumentsRids = currentDocuments.map((currentDocument) => currentDocument.rid);
const nextDocumentIds = documents
.filter((d: any) => {
return currentDocumentsRids.indexOf(d._rid) < 0;
@@ -251,7 +251,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
collectionName: this.collection.id(),
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
this.onLoadStartKey
);
@@ -270,7 +270,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
this.onLoadStartKey
);
@@ -320,7 +320,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
partitionKey = {
kind: partitionKey.kind,
paths: ["/" + this.partitionKeyProperty.replace(/\./g, "/")],
version: partitionKey.version
version: partitionKey.version,
};
}

View File

@@ -1,120 +1,120 @@
<div
class="tab-pane"
data-bind="
attr:{
id: tabId
}"
role="tabpanel"
>
<!-- Query Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton">
<!-- Execute Query - Start -->
<span
class="commandButton"
data-bind="
click: onExecuteQueryClick,
visible: executeQueryButton.visible() && executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery.svg" />Run
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: executeQueryButton.visible() && !executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery-disabled.svg" />Run
</span>
<!-- Execute Query - End -->
</div>
</div>
<!-- Query Tab Command Bar - End -->
<div
class="queryEditor"
data-bind="
attr: {
id: queryEditorId
},
css: {
mongoQueryEditor:$root.isPreferredApiMongoDB()
}"
></div>
<div
style="margin-left:50px; margin-top:-75px;"
data-bind="
visible: $root.isPreferredApiMongoDB() && sqlQueryEditorContent().length == 0"
>
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<!-- Query Errors Tab - Start-->
<div class="active queryErrorsHeaderContainer" data-bind="visible: errors().length > 0">
<span
class="queryErrors"
data-toggle="tab"
data-bind="
attr: {
href: '#queryerrors' + tabId
}"
>Errors</span
>
</div>
<!-- Query Errors Tab - End -->
<!-- Query Results & Errors Content Container - Start-->
<div class="queryResultErrorContentContainer">
<!-- Query Results Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryresults' + tabId
},
visible: allResultsMetadata().length > 0 && !errors().length > 0"
>
<div class="queryResultsValue">
<span class="queryResults"> Results: </span> <span data-bind="text: showingDocumentsDisplayText"></span>
<span class="queryResultDivider"> | </span> <span> Request Charge: </span>
<span data-bind="text: requestChargeDisplayText"></span> <span class="queryResultDivider"> | </span>
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
<a
data-bind="
click: onFetchNextPageClick"
>
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next.svg" />
</a>
</span>
<span class="queryResultNextDisable" data-bind="visible: !fetchNextPageButton.enabled()">
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next-Disabled.svg" />
</span>
</div>
<div
style="height: 600px;"
data-bind="
attr: {
id: resultsEditorId
}"
></div>
</div>
<!-- Query Results Content - Start-->
<!-- Query Errors Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryerrors' + tabId
},
visible: errors().length > 0"
>
<!-- ko foreach: errors -->
<div style="margin-left:17px; font-size: 12px;">
<span data-bind="text: $data.code"></span> : <span data-bind="text: $data.message"></span>
</div>
<!-- /ko -->
</div>
<!-- Query Errors Content - End-->
</div>
<!-- Results & Errors Content Container - Endt-->
</div>
<div
class="tab-pane"
data-bind="
attr:{
id: tabId
}"
role="tabpanel"
>
<!-- Query Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton">
<!-- Execute Query - Start -->
<span
class="commandButton"
data-bind="
click: onExecuteQueryClick,
visible: executeQueryButton.visible() && executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery.svg" />Run
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: executeQueryButton.visible() && !executeQueryButton.enabled()"
>
<img class="imgiconwidth" src="/ExecuteQuery-disabled.svg" />Run
</span>
<!-- Execute Query - End -->
</div>
</div>
<!-- Query Tab Command Bar - End -->
<div
class="queryEditor"
data-bind="
attr: {
id: queryEditorId
},
css: {
mongoQueryEditor:$root.isPreferredApiMongoDB()
}"
></div>
<div
style="margin-left: 50px; margin-top: -75px"
data-bind="
visible: $root.isPreferredApiMongoDB() && sqlQueryEditorContent().length == 0"
>
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<!-- Query Errors Tab - Start-->
<div class="active queryErrorsHeaderContainer" data-bind="visible: errors().length > 0">
<span
class="queryErrors"
data-toggle="tab"
data-bind="
attr: {
href: '#queryerrors' + tabId
}"
>Errors</span
>
</div>
<!-- Query Errors Tab - End -->
<!-- Query Results & Errors Content Container - Start-->
<div class="queryResultErrorContentContainer">
<!-- Query Results Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryresults' + tabId
},
visible: allResultsMetadata().length > 0 && !errors().length > 0"
>
<div class="queryResultsValue">
<span class="queryResults"> Results: </span> <span data-bind="text: showingDocumentsDisplayText"></span>
<span class="queryResultDivider"> | </span> <span> Request Charge: </span>
<span data-bind="text: requestChargeDisplayText"></span> <span class="queryResultDivider"> | </span>
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
<a
data-bind="
click: onFetchNextPageClick"
>
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next.svg" />
</a>
</span>
<span class="queryResultNextDisable" data-bind="visible: !fetchNextPageButton.enabled()">
<span>Next</span> <img class="queryResultnextImg" src="/Query-Editor-Next-Disabled.svg" />
</span>
</div>
<div
style="height: 600px"
data-bind="
attr: {
id: resultsEditorId
}"
></div>
</div>
<!-- Query Results Content - Start-->
<!-- Query Errors Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryerrors' + tabId
},
visible: errors().length > 0"
>
<!-- ko foreach: errors -->
<div style="margin-left: 17px; font-size: 12px">
<span data-bind="text: $data.code"></span> : <span data-bind="text: $data.message"></span>
</div>
<!-- /ko -->
</div>
<!-- Query Errors Content - End-->
</div>
<!-- Results & Errors Content Container - Endt-->
</div>

View File

@@ -1,15 +1,15 @@
<iframe
name="explorer"
class="iframe"
style="width:100%;height:100%;border:0px;padding:0px;margin:0px;overflow:hidden"
data-bind="
attr: {
src: url,
id: tabId
},
event:{
load: setContentFocus(event)
}"
title="Mongo Shell"
role="tabpanel"
></iframe>
<iframe
name="explorer"
class="iframe"
style="width: 100%; height: 100%; border: 0px; padding: 0px; margin: 0px; overflow: hidden"
data-bind="
attr: {
src: url,
id: tabId
},
event:{
load: setContentFocus(event)
}"
title="Mongo Shell"
role="tabpanel"
></iframe>

View File

@@ -1,215 +1,215 @@
import * as Constants from "../../Common/Constants";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import Q from "q";
import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../Common/HashMap";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { configContext, Platform } from "../../ConfigContext";
export default class MongoShellTab extends TabsBase {
public url: ko.Computed<string>;
private _container: Explorer;
private _runtimeEndpoint: string;
private _logTraces: HashMap<number>;
constructor(options: ViewModels.TabOptions) {
super(options);
this._logTraces = new HashMap<number>();
this._container = options.collection.container;
this.url = ko.computed<string>(() => {
const account = userContext.databaseAccount;
const resourceId = account && account.id;
const accountName = account && account.name;
const mongoEndpoint = account && (account.properties.mongoEndpoint || account.properties.documentEndpoint);
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/";
if (this._container.serverId() === "localhost") {
baseUrl = "/content/mongoshell/";
}
return `${extensionEndpoint}${baseUrl}index.html?resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
});
window.addEventListener("message", this.handleMessage.bind(this), false);
}
public setContentFocus(event: any): any {
// TODO: Work around cross origin security issue in Hosted Data Explorer by using Shell <-> Data Explorer messaging (253527)
// if(event.type === "load" && window.dataExplorerPlatform != PlatformType.Hosted) {
// let activeShell = event.target.contentWindow && event.target.contentWindow.mongo && event.target.contentWindow.mongo.shells && event.target.contentWindow.mongo.shells[0];
// activeShell && setTimeout(function(){
// activeShell.focus();
// },2000);
// }
}
public onTabClick(): void {
super.onTabClick();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
}
public handleMessage(event: MessageEvent) {
if (isInvalidParentFrameOrigin(event)) {
return;
}
const shellIframe: HTMLIFrameElement = <HTMLIFrameElement>document.getElementById(this.tabId);
if (!shellIframe) {
return;
}
if (typeof event.data !== "object" || event.data["signature"] !== "mongoshell") {
return;
}
if (!("data" in event.data) || !("eventType" in event.data)) {
return;
}
if (event.data.eventType == MessageType.IframeReady) {
this.handleReadyMessage(event, shellIframe);
} else if (event.data.eventType == MessageType.Notification) {
this.handleNotificationMessage(event, shellIframe);
} else {
this.handleLogMessage(event, shellIframe);
}
}
private handleReadyMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (typeof event.data["data"] !== "string") {
return;
}
if (event.data.data !== "ready") {
return;
}
const authorization: string = userContext.authorizationToken || "";
const resourceId = this._container.databaseAccount().id;
const accountName = this._container.databaseAccount().name;
const documentEndpoint =
this._container.databaseAccount().properties.mongoEndpoint ||
this._container.databaseAccount().properties.documentEndpoint;
const mongoEndpoint =
documentEndpoint.substr(
Constants.MongoDBAccounts.protocol.length + 3,
documentEndpoint.length -
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length)
) + Constants.MongoDBAccounts.defaultPort.toString();
const databaseId = this.collection.databaseId;
const collectionId = this.collection.id();
const apiEndpoint = configContext.BACKEND_ENDPOINT;
const encryptedAuthToken: string = userContext.accessToken;
shellIframe.contentWindow.postMessage(
{
signature: "dataexplorer",
data: {
resourceId: resourceId,
accountName: accountName,
mongoEndpoint: mongoEndpoint,
authorization: authorization,
databaseId: databaseId,
collectionId: collectionId,
encryptedAuthToken: encryptedAuthToken,
apiEndpoint: apiEndpoint
}
},
configContext.BACKEND_ENDPOINT
);
}
private handleLogMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
return;
}
if (!("logData" in event.data.data)) {
return;
}
const dataToLog = { message: event.data.data.logData };
const logType: string = event.data.data.logType;
const shellTraceId: string = event.data.data.traceId || "none";
switch (logType) {
case LogType.Information:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Success, dataToLog);
break;
case LogType.Warning:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Failed, dataToLog);
break;
case LogType.Verbose:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Mark, dataToLog);
break;
case LogType.StartTrace:
const telemetryTraceId: number = TelemetryProcessor.traceStart(Action.MongoShell, dataToLog);
this._logTraces.set(shellTraceId, telemetryTraceId);
break;
case LogType.SuccessTrace:
if (this._logTraces.has(shellTraceId)) {
const originalTelemetryTraceId: number = this._logTraces.get(shellTraceId);
TelemetryProcessor.traceSuccess(Action.MongoShell, dataToLog, originalTelemetryTraceId);
this._logTraces.delete(shellTraceId);
} else {
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Success, dataToLog);
}
break;
case LogType.FailureTrace:
if (this._logTraces.has(shellTraceId)) {
const originalTelemetryTraceId: number = this._logTraces.get(shellTraceId);
TelemetryProcessor.traceFailure(Action.MongoShell, dataToLog, originalTelemetryTraceId);
this._logTraces.delete(shellTraceId);
} else {
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Failed, dataToLog);
}
break;
}
}
private handleNotificationMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
return;
}
if (!("logData" in event.data.data)) {
return;
}
const dataToLog: string = event.data.data.logData;
const logType: string = event.data.data.logType;
switch (logType) {
case LogType.Information:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, dataToLog);
break;
case LogType.Warning:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, dataToLog);
break;
case LogType.InProgress:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, dataToLog);
}
}
}
class MessageType {
static IframeReady: string = "iframeready";
static Notification: string = "notification";
static Log: string = "log";
}
class LogType {
static Information: string = "information";
static Warning: string = "warning";
static Verbose: string = "verbose";
static InProgress: string = "inprogress";
static StartTrace: string = "start";
static SuccessTrace: string = "success";
static FailureTrace: string = "failure";
}
import * as Constants from "../../Common/Constants";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import Q from "q";
import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../Common/HashMap";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { configContext, Platform } from "../../ConfigContext";
export default class MongoShellTab extends TabsBase {
public url: ko.Computed<string>;
private _container: Explorer;
private _runtimeEndpoint: string;
private _logTraces: HashMap<number>;
constructor(options: ViewModels.TabOptions) {
super(options);
this._logTraces = new HashMap<number>();
this._container = options.collection.container;
this.url = ko.computed<string>(() => {
const account = userContext.databaseAccount;
const resourceId = account && account.id;
const accountName = account && account.name;
const mongoEndpoint = account && (account.properties.mongoEndpoint || account.properties.documentEndpoint);
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/";
if (this._container.serverId() === "localhost") {
baseUrl = "/content/mongoshell/";
}
return `${extensionEndpoint}${baseUrl}index.html?resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
});
window.addEventListener("message", this.handleMessage.bind(this), false);
}
public setContentFocus(event: any): any {
// TODO: Work around cross origin security issue in Hosted Data Explorer by using Shell <-> Data Explorer messaging (253527)
// if(event.type === "load" && window.dataExplorerPlatform != PlatformType.Hosted) {
// let activeShell = event.target.contentWindow && event.target.contentWindow.mongo && event.target.contentWindow.mongo.shells && event.target.contentWindow.mongo.shells[0];
// activeShell && setTimeout(function(){
// activeShell.focus();
// },2000);
// }
}
public onTabClick(): void {
super.onTabClick();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
}
public handleMessage(event: MessageEvent) {
if (isInvalidParentFrameOrigin(event)) {
return;
}
const shellIframe: HTMLIFrameElement = <HTMLIFrameElement>document.getElementById(this.tabId);
if (!shellIframe) {
return;
}
if (typeof event.data !== "object" || event.data["signature"] !== "mongoshell") {
return;
}
if (!("data" in event.data) || !("eventType" in event.data)) {
return;
}
if (event.data.eventType == MessageType.IframeReady) {
this.handleReadyMessage(event, shellIframe);
} else if (event.data.eventType == MessageType.Notification) {
this.handleNotificationMessage(event, shellIframe);
} else {
this.handleLogMessage(event, shellIframe);
}
}
private handleReadyMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (typeof event.data["data"] !== "string") {
return;
}
if (event.data.data !== "ready") {
return;
}
const authorization: string = userContext.authorizationToken || "";
const resourceId = this._container.databaseAccount().id;
const accountName = this._container.databaseAccount().name;
const documentEndpoint =
this._container.databaseAccount().properties.mongoEndpoint ||
this._container.databaseAccount().properties.documentEndpoint;
const mongoEndpoint =
documentEndpoint.substr(
Constants.MongoDBAccounts.protocol.length + 3,
documentEndpoint.length -
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length)
) + Constants.MongoDBAccounts.defaultPort.toString();
const databaseId = this.collection.databaseId;
const collectionId = this.collection.id();
const apiEndpoint = configContext.BACKEND_ENDPOINT;
const encryptedAuthToken: string = userContext.accessToken;
shellIframe.contentWindow.postMessage(
{
signature: "dataexplorer",
data: {
resourceId: resourceId,
accountName: accountName,
mongoEndpoint: mongoEndpoint,
authorization: authorization,
databaseId: databaseId,
collectionId: collectionId,
encryptedAuthToken: encryptedAuthToken,
apiEndpoint: apiEndpoint,
},
},
configContext.BACKEND_ENDPOINT
);
}
private handleLogMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
return;
}
if (!("logData" in event.data.data)) {
return;
}
const dataToLog = { message: event.data.data.logData };
const logType: string = event.data.data.logType;
const shellTraceId: string = event.data.data.traceId || "none";
switch (logType) {
case LogType.Information:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Success, dataToLog);
break;
case LogType.Warning:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Failed, dataToLog);
break;
case LogType.Verbose:
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Mark, dataToLog);
break;
case LogType.StartTrace:
const telemetryTraceId: number = TelemetryProcessor.traceStart(Action.MongoShell, dataToLog);
this._logTraces.set(shellTraceId, telemetryTraceId);
break;
case LogType.SuccessTrace:
if (this._logTraces.has(shellTraceId)) {
const originalTelemetryTraceId: number = this._logTraces.get(shellTraceId);
TelemetryProcessor.traceSuccess(Action.MongoShell, dataToLog, originalTelemetryTraceId);
this._logTraces.delete(shellTraceId);
} else {
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Success, dataToLog);
}
break;
case LogType.FailureTrace:
if (this._logTraces.has(shellTraceId)) {
const originalTelemetryTraceId: number = this._logTraces.get(shellTraceId);
TelemetryProcessor.traceFailure(Action.MongoShell, dataToLog, originalTelemetryTraceId);
this._logTraces.delete(shellTraceId);
} else {
TelemetryProcessor.trace(Action.MongoShell, ActionModifiers.Failed, dataToLog);
}
break;
}
}
private handleNotificationMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (!("logType" in event.data.data) || typeof event.data.data["logType"] !== "string") {
return;
}
if (!("logData" in event.data.data)) {
return;
}
const dataToLog: string = event.data.data.logData;
const logType: string = event.data.data.logType;
switch (logType) {
case LogType.Information:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, dataToLog);
break;
case LogType.Warning:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, dataToLog);
break;
case LogType.InProgress:
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, dataToLog);
}
}
}
class MessageType {
static IframeReady: string = "iframeready";
static Notification: string = "notification";
static Log: string = "log";
}
class LogType {
static Information: string = "information";
static Warning: string = "warning";
static Verbose: string = "verbose";
static InProgress: string = "inprogress";
static StartTrace: string = "start";
static SuccessTrace: string = "success";
static FailureTrace: string = "failure";
}

View File

@@ -56,7 +56,7 @@ export default class NotebookTabV2 extends TabsBase {
connectionInfo: this.container.notebookServerInfo(),
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
contentProvider: this.container.notebookManager?.notebookContentProvider
contentProvider: this.container.notebookManager?.notebookContentProvider,
});
}
@@ -70,7 +70,7 @@ export default class NotebookTabV2 extends TabsBase {
contentItem: options.notebookContentItem,
notebooksBasePath: this.container.getNotebookBasePath(),
notebookClient: NotebookTabV2.clientManager,
onUpdateKernelInfo: this.onKernelUpdate
onUpdateKernelInfo: this.onKernelUpdate,
});
this.selectedSparkPool = ko.observable<string>(null);
@@ -156,7 +156,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: copyToLabel,
hasPopup: false,
disabled: false,
ariaLabel: copyToLabel
ariaLabel: copyToLabel,
});
}
@@ -167,7 +167,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: publishLabel,
hasPopup: false,
disabled: false,
ariaLabel: publishLabel
ariaLabel: publishLabel,
});
}
@@ -187,10 +187,10 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: saveLabel,
hasPopup: false,
disabled: false,
ariaLabel: saveLabel
ariaLabel: saveLabel,
},
...saveButtonChildren
]
...saveButtonChildren,
],
},
{
iconSrc: null,
@@ -213,10 +213,10 @@ export default class NotebookTabV2 extends TabsBase {
dropdownItemKey: kernel.name,
hasPopup: false,
disabled: false,
ariaLabel: kernel.displayName
ariaLabel: kernel.displayName,
} as CommandButtonComponentProps)
),
ariaLabel: kernelLabel
ariaLabel: kernelLabel,
},
{
iconSrc: RunIcon,
@@ -240,7 +240,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: runActiveCellLabel,
hasPopup: false,
disabled: false,
ariaLabel: runActiveCellLabel
ariaLabel: runActiveCellLabel,
},
{
iconSrc: RunAllIcon,
@@ -252,7 +252,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: runAllLabel,
hasPopup: false,
disabled: false,
ariaLabel: runAllLabel
ariaLabel: runAllLabel,
},
{
iconSrc: InterruptKernelIcon,
@@ -261,7 +261,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: interruptKernelLabel,
hasPopup: false,
disabled: false,
ariaLabel: interruptKernelLabel
ariaLabel: interruptKernelLabel,
},
{
iconSrc: KillKernelIcon,
@@ -270,7 +270,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: killKernelLabel,
hasPopup: false,
disabled: false,
ariaLabel: killKernelLabel
ariaLabel: killKernelLabel,
},
{
iconSrc: RestartIcon,
@@ -279,9 +279,9 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: restartKernelLabel,
hasPopup: false,
disabled: false,
ariaLabel: restartKernelLabel
}
]
ariaLabel: restartKernelLabel,
},
],
},
{
iconSrc: ClearAllOutputsIcon,
@@ -290,7 +290,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: clearLabel,
hasPopup: false,
disabled: false,
ariaLabel: clearLabel
ariaLabel: clearLabel,
},
{
iconSrc: NewCellIcon,
@@ -299,7 +299,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: newCellLabel,
ariaLabel: newCellLabel,
hasPopup: false,
disabled: false
disabled: false,
},
CommandBarComponentButtonFactory.createDivider(),
{
@@ -323,7 +323,7 @@ export default class NotebookTabV2 extends TabsBase {
ariaLabel: codeLabel,
dropdownItemKey: cellCodeType,
hasPopup: false,
disabled: false
disabled: false,
},
{
iconSrc: null,
@@ -333,7 +333,7 @@ export default class NotebookTabV2 extends TabsBase {
ariaLabel: markdownLabel,
dropdownItemKey: cellMarkdownType,
hasPopup: false,
disabled: false
disabled: false,
},
{
iconSrc: null,
@@ -343,9 +343,9 @@ export default class NotebookTabV2 extends TabsBase {
ariaLabel: rawLabel,
dropdownItemKey: cellRawType,
hasPopup: false,
disabled: false
}
]
disabled: false,
},
],
},
{
iconSrc: CopyIcon,
@@ -363,7 +363,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: copyLabel,
ariaLabel: copyLabel,
hasPopup: false,
disabled: false
disabled: false,
},
{
iconSrc: CutIcon,
@@ -372,7 +372,7 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: cutLabel,
ariaLabel: cutLabel,
hasPopup: false,
disabled: false
disabled: false,
},
{
iconSrc: PasteIcon,
@@ -381,10 +381,10 @@ export default class NotebookTabV2 extends TabsBase {
commandButtonLabel: pasteLabel,
ariaLabel: pasteLabel,
hasPopup: false,
disabled: false
}
]
}
disabled: false,
},
],
},
// TODO: Uncomment when undo/redo is reimplemented in nteract
];
@@ -408,8 +408,8 @@ export default class NotebookTabV2 extends TabsBase {
},
onCreateNewSparkPoolClicked: (workspaceResourceId: string) => {
this.container.createSparkPool(workspaceResourceId);
}
}
},
},
};
buttons.splice(1, 0, arcadiaWorkspaceDropdown);
}
@@ -429,9 +429,9 @@ export default class NotebookTabV2 extends TabsBase {
this.container &&
this.container.arcadiaWorkspaces &&
this.container.arcadiaWorkspaces() &&
this.container.arcadiaWorkspaces().forEach(async workspace => {
this.container.arcadiaWorkspaces().forEach(async (workspace) => {
if (workspace && workspace.name && workspace.sparkPools) {
const selectedPoolIndex = _.findIndex(workspace.sparkPools, pool => pool && pool.name === item.text);
const selectedPoolIndex = _.findIndex(workspace.sparkPools, (pool) => pool && pool.name === item.text);
if (selectedPoolIndex >= 0) {
const selectedPool = workspace.sparkPools[selectedPoolIndex];
if (selectedPool && selectedPool.name) {
@@ -441,9 +441,9 @@ export default class NotebookTabV2 extends TabsBase {
endpoints: [
{
endpoint: `https://${workspace.name}.${configContext.ARCADIA_LIVY_ENDPOINT_DNS_ZONE}/livyApi/versions/${ArmApiVersions.arcadiaLivy}/sparkPools/${selectedPool.name}/`,
kind: DataModels.SparkClusterEndpointKind.Livy
}
]
kind: DataModels.SparkClusterEndpointKind.Livy,
},
],
});
this.selectedSparkPool(item.text);
await this.reconfigureServiceEndpoints();
@@ -456,7 +456,7 @@ export default class NotebookTabV2 extends TabsBase {
};
private onKernelUpdate = async () => {
await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName()).catch(reason => {
await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName()).catch((reason) => {
/* Erroring is ok here */
});
this.updateNavbarWithTabsButtons();
@@ -498,7 +498,7 @@ export default class NotebookTabV2 extends TabsBase {
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook
dataExplorerArea: Areas.Notebook,
});
}
}

View File

@@ -4,7 +4,7 @@ import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../Contracts/ViewModels";
import {
NotebookViewerComponent,
NotebookViewerComponentProps
NotebookViewerComponentProps,
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
@@ -30,7 +30,7 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
notebookUrl: this.notebookUrl,
backNavigationText: undefined,
onBackClick: undefined,
onTagClick: undefined
onTagClick: undefined,
};
return this.parameters() ? <NotebookViewerComponent {...props} /> : <></>;

View File

@@ -1,342 +1,342 @@
<div
class="tab-pane"
data-bind="setTemplateReady: true,
attr:{
id: tabId
}"
role="tabpanel"
>
<div class="tabPaneContentContainer">
<div class="mongoQueryHelper" data-bind="visible: isPreferredApiMongoDB && sqlQueryEditorContent().length === 0">
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: maybeSubQuery">
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/info_color.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery"
>Please see Cosmos sub query documentation for further information</a
>
</span>
</div>
</div>
<div class="queryEditorWithSplitter" data-bind="attr: { id: queryEditorId }">
<editor
class="queryEditor"
data-bind="css: { mongoQueryEditor: isPreferredApiMongoDB }"
params="{
content: initialEditorContent,
contentType: monacoSettings.language,
isReadOnly: monacoSettings.readOnly,
lineNumbers: 'on',
ariaLabel: 'Editing Query',
updatedContent: sqlQueryEditorContent,
selectedContent: selectedContent
}"
></editor>
<!-- Splitter - Start -->
<div class="splitter ui-resizable-handle ui-resizable-s" data-bind="attr: { id: splitterId }">
<img class="queryEditorHorizontalSplitter" src="/HorizontalSplitter.svg" alt="Splitter" />
</div>
</div>
<!-- Splitter - End -->
<!-- Script for results metadata that is common to all APIs -->
<script type="text/html" id="result-metadata-template">
<span>
<span data-bind="text: showingDocumentsDisplayText"></span>
</span>
<span class="queryResultDivider" data-bind="visible: fetchNextPageButton.enabled"> | </span>
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
<a data-bind="click: onFetchNextPageClick">
<span>Load more</span>
<img class="queryResultnextImg" src="/Query-Editor-Next.svg" alt="Fetch next page">
</a>
</span>
</script>
<!-- Query Errors Tab - Start-->
<div class="active queryErrorsHeaderContainer" data-bind="visible: !!error()">
<span class="queryErrors" data-toggle="tab" data-bind="attr: { href: '#queryerrors' + tabId }">Errors</span>
</div>
<!-- Query Errors Tab - End -->
<!-- Query Results & Errors Content Container - Start-->
<div class="queryResultErrorContentContainer">
<div
class="queryEditorWatermark"
data-bind="visible: allResultsMetadata().length === 0 && !error() && !queryResults() && !isExecuting()"
>
<p><img src="/RunQuery.png" alt="Execute Query Watermark" /></p>
<p class="queryEditorWatermarkText">Execute a query to see the results</p>
</div>
<div
class="queryResultsErrorsContent"
data-bind="visible: allResultsMetadata().length > 0 || !!error() || queryResults()"
>
<div class="togglesWithMetadata" data-bind="visible: !error()">
<div
class="toggles"
aria-label="Successful execution"
id="execute-query-toggles"
data-bind="event: { keydown: onToggleKeyDown }"
tabindex="0"
>
<div class="tab">
<input type="radio" class="radio" value="result" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
aria-label="Results"
>Results</span
>
</div>
<div class="tab">
<input type="radio" class="radio" value="logs" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleMetrics, css:{ selectedToggle: isMetricsToggled(), unselectedToggle: !isMetricsToggled() }"
aria-label="Query stats"
>Query Stats</span
>
</div>
</div>
<div
class="result-metadata"
data-bind="template: { name: 'result-metadata-template' }, visible: isResultToggled()"
></div>
</div>
<json-editor
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
>
</json-editor>
<div
class="queryMetricsSummaryContainer"
data-bind="visible: isMetricsToggled() && allResultsMetadata().length > 0 && !error()"
>
<table class="queryMetricsSummary">
<caption>
Query Statistics
</caption>
<thead class="queryMetricsSummaryHead">
<tr class="queryMetricsSummaryHeader queryMetricsSummaryTuple">
<th title="METRIC" scope="col">METRIC</th>
<th title="VALUE" scope="col">VALUE</th>
</tr>
</thead>
<tbody class="queryMetricsSummaryBody" data-bind="with: aggregatedQueryMetrics">
<tr class="queryMetricsSummaryTuple">
<td title="Request Charge">Request Charge</td>
<td>
<span
data-bind="text: $parent.requestChargeDisplayText, attr: { title: $parent.requestChargeDisplayText }"
></span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple">
<td title="Showing Results">Showing Results</td>
<td>
<span
data-bind="text: $parent.showingDocumentsDisplayText, attr: { title: $parent.showingDocumentsDisplayText }"
></span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Retrieved document count">Retrieved document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total number of retrieved documents</span>
</span>
</td>
<td><span data-bind="text: retrievedDocumentCount, attr: { title: retrievedDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Retrieved document size">Retrieved document size</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total size of retrieved documents in bytes</span>
</span>
</td>
<td>
<span data-bind="text: retrievedDocumentSize, attr: { title: retrievedDocumentSize }"></span>
<span>bytes</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Output document count">Output document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Number of output documents</span>
</span>
</td>
<td><span data-bind="text: outputDocumentCount, attr: { title: outputDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Output document size">Output document size</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total size of output documents in bytes</span>
</span>
</td>
<td>
<span data-bind="text: outputDocumentSize, attr: { title: outputDocumentSize }"></span>
<span>bytes</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Index hit document count">Index hit document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total number of documents matched by the filter</span>
</span>
</td>
<td><span data-bind="text: indexHitDocumentCount, attr: { title: indexHitDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Index lookup time">Index lookup time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent in physical index layer</span>
</span>
</td>
<td>
<span data-bind="text: indexLookupTime, attr: { title: indexLookupTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Document load time">Document load time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent in loading documents</span>
</span>
</td>
<td>
<span data-bind="text: documentLoadTime, attr: { title: documentLoadTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Query engine execution time">Query engine execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText queryEngineExeTimeInfo"
>Time spent by the query engine to execute the query expression (excludes other execution times
like load documents or write results)</span
>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.queryEngineExecutionTime, attr: { title: runtimeExecutionTimes.queryEngineExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="System function execution time">System function execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total time spent executing system (built-in) functions</span>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.systemFunctionExecutionTime, attr: { title: runtimeExecutionTimes.systemFunctionExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="User defined function execution time">User defined function execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total time spent executing user-defined functions</span>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.userDefinedFunctionExecutionTime, attr: { title: runtimeExecutionTimes.userDefinedFunctionExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Document write time">Document write time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent to write query result set to response buffer</span>
</span>
</td>
<td>
<span data-bind="text: documentWriteTime, attr: { title: documentWriteTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.roundTrips() != null">
<td title="Round Trips">Round Trips</td>
<td><span data-bind="text: $parent.roundTrips, attr: { title: $parent.roundTrips }"></span></td>
</tr>
<!-- TODO: Report activity id for mongo queries -->
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.activityId() != null">
<td title="Activity id">Activity id</td>
<td></td>
<td><span data-bind="text: $parent.activityId, attr: { title: $parent.activityId }"></span></td>
</tr>
</tbody>
</table>
<div class="downloadMetricsLinkContainer" data-bind="visible: $parent.isQueryMetricsEnabled">
<a
id="downloadMetricsLink"
role="button"
tabindex="0"
data-bind="event: { click: onDownloadQueryMetricsCsvClick, keypress: onDownloadQueryMetricsCsvKeyPress }"
>
<img class="downloadCsvImg" src="/DownloadQuery.svg" alt="download query metrics csv" />
<span>Per-partition query metrics (CSV)</span>
</a>
</div>
</div>
<!-- Query Errors Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryerrors' + tabId
},
visible: !!error()"
>
<div class="errorContent">
<span class="errorMessage" data-bind="text: error"></span>
<span class="errorDetailsLink">
<a
data-bind="click: $parent.onErrorDetailsClick, event: { keypress: $parent.onErrorDetailsKeyPress }"
id="error-display"
tabindex="0"
aria-label="Error details link"
>More details</a
>
</span>
</div>
</div>
<!-- Query Errors Content - End-->
</div>
</div>
<!-- Results & Errors Content Container - End-->
</div>
</div>
<div
class="tab-pane"
data-bind="setTemplateReady: true,
attr:{
id: tabId
}"
role="tabpanel"
>
<div class="tabPaneContentContainer">
<div class="mongoQueryHelper" data-bind="visible: isPreferredApiMongoDB && sqlQueryEditorContent().length === 0">
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: maybeSubQuery">
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/info_color.svg" alt="Error" /></span>
<span class="warningErrorDetailsLinkContainer">
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery"
>Please see Cosmos sub query documentation for further information</a
>
</span>
</div>
</div>
<div class="queryEditorWithSplitter" data-bind="attr: { id: queryEditorId }">
<editor
class="queryEditor"
data-bind="css: { mongoQueryEditor: isPreferredApiMongoDB }"
params="{
content: initialEditorContent,
contentType: monacoSettings.language,
isReadOnly: monacoSettings.readOnly,
lineNumbers: 'on',
ariaLabel: 'Editing Query',
updatedContent: sqlQueryEditorContent,
selectedContent: selectedContent
}"
></editor>
<!-- Splitter - Start -->
<div class="splitter ui-resizable-handle ui-resizable-s" data-bind="attr: { id: splitterId }">
<img class="queryEditorHorizontalSplitter" src="/HorizontalSplitter.svg" alt="Splitter" />
</div>
</div>
<!-- Splitter - End -->
<!-- Script for results metadata that is common to all APIs -->
<script type="text/html" id="result-metadata-template">
<span>
<span data-bind="text: showingDocumentsDisplayText"></span>
</span>
<span class="queryResultDivider" data-bind="visible: fetchNextPageButton.enabled"> | </span>
<span class="queryResultNextEnable" data-bind="visible: fetchNextPageButton.enabled">
<a data-bind="click: onFetchNextPageClick">
<span>Load more</span>
<img class="queryResultnextImg" src="/Query-Editor-Next.svg" alt="Fetch next page" />
</a>
</span>
</script>
<!-- Query Errors Tab - Start-->
<div class="active queryErrorsHeaderContainer" data-bind="visible: !!error()">
<span class="queryErrors" data-toggle="tab" data-bind="attr: { href: '#queryerrors' + tabId }">Errors</span>
</div>
<!-- Query Errors Tab - End -->
<!-- Query Results & Errors Content Container - Start-->
<div class="queryResultErrorContentContainer">
<div
class="queryEditorWatermark"
data-bind="visible: allResultsMetadata().length === 0 && !error() && !queryResults() && !isExecuting()"
>
<p><img src="/RunQuery.png" alt="Execute Query Watermark" /></p>
<p class="queryEditorWatermarkText">Execute a query to see the results</p>
</div>
<div
class="queryResultsErrorsContent"
data-bind="visible: allResultsMetadata().length > 0 || !!error() || queryResults()"
>
<div class="togglesWithMetadata" data-bind="visible: !error()">
<div
class="toggles"
aria-label="Successful execution"
id="execute-query-toggles"
data-bind="event: { keydown: onToggleKeyDown }"
tabindex="0"
>
<div class="tab">
<input type="radio" class="radio" value="result" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
aria-label="Results"
>Results</span
>
</div>
<div class="tab">
<input type="radio" class="radio" value="logs" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleMetrics, css:{ selectedToggle: isMetricsToggled(), unselectedToggle: !isMetricsToggled() }"
aria-label="Query stats"
>Query Stats</span
>
</div>
</div>
<div
class="result-metadata"
data-bind="template: { name: 'result-metadata-template' }, visible: isResultToggled()"
></div>
</div>
<json-editor
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
>
</json-editor>
<div
class="queryMetricsSummaryContainer"
data-bind="visible: isMetricsToggled() && allResultsMetadata().length > 0 && !error()"
>
<table class="queryMetricsSummary">
<caption>
Query Statistics
</caption>
<thead class="queryMetricsSummaryHead">
<tr class="queryMetricsSummaryHeader queryMetricsSummaryTuple">
<th title="METRIC" scope="col">METRIC</th>
<th title="VALUE" scope="col">VALUE</th>
</tr>
</thead>
<tbody class="queryMetricsSummaryBody" data-bind="with: aggregatedQueryMetrics">
<tr class="queryMetricsSummaryTuple">
<td title="Request Charge">Request Charge</td>
<td>
<span
data-bind="text: $parent.requestChargeDisplayText, attr: { title: $parent.requestChargeDisplayText }"
></span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple">
<td title="Showing Results">Showing Results</td>
<td>
<span
data-bind="text: $parent.showingDocumentsDisplayText, attr: { title: $parent.showingDocumentsDisplayText }"
></span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Retrieved document count">Retrieved document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total number of retrieved documents</span>
</span>
</td>
<td><span data-bind="text: retrievedDocumentCount, attr: { title: retrievedDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Retrieved document size">Retrieved document size</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total size of retrieved documents in bytes</span>
</span>
</td>
<td>
<span data-bind="text: retrievedDocumentSize, attr: { title: retrievedDocumentSize }"></span>
<span>bytes</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Output document count">Output document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Number of output documents</span>
</span>
</td>
<td><span data-bind="text: outputDocumentCount, attr: { title: outputDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Output document size">Output document size</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total size of output documents in bytes</span>
</span>
</td>
<td>
<span data-bind="text: outputDocumentSize, attr: { title: outputDocumentSize }"></span>
<span>bytes</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Index hit document count">Index hit document count</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total number of documents matched by the filter</span>
</span>
</td>
<td><span data-bind="text: indexHitDocumentCount, attr: { title: indexHitDocumentCount }"></span></td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Index lookup time">Index lookup time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent in physical index layer</span>
</span>
</td>
<td>
<span data-bind="text: indexLookupTime, attr: { title: indexLookupTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Document load time">Document load time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent in loading documents</span>
</span>
</td>
<td>
<span data-bind="text: documentLoadTime, attr: { title: documentLoadTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Query engine execution time">Query engine execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText queryEngineExeTimeInfo"
>Time spent by the query engine to execute the query expression (excludes other execution times
like load documents or write results)</span
>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.queryEngineExecutionTime, attr: { title: runtimeExecutionTimes.queryEngineExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="System function execution time">System function execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total time spent executing system (built-in) functions</span>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.systemFunctionExecutionTime, attr: { title: runtimeExecutionTimes.systemFunctionExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="User defined function execution time">User defined function execution time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Total time spent executing user-defined functions</span>
</span>
</td>
<td>
<span
data-bind="text: runtimeExecutionTimes.userDefinedFunctionExecutionTime, attr: { title: runtimeExecutionTimes.userDefinedFunctionExecutionTime }"
></span>
<span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.isQueryMetricsEnabled">
<td>
<span title="Document write time">Document write time</span>
<span class="queryMetricInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="queryMetricTooltipText">Time spent to write query result set to response buffer</span>
</span>
</td>
<td>
<span data-bind="text: documentWriteTime, attr: { title: documentWriteTime }"></span> <span>ms</span>
</td>
</tr>
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.roundTrips() != null">
<td title="Round Trips">Round Trips</td>
<td><span data-bind="text: $parent.roundTrips, attr: { title: $parent.roundTrips }"></span></td>
</tr>
<!-- TODO: Report activity id for mongo queries -->
<tr class="queryMetricsSummaryTuple" data-bind="visible: $parent.activityId() != null">
<td title="Activity id">Activity id</td>
<td></td>
<td><span data-bind="text: $parent.activityId, attr: { title: $parent.activityId }"></span></td>
</tr>
</tbody>
</table>
<div class="downloadMetricsLinkContainer" data-bind="visible: $parent.isQueryMetricsEnabled">
<a
id="downloadMetricsLink"
role="button"
tabindex="0"
data-bind="event: { click: onDownloadQueryMetricsCsvClick, keypress: onDownloadQueryMetricsCsvKeyPress }"
>
<img class="downloadCsvImg" src="/DownloadQuery.svg" alt="download query metrics csv" />
<span>Per-partition query metrics (CSV)</span>
</a>
</div>
</div>
<!-- Query Errors Content - Start-->
<div
class="tab-pane active"
data-bind="
id: {
href: 'queryerrors' + tabId
},
visible: !!error()"
>
<div class="errorContent">
<span class="errorMessage" data-bind="text: error"></span>
<span class="errorDetailsLink">
<a
data-bind="click: $parent.onErrorDetailsClick, event: { keypress: $parent.onErrorDetailsKeyPress }"
id="error-display"
tabindex="0"
aria-label="Error details link"
>More details</a
>
</span>
</div>
</div>
<!-- Query Errors Content - End-->
</div>
</div>
<!-- Results & Errors Content Container - End-->
</div>
</div>

View File

@@ -1,86 +1,86 @@
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import QueryTab from "./QueryTab";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
describe("Query Tab", () => {
function getNewQueryTabForContainer(container: Explorer): QueryTab {
const database = {
container: container,
id: ko.observable<string>("test"),
isDatabaseShared: () => false
} as ViewModels.Database;
const collection = {
container: container,
databaseId: "test",
id: ko.observable<string>("test")
} as ViewModels.Collection;
return new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
collection: collection,
database: database,
title: "",
tabPath: "",
isActive: ko.observable<boolean>(false),
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
}
describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => {
const collection = {
id: ko.observable<string>("withoutsystempk"),
partitionKey: {
systemKey: true
}
} as ViewModels.Collection;
it("no container with system pk, should not set partition key option", () => {
const iteratorOptions = QueryTab.getIteratorOptions(collection);
expect(iteratorOptions.initialHeaders).toBeUndefined();
});
});
describe("isQueryMetricsEnabled()", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer();
});
it("should be true for accounts using SQL API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB.toLowerCase());
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.isQueryMetricsEnabled()).toBe(true);
});
it("should be false for accounts using other APIs", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.Graph.toLowerCase());
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.isQueryMetricsEnabled()).toBe(false);
});
});
describe("Save Queries command button", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer();
});
it("should be visible when using a supported API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB);
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.saveQueryButton.visible()).toBe(true);
});
it("should not be visible when using an unsupported API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.saveQueryButton.visible()).toBe(false);
});
});
});
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import QueryTab from "./QueryTab";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
describe("Query Tab", () => {
function getNewQueryTabForContainer(container: Explorer): QueryTab {
const database = {
container: container,
id: ko.observable<string>("test"),
isDatabaseShared: () => false,
} as ViewModels.Database;
const collection = {
container: container,
databaseId: "test",
id: ko.observable<string>("test"),
} as ViewModels.Collection;
return new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
collection: collection,
database: database,
title: "",
tabPath: "",
isActive: ko.observable<boolean>(false),
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
}
describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => {
const collection = {
id: ko.observable<string>("withoutsystempk"),
partitionKey: {
systemKey: true,
},
} as ViewModels.Collection;
it("no container with system pk, should not set partition key option", () => {
const iteratorOptions = QueryTab.getIteratorOptions(collection);
expect(iteratorOptions.initialHeaders).toBeUndefined();
});
});
describe("isQueryMetricsEnabled()", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer();
});
it("should be true for accounts using SQL API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB.toLowerCase());
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.isQueryMetricsEnabled()).toBe(true);
});
it("should be false for accounts using other APIs", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.Graph.toLowerCase());
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.isQueryMetricsEnabled()).toBe(false);
});
});
describe("Save Queries command button", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer();
});
it("should be visible when using a supported API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB);
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.saveQueryButton.visible()).toBe(true);
});
it("should not be visible when using an unsupported API", () => {
explorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
const queryTab = getNewQueryTabForContainer(explorer);
expect(queryTab.saveQueryButton.visible()).toBe(false);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,249 +1,278 @@
<div
class="tab-pane tableContainer"
data-bind="
attr:{
id: tabId
}"
role="tabpanel"
>
<!-- Tables Query Tab Query Builder - Start-->
<div
class="query-builder"
data-bind="with: queryViewModel, attr: {
id: queryViewModel.id
}"
>
<!-- Tables Query Tab Errors - Start-->
<div class="error-bar">
<div class="error-message" aria-label="Error Message" data-bind="visible: hasQueryError">
<span><img class="entity-error-Img" src="/error_red.svg"/></span>
<span class="error-text" role="alert" data-bind="text: queryErrorMessage"></span>
</div>
</div>
<!-- Tables Query Tab Errors - End-->
<!-- Tables Query Tab Query Text - Start-->
<div class="query-editor-panel" data-bind="visible: isEditorActive">
<div>
<textarea
class="query-editor-text"
data-bind="textInput: queryText,
css: { 'query-editor-text-invalid': hasQueryError },
readOnly: true"
name="query-editor"
rows="5"
cols="100"
></textarea>
</div>
</div>
<!-- Tables Query Tab Query Text - End-->
<!-- Tables Query Tab Query Helper - Start-->
<div data-bind="visible: isHelperActive" style="padding-left:13px">
<div class="clause-table" data-bind="with: queryBuilderViewModel ">
<div class="scroll-box scrollable" id="scroll">
<table class="clause-table">
<thead>
<tr class="clause-table-row">
<th class="clause-table-cell header-background action-header">
<span data-bind="text: actionLabel"></span>
</th>
<th class="clause-table-cell header-background group-control-header">
<button
type="button"
data-bind="enable: canGroupClauses, attr:{title: groupSelectedClauses}, click: groupClauses"
>
<img class="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" />
</button>
</th>
<th class="clause-table-cell header-background"><!-- Grouping indicator --></th>
<th class="clause-table-cell header-background and-or-header">
<span data-bind="text: andLabel"></span>
</th>
<th class="clause-table-cell header-background field-header">
<span data-bind="text: fieldLabel"></span>
</th>
<th class="clause-table-cell header-background type-header">
<span data-bind="text: dataTypeLabel"></span>
</th>
<th class="clause-table-cell header-background operator-header">
<span data-bind="text: operatorLabel"></span>
</th>
<th class="clause-table-cell header-background value-header">
<span data-bind="text: valueLabel"></span>
</th>
</tr>
</thead>
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
</table>
</div>
<div
class="addClause"
role="button"
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
tabindex="0"
>
<div class="addClause-heading">
<span class="clause-table addClause-title">
<img
class="addclauseProperty-Img"
style="margin-bottom:5px;"
src="/Add-property.svg"
alt="Add new clause"
/>
<span style="margin-left:5px;" data-bind="text: addNewClauseLine"></span>
</span>
</div>
</div>
</div>
</div>
<!-- Tables Query Tab Query Helper - End-->
<!-- Tables Query Tab Advanced Options - Start-->
<div class="advanced-options-panel">
<div class="advanced-heading">
<span
class="advanced-title"
role="button"
data-bind="click:toggleAdvancedOptions, event: { keydown: ontoggleAdvancedOptionsKeyDown }, attr:{ 'aria-expanded': isExpanded() ? 'true' : 'false' }"
tabindex="0"
>
<!-- ko template: { ifnot: isExpanded} -->
<div class="themed-images" type="text/html" id="ExpandChevronRight" data-bind="hasFocus: focusExpandIcon">
<img class="imgiconwidth expand-triangle expand-triangle-right " src="/Triangle-right.svg" alt="toggle" />
</div>
<!-- /ko -->
<!-- ko template: { if: isExpanded} -->
<div class="themed-images" type="text/html" id="ExpandChevronDown">
<img class="imgiconwidth expand-triangle" src="/Triangle-down.svg" alt="toggle" />
</div>
<!-- /ko -->
<span>Advanced Options</span>
</span>
</div>
<div class="advanced-options" data-bind="visible: isExpanded">
<div class="top">
<span>Show top results:</span>
<input
class="top-input"
type="number"
data-bind="hasFocus: focusTopResult, textInput: topValue, attr: { title: topValueLimitMessage }"
role="textbox"
aria-label="Show top results"
/>
<div role="alert" aria-atomic="true" class="inline-div" data-bind="visible: isExceedingLimit">
<img class="advanced-options-icon" src="/QueryBuilder/StatusWarning_16x.png" />
<span data-bind="text: topValueLimitMessage"></span>
</div>
</div>
<div class="select">
<span> Select fields for query: </span>
<div data-bind="visible: isSelected">
<img class="advanced-options-icon" src="/QueryBuilder/QueryInformation_16x.png" />
<span class="select-options-text" data-bind="text: selectMessage" />
</div>
<a
class="select-options-link"
data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }"
tabindex="0"
role="link"
>
<span>Choose Columns... </span>
</a>
</div>
</div>
</div>
<!-- Tables Query Tab Advanced Options - End-->
</div>
<!-- Tables Query Tab Query Builder - End-->
<div
class="tablesQueryTab tableContainer"
data-bind="with: tableEntityListViewModel, attr: {
id: tableEntityListViewModel.id
}"
>
<!-- Keyboard navigation - tabindex is required to make the table focusable. -->
<table
id="storageTable"
class="storage azure-table show-gridlines"
tabindex="0"
data-bind="tableSource: items, tableSelection: selected"
></table>
</div>
</div>
<!-- Script for each clause in the tables query builder -->
<script type="text/html" id="queryClause-template">
<tr class="clause-table-row">
<td class="clause-table-cell action-column">
<span class="entity-Add-Cancel" role="button" tabindex="0" data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}">
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause">
</span>
<span class="entity-Add-Cancel" role="button" tabindex="0" data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}">
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause">
</span>
</td>
<td class="clause-table-cell group-control-column">
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping"/>
</td>
<td>
<table class="group-indicator-table">
<tbody>
<tr data-bind="template: { name: 'groupIndicator-template', foreach: $parent.getClauseGroupViewModels($data), as: 'gi' }">
</tr>
</tbody>
</table>
</td>
<td class="clause-table-cell and-or-column">
<select class="clause-table-field and-or-column" data-bind="hasFocus: isAndOrFocused, options: $parent.clauseRules, value: and_or, visible: canAnd, attr:{ 'aria-label': and_or }">
</select>
</td>
<td class="clause-table-cell field-column" data-bind="click: $parent.updateColumnOptions">
<select class="clause-table-field field-column" data-bind="options: $parent.columnOptions, value: field, attr:{ 'aria-label': field }">
</select>
</td>
<td class="clause-table-cell type-column">
<select class="clause-table-field type-column" data-bind="
options: $parent.edmTypes,
enable: isTypeEditable,
value: type,
css: {'query-builder-isDisabled': !isTypeEditable()},
attr: { 'aria-label': type }">
</select>
</td>
<td class="clause-table-cell operator-column">
<select class="clause-table-field operator-column" data-bind="
options: $parent.operators,
enable: isOperaterEditable,
value: operator,
css: {'query-builder-isDisabled': !isOperaterEditable()},
attr: { 'aria-label': operator }">
</select>
</td>
<td class="clause-table-cell value-column">
<!-- ko template: {if: isValue} -->
<input type="text" class="clause-table-field value-column" type="search" data-bind="textInput: value, attr: {'aria-label': $parent.valueLabel}" />
<!-- /ko -->
<!-- ko template: {if: isTimestamp} -->
<select class="clause-table-field time-column" data-bind="options: $parent.timeOptions, value: timeValue">
</select>
<!-- /ko -->
<!-- ko template: {if: isCustomLastTimestamp} -->
<input class="clause-table-field time-column" data-bind="value: customTimeValue, click: customTimestampDialog" />
<!-- /ko -->
<!-- ko template: {if: isCustomRangeTimestamp} -->
<input class="clause-table-field time-column" type="datetime-local" step=1 data-bind="value: customTimeValue" />
<!-- /ko -->
</td>
</tr>
</script>
<!-- Script for each clause group in the tables query builder -->
<script type="text/html" id="groupIndicator-template">
<td class="group-indicator-column" data-bind="style: {backgroundColor: gi.backgroundColor, borderTop: gi.showTopBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderLeft: gi.showLeftBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderBottom: gi.showBottomBorder.peek() ? 'solid thin #CCCCCC' : gi.borderBackgroundColor}">
<!-- ko template: {if: gi.canUngroup} -->
<button type="button" data-bind="click: ungroupClauses, attr: {title: ungroupClausesLabel}">
<img src="/QueryBuilder/UngroupClause_16x.png" alt="Ungroup clauses"/>
</button>
<!-- /ko -->
</td>
</script>
<div
class="tab-pane tableContainer"
data-bind="
attr:{
id: tabId
}"
role="tabpanel"
>
<!-- Tables Query Tab Query Builder - Start-->
<div
class="query-builder"
data-bind="with: queryViewModel, attr: {
id: queryViewModel.id
}"
>
<!-- Tables Query Tab Errors - Start-->
<div class="error-bar">
<div class="error-message" aria-label="Error Message" data-bind="visible: hasQueryError">
<span><img class="entity-error-Img" src="/error_red.svg" /></span>
<span class="error-text" role="alert" data-bind="text: queryErrorMessage"></span>
</div>
</div>
<!-- Tables Query Tab Errors - End-->
<!-- Tables Query Tab Query Text - Start-->
<div class="query-editor-panel" data-bind="visible: isEditorActive">
<div>
<textarea
class="query-editor-text"
data-bind="textInput: queryText,
css: { 'query-editor-text-invalid': hasQueryError },
readOnly: true"
name="query-editor"
rows="5"
cols="100"
></textarea>
</div>
</div>
<!-- Tables Query Tab Query Text - End-->
<!-- Tables Query Tab Query Helper - Start-->
<div data-bind="visible: isHelperActive" style="padding-left: 13px">
<div class="clause-table" data-bind="with: queryBuilderViewModel ">
<div class="scroll-box scrollable" id="scroll">
<table class="clause-table">
<thead>
<tr class="clause-table-row">
<th class="clause-table-cell header-background action-header">
<span data-bind="text: actionLabel"></span>
</th>
<th class="clause-table-cell header-background group-control-header">
<button
type="button"
data-bind="enable: canGroupClauses, attr:{title: groupSelectedClauses}, click: groupClauses"
>
<img class="and-or-svg" src="/And-Or.svg" alt="Group selected clauses" />
</button>
</th>
<th class="clause-table-cell header-background"><!-- Grouping indicator --></th>
<th class="clause-table-cell header-background and-or-header">
<span data-bind="text: andLabel"></span>
</th>
<th class="clause-table-cell header-background field-header">
<span data-bind="text: fieldLabel"></span>
</th>
<th class="clause-table-cell header-background type-header">
<span data-bind="text: dataTypeLabel"></span>
</th>
<th class="clause-table-cell header-background operator-header">
<span data-bind="text: operatorLabel"></span>
</th>
<th class="clause-table-cell header-background value-header">
<span data-bind="text: valueLabel"></span>
</th>
</tr>
</thead>
<tbody data-bind="template: { name: 'queryClause-template', foreach: clauseArray, as: 'clause' }"></tbody>
</table>
</div>
<div
class="addClause"
role="button"
data-bind="click: addNewClause, event: { keydown: onAddNewClauseKeyDown }, attr: { title: addNewClauseLine }"
tabindex="0"
>
<div class="addClause-heading">
<span class="clause-table addClause-title">
<img
class="addclauseProperty-Img"
style="margin-bottom: 5px"
src="/Add-property.svg"
alt="Add new clause"
/>
<span style="margin-left: 5px" data-bind="text: addNewClauseLine"></span>
</span>
</div>
</div>
</div>
</div>
<!-- Tables Query Tab Query Helper - End-->
<!-- Tables Query Tab Advanced Options - Start-->
<div class="advanced-options-panel">
<div class="advanced-heading">
<span
class="advanced-title"
role="button"
data-bind="click:toggleAdvancedOptions, event: { keydown: ontoggleAdvancedOptionsKeyDown }, attr:{ 'aria-expanded': isExpanded() ? 'true' : 'false' }"
tabindex="0"
>
<!-- ko template: { ifnot: isExpanded} -->
<div class="themed-images" type="text/html" id="ExpandChevronRight" data-bind="hasFocus: focusExpandIcon">
<img class="imgiconwidth expand-triangle expand-triangle-right" src="/Triangle-right.svg" alt="toggle" />
</div>
<!-- /ko -->
<!-- ko template: { if: isExpanded} -->
<div class="themed-images" type="text/html" id="ExpandChevronDown">
<img class="imgiconwidth expand-triangle" src="/Triangle-down.svg" alt="toggle" />
</div>
<!-- /ko -->
<span>Advanced Options</span>
</span>
</div>
<div class="advanced-options" data-bind="visible: isExpanded">
<div class="top">
<span>Show top results:</span>
<input
class="top-input"
type="number"
data-bind="hasFocus: focusTopResult, textInput: topValue, attr: { title: topValueLimitMessage }"
role="textbox"
aria-label="Show top results"
/>
<div role="alert" aria-atomic="true" class="inline-div" data-bind="visible: isExceedingLimit">
<img class="advanced-options-icon" src="/QueryBuilder/StatusWarning_16x.png" />
<span data-bind="text: topValueLimitMessage"></span>
</div>
</div>
<div class="select">
<span> Select fields for query: </span>
<div data-bind="visible: isSelected">
<img class="advanced-options-icon" src="/QueryBuilder/QueryInformation_16x.png" />
<span class="select-options-text" data-bind="text: selectMessage" />
</div>
<a
class="select-options-link"
data-bind="click: selectQueryOptions, event: { keydown: onselectQueryOptionsKeyDown }"
tabindex="0"
role="link"
>
<span>Choose Columns... </span>
</a>
</div>
</div>
</div>
<!-- Tables Query Tab Advanced Options - End-->
</div>
<!-- Tables Query Tab Query Builder - End-->
<div
class="tablesQueryTab tableContainer"
data-bind="with: tableEntityListViewModel, attr: {
id: tableEntityListViewModel.id
}"
>
<!-- Keyboard navigation - tabindex is required to make the table focusable. -->
<table
id="storageTable"
class="storage azure-table show-gridlines"
tabindex="0"
data-bind="tableSource: items, tableSelection: selected"
></table>
</div>
</div>
<!-- Script for each clause in the tables query builder -->
<script type="text/html" id="queryClause-template">
<tr class="clause-table-row">
<td class="clause-table-cell action-column">
<span
class="entity-Add-Cancel"
role="button"
tabindex="0"
data-bind="click: $parent.addClauseIndex.bind($data, $index()), event: { keydown: $parent.onAddClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.insertNewFilterLine}"
>
<img class="querybuilder-addpropertyImg" src="/Add-property.svg" alt="Add clause" />
</span>
<span
class="entity-Add-Cancel"
role="button"
tabindex="0"
data-bind="hasFocus: isDeleteButtonFocused, click: $parent.deleteClause.bind($data, $index()), event: { keydown: $parent.onDeleteClauseKeyDown.bind($data, $index()) }, attr:{title: $parent.removeThisFilterLine}"
>
<img class="querybuilder-cancelImg" src="/Entity_cancel.svg" alt="Delete clause" />
</span>
</td>
<td class="clause-table-cell group-control-column">
<input type="checkbox" aria-label="And/Or" data-bind="checked: checkedForGrouping" />
</td>
<td>
<table class="group-indicator-table">
<tbody>
<tr
data-bind="template: { name: 'groupIndicator-template', foreach: $parent.getClauseGroupViewModels($data), as: 'gi' }"
></tr>
</tbody>
</table>
</td>
<td class="clause-table-cell and-or-column">
<select
class="clause-table-field and-or-column"
data-bind="hasFocus: isAndOrFocused, options: $parent.clauseRules, value: and_or, visible: canAnd, attr:{ 'aria-label': and_or }"
></select>
</td>
<td class="clause-table-cell field-column" data-bind="click: $parent.updateColumnOptions">
<select
class="clause-table-field field-column"
data-bind="options: $parent.columnOptions, value: field, attr:{ 'aria-label': field }"
></select>
</td>
<td class="clause-table-cell type-column">
<select
class="clause-table-field type-column"
data-bind="
options: $parent.edmTypes,
enable: isTypeEditable,
value: type,
css: {'query-builder-isDisabled': !isTypeEditable()},
attr: { 'aria-label': type }"
></select>
</td>
<td class="clause-table-cell operator-column">
<select
class="clause-table-field operator-column"
data-bind="
options: $parent.operators,
enable: isOperaterEditable,
value: operator,
css: {'query-builder-isDisabled': !isOperaterEditable()},
attr: { 'aria-label': operator }"
></select>
</td>
<td class="clause-table-cell value-column">
<!-- ko template: {if: isValue} -->
<input
type="text"
class="clause-table-field value-column"
type="search"
data-bind="textInput: value, attr: {'aria-label': $parent.valueLabel}"
/>
<!-- /ko -->
<!-- ko template: {if: isTimestamp} -->
<select
class="clause-table-field time-column"
data-bind="options: $parent.timeOptions, value: timeValue"
></select>
<!-- /ko -->
<!-- ko template: {if: isCustomLastTimestamp} -->
<input class="clause-table-field time-column" data-bind="value: customTimeValue, click: customTimestampDialog" />
<!-- /ko -->
<!-- ko template: {if: isCustomRangeTimestamp} -->
<input class="clause-table-field time-column" type="datetime-local" step="1" data-bind="value: customTimeValue" />
<!-- /ko -->
</td>
</tr>
</script>
<!-- Script for each clause group in the tables query builder -->
<script type="text/html" id="groupIndicator-template">
<td
class="group-indicator-column"
data-bind="style: {backgroundColor: gi.backgroundColor, borderTop: gi.showTopBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderLeft: gi.showLeftBorder.peek() ? 'solid thin #CCCCCC' : 'none', borderBottom: gi.showBottomBorder.peek() ? 'solid thin #CCCCCC' : gi.borderBackgroundColor}"
>
<!-- ko template: {if: gi.canUngroup} -->
<button type="button" data-bind="click: ungroupClauses, attr: {title: ungroupClausesLabel}">
<img src="/QueryBuilder/UngroupClause_16x.png" alt="Ungroup clauses" />
</button>
<!-- /ko -->
</td>
</script>

View File

@@ -1,279 +1,277 @@
import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel";
import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel";
import TableCommands from "../Tables/DataTable/TableCommands";
import { TableDataClient } from "../Tables/TableDataClient";
import QueryBuilderIcon from "../../../images/Query-Builder.svg";
import QueryTextIcon from "../../../images/Query-Text.svg";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import AddEntityIcon from "../../../images/AddEntity.svg";
import EditEntityIcon from "../../../images/Edit-entity.svg";
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
// Will act as table explorer class
export default class QueryTablesTab extends TabsBase {
public collection: ViewModels.Collection;
public tableEntityListViewModel = ko.observable<TableEntityListViewModel>();
public queryViewModel = ko.observable<QueryViewModel>();
public tableCommands: TableCommands;
public tableDataClient: TableDataClient;
public queryText = ko.observable("PartitionKey eq 'partitionKey1'"); // Start out with an example they can modify
public selectedQueryText = ko.observable("").extend({ notify: "always" });
public executeQueryButton: ViewModels.Button;
public addEntityButton: ViewModels.Button;
public editEntityButton: ViewModels.Button;
public deleteEntityButton: ViewModels.Button;
public queryBuilderButton: ViewModels.Button;
public queryTextButton: ViewModels.Button;
public container: Explorer;
constructor(options: ViewModels.TabOptions) {
super(options);
this.container = options.collection && options.collection.container;
this.tableCommands = new TableCommands(this.container);
this.tableDataClient = this.container.tableDataClient;
this.tableEntityListViewModel(new TableEntityListViewModel(this.tableCommands, this));
this.tableEntityListViewModel().queryTablesTab = this;
this.queryViewModel(new QueryViewModel(this));
const sampleQuerySubscription = this.tableEntityListViewModel().items.subscribe(() => {
if (this.tableEntityListViewModel().items().length > 0 && this.container.isPreferredApiTable()) {
this.queryViewModel()
.queryBuilderViewModel()
.setExample();
}
sampleQuerySubscription.dispose();
});
this.executeQueryButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
})
};
this.queryBuilderButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
isSelected: ko.computed<boolean>(() => {
return this.queryViewModel() ? this.queryViewModel().isHelperActive() : false;
})
};
this.queryTextButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
isSelected: ko.computed<boolean>(() => {
return this.queryViewModel() ? this.queryViewModel().isEditorActive() : false;
})
};
this.addEntityButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
})
};
this.editEntityButton = {
enabled: ko.computed<boolean>(() => {
return this.tableCommands.isEnabled(
TableCommands.editEntityCommand,
this.tableEntityListViewModel().selected()
);
}),
visible: ko.computed<boolean>(() => {
return true;
})
};
this.deleteEntityButton = {
enabled: ko.computed<boolean>(() => {
return this.tableCommands.isEnabled(
TableCommands.deleteEntitiesCommand,
this.tableEntityListViewModel().selected()
);
}),
visible: ko.computed<boolean>(() => {
return true;
})
};
this.buildCommandBarOptions();
}
public onExecuteQueryClick = (): Q.Promise<any> => {
this.queryViewModel().runQuery();
return null;
};
public onQueryBuilderClick = (): Q.Promise<any> => {
this.queryViewModel().selectHelper();
return null;
};
public onQueryTextClick = (): Q.Promise<any> => {
this.queryViewModel().selectEditor();
return null;
};
public onAddEntityClick = (): Q.Promise<any> => {
this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel();
this.container.addTableEntityPane.open();
return null;
};
public onEditEntityClick = (): Q.Promise<any> => {
this.tableCommands.editEntityCommand(this.tableEntityListViewModel());
return null;
};
public onDeleteEntityClick = (): Q.Promise<any> => {
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel());
return null;
};
public onActivate(): void {
super.onActivate();
const columns =
!!this.tableEntityListViewModel() &&
!!this.tableEntityListViewModel().table &&
this.tableEntityListViewModel().table.columns;
if (!!columns) {
columns.adjust();
$(window).resize();
}
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (this.queryBuilderButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "CQL Query Builder" : "Query Builder";
buttons.push({
iconSrc: QueryBuilderIcon,
iconAlt: label,
onCommandClick: this.onQueryBuilderClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.queryBuilderButton.enabled(),
isSelected: this.queryBuilderButton.isSelected()
});
}
if (this.queryTextButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "CQL Query Text" : "Query Text";
buttons.push({
iconSrc: QueryTextIcon,
iconAlt: label,
onCommandClick: this.onQueryTextClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.queryTextButton.enabled(),
isSelected: this.queryTextButton.isSelected()
});
}
if (this.executeQueryButton.visible()) {
const label = "Run Query";
buttons.push({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: this.onExecuteQueryClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.executeQueryButton.enabled()
});
}
if (this.addEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Add Row" : "Add Entity";
buttons.push({
iconSrc: AddEntityIcon,
iconAlt: label,
onCommandClick: this.onAddEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.addEntityButton.enabled()
});
}
if (this.editEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Edit Row" : "Edit Entity";
buttons.push({
iconSrc: EditEntityIcon,
iconAlt: label,
onCommandClick: this.onEditEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.editEntityButton.enabled()
});
}
if (this.deleteEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Delete Rows" : "Delete Entities";
buttons.push({
iconSrc: DeleteEntitiesIcon,
iconAlt: label,
onCommandClick: this.onDeleteEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.deleteEntityButton.enabled()
});
}
return buttons;
}
protected buildCommandBarOptions(): void {
ko.computed(() =>
ko.toJSON([
this.queryBuilderButton.visible,
this.queryBuilderButton.enabled,
this.queryTextButton.visible,
this.queryTextButton.enabled,
this.executeQueryButton.visible,
this.executeQueryButton.enabled,
this.addEntityButton.visible,
this.addEntityButton.enabled,
this.editEntityButton.visible,
this.editEntityButton.enabled,
this.deleteEntityButton.visible,
this.deleteEntityButton.enabled
])
).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();
}
}
import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel";
import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel";
import TableCommands from "../Tables/DataTable/TableCommands";
import { TableDataClient } from "../Tables/TableDataClient";
import QueryBuilderIcon from "../../../images/Query-Builder.svg";
import QueryTextIcon from "../../../images/Query-Text.svg";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import AddEntityIcon from "../../../images/AddEntity.svg";
import EditEntityIcon from "../../../images/Edit-entity.svg";
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
// Will act as table explorer class
export default class QueryTablesTab extends TabsBase {
public collection: ViewModels.Collection;
public tableEntityListViewModel = ko.observable<TableEntityListViewModel>();
public queryViewModel = ko.observable<QueryViewModel>();
public tableCommands: TableCommands;
public tableDataClient: TableDataClient;
public queryText = ko.observable("PartitionKey eq 'partitionKey1'"); // Start out with an example they can modify
public selectedQueryText = ko.observable("").extend({ notify: "always" });
public executeQueryButton: ViewModels.Button;
public addEntityButton: ViewModels.Button;
public editEntityButton: ViewModels.Button;
public deleteEntityButton: ViewModels.Button;
public queryBuilderButton: ViewModels.Button;
public queryTextButton: ViewModels.Button;
public container: Explorer;
constructor(options: ViewModels.TabOptions) {
super(options);
this.container = options.collection && options.collection.container;
this.tableCommands = new TableCommands(this.container);
this.tableDataClient = this.container.tableDataClient;
this.tableEntityListViewModel(new TableEntityListViewModel(this.tableCommands, this));
this.tableEntityListViewModel().queryTablesTab = this;
this.queryViewModel(new QueryViewModel(this));
const sampleQuerySubscription = this.tableEntityListViewModel().items.subscribe(() => {
if (this.tableEntityListViewModel().items().length > 0 && this.container.isPreferredApiTable()) {
this.queryViewModel().queryBuilderViewModel().setExample();
}
sampleQuerySubscription.dispose();
});
this.executeQueryButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
};
this.queryBuilderButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
isSelected: ko.computed<boolean>(() => {
return this.queryViewModel() ? this.queryViewModel().isHelperActive() : false;
}),
};
this.queryTextButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
isSelected: ko.computed<boolean>(() => {
return this.queryViewModel() ? this.queryViewModel().isEditorActive() : false;
}),
};
this.addEntityButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
};
this.editEntityButton = {
enabled: ko.computed<boolean>(() => {
return this.tableCommands.isEnabled(
TableCommands.editEntityCommand,
this.tableEntityListViewModel().selected()
);
}),
visible: ko.computed<boolean>(() => {
return true;
}),
};
this.deleteEntityButton = {
enabled: ko.computed<boolean>(() => {
return this.tableCommands.isEnabled(
TableCommands.deleteEntitiesCommand,
this.tableEntityListViewModel().selected()
);
}),
visible: ko.computed<boolean>(() => {
return true;
}),
};
this.buildCommandBarOptions();
}
public onExecuteQueryClick = (): Q.Promise<any> => {
this.queryViewModel().runQuery();
return null;
};
public onQueryBuilderClick = (): Q.Promise<any> => {
this.queryViewModel().selectHelper();
return null;
};
public onQueryTextClick = (): Q.Promise<any> => {
this.queryViewModel().selectEditor();
return null;
};
public onAddEntityClick = (): Q.Promise<any> => {
this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel();
this.container.addTableEntityPane.open();
return null;
};
public onEditEntityClick = (): Q.Promise<any> => {
this.tableCommands.editEntityCommand(this.tableEntityListViewModel());
return null;
};
public onDeleteEntityClick = (): Q.Promise<any> => {
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel());
return null;
};
public onActivate(): void {
super.onActivate();
const columns =
!!this.tableEntityListViewModel() &&
!!this.tableEntityListViewModel().table &&
this.tableEntityListViewModel().table.columns;
if (!!columns) {
columns.adjust();
$(window).resize();
}
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (this.queryBuilderButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "CQL Query Builder" : "Query Builder";
buttons.push({
iconSrc: QueryBuilderIcon,
iconAlt: label,
onCommandClick: this.onQueryBuilderClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.queryBuilderButton.enabled(),
isSelected: this.queryBuilderButton.isSelected(),
});
}
if (this.queryTextButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "CQL Query Text" : "Query Text";
buttons.push({
iconSrc: QueryTextIcon,
iconAlt: label,
onCommandClick: this.onQueryTextClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.queryTextButton.enabled(),
isSelected: this.queryTextButton.isSelected(),
});
}
if (this.executeQueryButton.visible()) {
const label = "Run Query";
buttons.push({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: this.onExecuteQueryClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.executeQueryButton.enabled(),
});
}
if (this.addEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Add Row" : "Add Entity";
buttons.push({
iconSrc: AddEntityIcon,
iconAlt: label,
onCommandClick: this.onAddEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.addEntityButton.enabled(),
});
}
if (this.editEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Edit Row" : "Edit Entity";
buttons.push({
iconSrc: EditEntityIcon,
iconAlt: label,
onCommandClick: this.onEditEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.editEntityButton.enabled(),
});
}
if (this.deleteEntityButton.visible()) {
const label = this.container.isPreferredApiCassandra() ? "Delete Rows" : "Delete Entities";
buttons.push({
iconSrc: DeleteEntitiesIcon,
iconAlt: label,
onCommandClick: this.onDeleteEntityClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: !this.deleteEntityButton.enabled(),
});
}
return buttons;
}
protected buildCommandBarOptions(): void {
ko.computed(() =>
ko.toJSON([
this.queryBuilderButton.visible,
this.queryBuilderButton.enabled,
this.queryTextButton.visible,
this.queryTextButton.enabled,
this.executeQueryButton.visible,
this.executeQueryButton.enabled,
this.addEntityButton.visible,
this.addEntityButton.enabled,
this.editEntityButton.visible,
this.editEntityButton.enabled,
this.deleteEntityButton.visible,
this.deleteEntityButton.enabled,
])
).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();
}
}

View File

@@ -66,7 +66,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
this._setBaselines();
this.id.editableIsValid.subscribe(isValid => {
this.id.editableIsValid.subscribe((isValid) => {
const currentState = this.editorState();
switch (currentState) {
case ViewModels.ScriptEditorState.newValid:
@@ -94,7 +94,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
this.editor = ko.observable<monaco.editor.IStandaloneCodeEditor>();
this.formIsValid = ko.computed<boolean>(() => {
const formIsValid: boolean = this.formFields().every(field => {
const formIsValid: boolean = this.formFields().every((field) => {
return field.editableIsValid();
});
@@ -102,7 +102,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
});
this.formIsDirty = ko.computed<boolean>(() => {
const formIsDirty: boolean = this.formFields().some(field => {
const formIsDirty: boolean = this.formFields().some((field) => {
return field.editableIsDirty();
});
@@ -124,7 +124,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
visible: ko.computed<boolean>(() => {
return this.isNew();
})
}),
};
this.updateButton = {
@@ -142,7 +142,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
visible: ko.computed<boolean>(() => {
return !this.isNew();
})
}),
};
this.discardButton = {
@@ -152,7 +152,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
this.deleteButton = {
@@ -162,7 +162,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
this.executeButton = {
@@ -172,7 +172,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
visible: ko.computed<boolean>(() => {
return true;
})
}),
};
}
@@ -226,7 +226,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.saveButton.enabled()
disabled: !this.saveButton.enabled(),
});
}
@@ -239,7 +239,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.updateButton.enabled()
disabled: !this.updateButton.enabled(),
});
}
@@ -252,7 +252,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.discardButton.enabled()
disabled: !this.discardButton.enabled(),
});
}
return buttons;
@@ -266,7 +266,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
this.updateButton.visible,
this.updateButton.enabled,
this.discardButton.visible,
this.discardButton.enabled
this.discardButton.enabled,
])
).subscribe(() => this.updateNavbarWithTabsButtons());
this.updateNavbarWithTabsButtons();
@@ -324,7 +324,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
const editorPosition: ViewModels.EditorPosition = {
line: i + 1,
column: target - cursor + 1
column: target - cursor + 1,
};
return editorPosition;
@@ -337,7 +337,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
value: this.editorContent(),
language: "javascript",
readOnly: false,
ariaLabel: this.ariaLabel()
ariaLabel: this.ariaLabel(),
};
container.innerHTML = "";
@@ -355,7 +355,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
}
private _setModelMarkers(errors: ViewModels.QueryError[]) {
const markers: monaco.editor.IMarkerData[] = errors.map(e => this._toMarker(e));
const markers: monaco.editor.IMarkerData[] = errors.map((e) => this._toMarker(e));
const editorModel = this.editor().getModel();
monaco.editor.setModelMarkers(editorModel, this.tabId, markers);
}
@@ -378,7 +378,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
startColumn: start.column,
endLineNumber: end.line,
endColumn: end.column,
code: error.code
code: error.code,
};
}
}

View File

@@ -24,7 +24,7 @@ export default class SettingsTabV2 extends TabsBase {
this.options = options;
this.tabId = "SettingsV2-" + this.tabId;
const props: SettingsComponentProps = {
settingsTab: this
settingsTab: this,
};
this.settingsComponentAdapter = new SettingsComponentAdapter(props);
this.currentCollection = this.collection as ViewModels.Collection;
@@ -54,7 +54,7 @@ export default class SettingsTabV2 extends TabsBase {
this.notification = data;
this.notificationRead(true);
},
error => {
(error) => {
const errorMessage = getErrorMessage(error);
this.notification = undefined;
this.notificationRead(true);
@@ -68,7 +68,7 @@ export default class SettingsTabV2 extends TabsBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle,
error: errorMessage,
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
this.options.onLoadStartKey
);

View File

@@ -1,7 +1,7 @@
<div style="width: 100%; height: 100%; margin-left: 3px;" data-bind="attr: { id: tabId }">
<!-- This runs the NotebookApp hosted by DataExplorer -->
<iframe
style="width:100%; height: 100%; border:none"
data-bind="setTemplateReady: true, attr: { src: sparkMasterSrc }, visible: !!sparkMasterSrc()"
></iframe>
</div>
<div style="width: 100%; height: 100%; margin-left: 3px" data-bind="attr: { id: tabId }">
<!-- This runs the NotebookApp hosted by DataExplorer -->
<iframe
style="width: 100%; height: 100%; border: none"
data-bind="setTemplateReady: true, attr: { src: sparkMasterSrc }, visible: !!sparkMasterSrc()"
></iframe>
</div>

View File

@@ -1,35 +1,35 @@
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
interface SparkMasterTabOptions extends ViewModels.TabOptions {
clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
container: Explorer;
}
export default class SparkMasterTab extends TabsBase {
public sparkMasterSrc: ko.Observable<string>;
private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
private _container: Explorer;
constructor(options: SparkMasterTabOptions) {
super(options);
super.onActivate.bind(this);
this._container = options.container;
this._clusterConnectionInfo = options.clusterConnectionInfo;
const sparkMasterEndpoint =
this._clusterConnectionInfo &&
this._clusterConnectionInfo.endpoints &&
this._clusterConnectionInfo.endpoints.find(
endpoint => endpoint.kind === DataModels.SparkClusterEndpointKind.SparkUI
);
this.sparkMasterSrc = ko.observable<string>(sparkMasterEndpoint && sparkMasterEndpoint.endpoint);
}
protected getContainer() {
return this._container;
}
}
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
interface SparkMasterTabOptions extends ViewModels.TabOptions {
clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
container: Explorer;
}
export default class SparkMasterTab extends TabsBase {
public sparkMasterSrc: ko.Observable<string>;
private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
private _container: Explorer;
constructor(options: SparkMasterTabOptions) {
super(options);
super.onActivate.bind(this);
this._container = options.container;
this._clusterConnectionInfo = options.clusterConnectionInfo;
const sparkMasterEndpoint =
this._clusterConnectionInfo &&
this._clusterConnectionInfo.endpoints &&
this._clusterConnectionInfo.endpoints.find(
(endpoint) => endpoint.kind === DataModels.SparkClusterEndpointKind.SparkUI
);
this.sparkMasterSrc = ko.observable<string>(sparkMasterEndpoint && sparkMasterEndpoint.endpoint);
}
protected getContainer() {
return this._container;
}
}

View File

@@ -1,89 +1,89 @@
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- Stored Procedure Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">Stored Procedure Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="Stored procedure id"
placeholder="Enter the new stored procedure id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">Stored Procedure Body</div>
<editor
params="{
content: originalSprocBody,
contentType: 'javascript',
isReadOnly: false,
ariaLabel: 'Stored procedure body',
lineNumbers: 'on',
updatedContent: editorContent,
theme: _theme
}"
data-bind="attr: { id: editorId }"
></editor>
<!-- Results & Errors Content - Start-->
<div class="results-container" data-bind="visible: hasResults">
<div
class="toggles"
id="execute-storedproc-toggles"
aria-label="Successful execution of stored procedure"
data-bind="event: { keydown: onToggleKeyDown }"
tabindex="0"
>
<div class="tab">
<input type="radio" class="radio" value="result" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
aria-label="Result"
>Result</span
>
</div>
<div class="tab">
<input type="radio" class="radio" value="logs" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleLogs, css:{ selectedToggle: isLogsToggled(), unselectedToggle: !isLogsToggled() }"
aria-label="console.log"
>console.log</span
>
</div>
</div>
<json-editor
params="{ content: resultsData, isReadOnly: true, ariaLabel: 'Execute stored procedure result' }"
data-bind="attr: { id: executeResultsEditorId }, visible: hasResults() && isResultToggled()"
>
</json-editor>
<json-editor
params="{ content: logsData, isReadOnly: true, ariaLabel: 'Execute stored procedure logs' }"
data-bind="attr: { id: executeLogsEditorId }, visible: hasResults() && isLogsToggled()"
></json-editor>
</div>
<div class="errors-container" data-bind="visible: hasErrors">
<div class="errors-header">Errors:</div>
<div class="errorContent">
<span class="errorMessage" data-bind="text: error"></span>
<span class="errorDetailsLink">
<a
data-bind="click: $data.onErrorDetailsClick, event: { keypress: $data.onErrorDetailsKeyPress }"
aria-label="Error details link"
>More details</a
>
</span>
</div>
</div>
<!-- Results & Errors Content - End-->
</div>
</div>
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- Stored Procedure Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">Stored Procedure Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="Stored procedure id"
placeholder="Enter the new stored procedure id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">Stored Procedure Body</div>
<editor
params="{
content: originalSprocBody,
contentType: 'javascript',
isReadOnly: false,
ariaLabel: 'Stored procedure body',
lineNumbers: 'on',
updatedContent: editorContent,
theme: _theme
}"
data-bind="attr: { id: editorId }"
></editor>
<!-- Results & Errors Content - Start-->
<div class="results-container" data-bind="visible: hasResults">
<div
class="toggles"
id="execute-storedproc-toggles"
aria-label="Successful execution of stored procedure"
data-bind="event: { keydown: onToggleKeyDown }"
tabindex="0"
>
<div class="tab">
<input type="radio" class="radio" value="result" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
aria-label="Result"
>Result</span
>
</div>
<div class="tab">
<input type="radio" class="radio" value="logs" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleLogs, css:{ selectedToggle: isLogsToggled(), unselectedToggle: !isLogsToggled() }"
aria-label="console.log"
>console.log</span
>
</div>
</div>
<json-editor
params="{ content: resultsData, isReadOnly: true, ariaLabel: 'Execute stored procedure result' }"
data-bind="attr: { id: executeResultsEditorId }, visible: hasResults() && isResultToggled()"
>
</json-editor>
<json-editor
params="{ content: logsData, isReadOnly: true, ariaLabel: 'Execute stored procedure logs' }"
data-bind="attr: { id: executeLogsEditorId }, visible: hasResults() && isLogsToggled()"
></json-editor>
</div>
<div class="errors-container" data-bind="visible: hasErrors">
<div class="errors-header">Errors:</div>
<div class="errorContent">
<span class="errorMessage" data-bind="text: error"></span>
<span class="errorDetailsLink">
<a
data-bind="click: $data.onErrorDetailsClick, event: { keypress: $data.onErrorDetailsKeyPress }"
aria-label="Error details link"
>More details</a
>
</span>
</div>
</div>
<!-- Results & Errors Content - End-->
</div>
</div>

View File

@@ -1,296 +1,296 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import Q from "q";
import * as _ from "underscore";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import * as Constants from "../../Common/Constants";
import { createStoredProcedure } from "../../Common/dataAccess/createStoredProcedure";
import { updateStoredProcedure } from "../../Common/dataAccess/updateStoredProcedure";
import editable from "../../Common/EditableUtility";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import StoredProcedure from "../Tree/StoredProcedure";
import ScriptTabBase from "./ScriptTabBase";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
enum ToggleState {
Result = "result",
Logs = "logs"
}
export default class StoredProcedureTab extends ScriptTabBase {
public collection: ViewModels.Collection;
public node: StoredProcedure;
public executeResultsEditorId: string;
public executeLogsEditorId: string;
public toggleState: ko.Observable<ToggleState>;
public originalSprocBody: ViewModels.Editable<string>;
public resultsData: ko.Observable<string>;
public logsData: ko.Observable<string>;
public error: ko.Observable<string>;
public hasResults: ko.Observable<boolean>;
public hasErrors: ko.Observable<boolean>;
constructor(options: ViewModels.ScriptTabOption) {
super(options);
super.onActivate.bind(this);
this.executeResultsEditorId = `executestoredprocedureresults${this.tabId}`;
this.executeLogsEditorId = `executestoredprocedurelogs${this.tabId}`;
this.toggleState = ko.observable<ToggleState>(ToggleState.Result);
this.originalSprocBody = editable.observable<string>(this.editorContent());
this.resultsData = ko.observable<string>();
this.logsData = ko.observable<string>();
this.error = ko.observable<string>();
this.hasResults = ko.observable<boolean>(false);
this.hasErrors = ko.observable<boolean>(false);
this.error.subscribe((error: string) => {
this.hasErrors(error != null);
this.hasResults(error == null);
});
this.ariaLabel("Stored Procedure Body");
this.buildCommandBarOptions();
}
public onSaveClick = (): Promise<StoredProcedureDefinition & Resource> => {
return this._createStoredProcedure({
id: this.id(),
body: this.editorContent()
});
};
public onDiscard = (): Q.Promise<any> => {
this.setBaselines();
const original = this.editorContent.getEditableOriginalValue();
this.originalSprocBody(original);
this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor
return Q();
};
public onUpdateClick = (): Promise<any> => {
const data = this._getResource();
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateStoredProcedure, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
});
return updateStoredProcedure(this.collection.databaseId, this.collection.id(), data)
.then(
updatedResource => {
this.resource(updatedResource);
this.tabTitle(updatedResource.id);
this.node.id(updatedResource.id);
this.node.body(updatedResource.body as string);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(updatedResource.body as string);
this.editorContent.setBaseline(updatedResource.body as string);
TelemetryProcessor.traceSuccess(
Action.UpdateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
},
startKey
);
},
(error: any) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.UpdateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
},
startKey
);
}
)
.finally(() => this.isExecuting(false));
};
public onExecuteSprocsResult(result: any, logsData: any): void {
const resultData: string = this.renderObjectForEditor(_.omit(result, "scriptLogs").result, null, 4);
const scriptLogs: string = (result.scriptLogs && decodeURIComponent(result.scriptLogs)) || "";
const logs: string = this.renderObjectForEditor(scriptLogs, null, 4);
this.error(null);
this.resultsData(resultData);
this.logsData(logs);
}
public onExecuteSprocsError(error: string): void {
this.isExecutionError(true);
console.error(error);
this.error(error);
}
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.resultsData.valueHasMutated(); // needed to refresh the json-editor component
}
public toggleLogs(): void {
this.toggleState(ToggleState.Logs);
this.logsData.valueHasMutated(); // needed to refresh the json-editor component
}
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.toggleLogs();
event.stopPropagation();
return false;
}
return true;
};
public isResultToggled(): boolean {
return this.toggleState() === ToggleState.Result;
}
public isLogsToggled(): boolean {
return this.toggleState() === ToggleState.Logs;
}
protected updateSelectedNode(): void {
if (this.collection == null) {
return;
}
const database: ViewModels.Database = this.collection.getDatabase();
if (!database.isDatabaseExpanded()) {
this.collection.container.selectedNode(database);
} else if (!this.collection.isCollectionExpanded() || !this.collection.isStoredProceduresExpanded()) {
this.collection.container.selectedNode(this.collection);
} else {
this.collection.container.selectedNode(this.node);
}
}
protected buildCommandBarOptions(): void {
ko.computed(() => ko.toJSON([this.isNew, this.formIsDirty])).subscribe(() => this.updateNavbarWithTabsButtons());
super.buildCommandBarOptions();
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const label = "Execute";
return super.getTabsButtons().concat({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: () => {
this.collection && this.collection.container.executeSprocParamsPane.open();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isNew() || this.formIsDirty()
});
}
private _getResource() {
return {
id: this.id(),
body: this.editorContent()
};
}
private _createStoredProcedure(resource: StoredProcedureDefinition): Promise<StoredProcedureDefinition & Resource> {
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateStoredProcedure, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
});
return createStoredProcedure(this.collection.databaseId, this.collection.id(), resource)
.then(
createdResource => {
this.tabTitle(createdResource.id);
this.isNew(false);
this.resource(createdResource);
this.hashLocation(
`${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${createdResource.id}`
);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(createdResource.body as string);
this.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createStoredProcedureNode(createdResource);
TelemetryProcessor.traceSuccess(
Action.CreateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
},
startKey
);
this.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
return createdResource;
},
createError => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.CreateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError)
},
startKey
);
return Promise.reject(createError);
}
)
.finally(() => this.isExecuting(false));
}
public onDelete(): Q.Promise<any> {
// TODO
return Q();
}
}
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import Q from "q";
import * as _ from "underscore";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import * as Constants from "../../Common/Constants";
import { createStoredProcedure } from "../../Common/dataAccess/createStoredProcedure";
import { updateStoredProcedure } from "../../Common/dataAccess/updateStoredProcedure";
import editable from "../../Common/EditableUtility";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import StoredProcedure from "../Tree/StoredProcedure";
import ScriptTabBase from "./ScriptTabBase";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
enum ToggleState {
Result = "result",
Logs = "logs",
}
export default class StoredProcedureTab extends ScriptTabBase {
public collection: ViewModels.Collection;
public node: StoredProcedure;
public executeResultsEditorId: string;
public executeLogsEditorId: string;
public toggleState: ko.Observable<ToggleState>;
public originalSprocBody: ViewModels.Editable<string>;
public resultsData: ko.Observable<string>;
public logsData: ko.Observable<string>;
public error: ko.Observable<string>;
public hasResults: ko.Observable<boolean>;
public hasErrors: ko.Observable<boolean>;
constructor(options: ViewModels.ScriptTabOption) {
super(options);
super.onActivate.bind(this);
this.executeResultsEditorId = `executestoredprocedureresults${this.tabId}`;
this.executeLogsEditorId = `executestoredprocedurelogs${this.tabId}`;
this.toggleState = ko.observable<ToggleState>(ToggleState.Result);
this.originalSprocBody = editable.observable<string>(this.editorContent());
this.resultsData = ko.observable<string>();
this.logsData = ko.observable<string>();
this.error = ko.observable<string>();
this.hasResults = ko.observable<boolean>(false);
this.hasErrors = ko.observable<boolean>(false);
this.error.subscribe((error: string) => {
this.hasErrors(error != null);
this.hasResults(error == null);
});
this.ariaLabel("Stored Procedure Body");
this.buildCommandBarOptions();
}
public onSaveClick = (): Promise<StoredProcedureDefinition & Resource> => {
return this._createStoredProcedure({
id: this.id(),
body: this.editorContent(),
});
};
public onDiscard = (): Q.Promise<any> => {
this.setBaselines();
const original = this.editorContent.getEditableOriginalValue();
this.originalSprocBody(original);
this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor
return Q();
};
public onUpdateClick = (): Promise<any> => {
const data = this._getResource();
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateStoredProcedure, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return updateStoredProcedure(this.collection.databaseId, this.collection.id(), data)
.then(
(updatedResource) => {
this.resource(updatedResource);
this.tabTitle(updatedResource.id);
this.node.id(updatedResource.id);
this.node.body(updatedResource.body as string);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(updatedResource.body as string);
this.editorContent.setBaseline(updatedResource.body as string);
TelemetryProcessor.traceSuccess(
Action.UpdateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
},
(error: any) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.UpdateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
}
)
.finally(() => this.isExecuting(false));
};
public onExecuteSprocsResult(result: any, logsData: any): void {
const resultData: string = this.renderObjectForEditor(_.omit(result, "scriptLogs").result, null, 4);
const scriptLogs: string = (result.scriptLogs && decodeURIComponent(result.scriptLogs)) || "";
const logs: string = this.renderObjectForEditor(scriptLogs, null, 4);
this.error(null);
this.resultsData(resultData);
this.logsData(logs);
}
public onExecuteSprocsError(error: string): void {
this.isExecutionError(true);
console.error(error);
this.error(error);
}
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.resultsData.valueHasMutated(); // needed to refresh the json-editor component
}
public toggleLogs(): void {
this.toggleState(ToggleState.Logs);
this.logsData.valueHasMutated(); // needed to refresh the json-editor component
}
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.toggleLogs();
event.stopPropagation();
return false;
}
return true;
};
public isResultToggled(): boolean {
return this.toggleState() === ToggleState.Result;
}
public isLogsToggled(): boolean {
return this.toggleState() === ToggleState.Logs;
}
protected updateSelectedNode(): void {
if (this.collection == null) {
return;
}
const database: ViewModels.Database = this.collection.getDatabase();
if (!database.isDatabaseExpanded()) {
this.collection.container.selectedNode(database);
} else if (!this.collection.isCollectionExpanded() || !this.collection.isStoredProceduresExpanded()) {
this.collection.container.selectedNode(this.collection);
} else {
this.collection.container.selectedNode(this.node);
}
}
protected buildCommandBarOptions(): void {
ko.computed(() => ko.toJSON([this.isNew, this.formIsDirty])).subscribe(() => this.updateNavbarWithTabsButtons());
super.buildCommandBarOptions();
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const label = "Execute";
return super.getTabsButtons().concat({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: () => {
this.collection && this.collection.container.executeSprocParamsPane.open();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isNew() || this.formIsDirty(),
});
}
private _getResource() {
return {
id: this.id(),
body: this.editorContent(),
};
}
private _createStoredProcedure(resource: StoredProcedureDefinition): Promise<StoredProcedureDefinition & Resource> {
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateStoredProcedure, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return createStoredProcedure(this.collection.databaseId, this.collection.id(), resource)
.then(
(createdResource) => {
this.tabTitle(createdResource.id);
this.isNew(false);
this.resource(createdResource);
this.hashLocation(
`${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${createdResource.id}`
);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(createdResource.body as string);
this.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createStoredProcedureNode(createdResource);
TelemetryProcessor.traceSuccess(
Action.CreateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
this.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
return createdResource;
},
(createError) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.CreateStoredProcedure,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
return Promise.reject(createError);
}
)
.finally(() => this.isExecuting(false));
}
public onDelete(): Q.Promise<any> {
// TODO
return Q();
}
}

View File

@@ -1,196 +1,196 @@
import DocumentsTabTemplate from "./DocumentsTab.html";
import ConflictsTabTemplate from "./ConflictsTab.html";
import GraphTabTemplate from "./GraphTab.html";
import SparkMasterTabTemplate from "./SparkMasterTab.html";
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
import TerminalTabTemplate from "./TerminalTab.html";
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
import MongoQueryTabTemplate from "./MongoQueryTab.html";
import MongoShellTabTemplate from "./MongoShellTab.html";
import QueryTabTemplate from "./QueryTab.html";
import QueryTablesTabTemplate from "./QueryTablesTab.html";
import SettingsTabV2Template from "./SettingsTabV2.html";
import DatabaseSettingsTabTemplate from "./DatabaseSettingsTab.html";
import StoredProcedureTabTemplate from "./StoredProcedureTab.html";
import TriggerTabTemplate from "./TriggerTab.html";
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
import GalleryTabTemplate from "./GalleryTab.html";
import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
import TabsManagerTemplate from "./TabsManager.html";
export class TabComponent {
constructor(data: any) {
return data.data;
}
}
export class TabsManager {
constructor() {
return {
viewModel: TabComponent,
template: TabsManagerTemplate
};
}
}
export class DocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: DocumentsTabTemplate
};
}
}
export class ConflictsTab {
constructor() {
return {
viewModel: TabComponent,
template: ConflictsTabTemplate
};
}
}
export class GraphTab {
constructor() {
return {
viewModel: TabComponent,
template: GraphTabTemplate
};
}
}
export class SparkMasterTab {
constructor() {
return {
viewModel: TabComponent,
template: SparkMasterTabTemplate
};
}
}
export class NotebookV2Tab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookV2TabTemplate
};
}
}
export class TerminalTab {
constructor() {
return {
viewModel: TabComponent,
template: TerminalTabTemplate
};
}
}
export class MongoDocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoDocumentsTabTemplate
};
}
}
export class MongoQueryTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoQueryTabTemplate
};
}
}
export class MongoShellTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoShellTabTemplate
};
}
}
export class QueryTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTabTemplate
};
}
}
export class QueryTablesTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTablesTabTemplate
};
}
}
export class SettingsTabV2 {
constructor() {
return {
viewModel: TabComponent,
template: SettingsTabV2Template
};
}
}
export class DatabaseSettingsTab {
constructor() {
return {
viewModel: TabComponent,
template: DatabaseSettingsTabTemplate
};
}
}
export class StoredProcedureTab {
constructor() {
return {
viewModel: TabComponent,
template: StoredProcedureTabTemplate
};
}
}
export class TriggerTab {
constructor() {
return {
viewModel: TabComponent,
template: TriggerTabTemplate
};
}
}
export class UserDefinedFunctionTab {
constructor() {
return {
viewModel: TabComponent,
template: UserDefinedFunctionTabTemplate
};
}
}
export class GalleryTab {
constructor() {
return {
viewModel: TabComponent,
template: GalleryTabTemplate
};
}
}
export class NotebookViewerTab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookViewerTabTemplate
};
}
}
import DocumentsTabTemplate from "./DocumentsTab.html";
import ConflictsTabTemplate from "./ConflictsTab.html";
import GraphTabTemplate from "./GraphTab.html";
import SparkMasterTabTemplate from "./SparkMasterTab.html";
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
import TerminalTabTemplate from "./TerminalTab.html";
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
import MongoQueryTabTemplate from "./MongoQueryTab.html";
import MongoShellTabTemplate from "./MongoShellTab.html";
import QueryTabTemplate from "./QueryTab.html";
import QueryTablesTabTemplate from "./QueryTablesTab.html";
import SettingsTabV2Template from "./SettingsTabV2.html";
import DatabaseSettingsTabTemplate from "./DatabaseSettingsTab.html";
import StoredProcedureTabTemplate from "./StoredProcedureTab.html";
import TriggerTabTemplate from "./TriggerTab.html";
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
import GalleryTabTemplate from "./GalleryTab.html";
import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
import TabsManagerTemplate from "./TabsManager.html";
export class TabComponent {
constructor(data: any) {
return data.data;
}
}
export class TabsManager {
constructor() {
return {
viewModel: TabComponent,
template: TabsManagerTemplate,
};
}
}
export class DocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: DocumentsTabTemplate,
};
}
}
export class ConflictsTab {
constructor() {
return {
viewModel: TabComponent,
template: ConflictsTabTemplate,
};
}
}
export class GraphTab {
constructor() {
return {
viewModel: TabComponent,
template: GraphTabTemplate,
};
}
}
export class SparkMasterTab {
constructor() {
return {
viewModel: TabComponent,
template: SparkMasterTabTemplate,
};
}
}
export class NotebookV2Tab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookV2TabTemplate,
};
}
}
export class TerminalTab {
constructor() {
return {
viewModel: TabComponent,
template: TerminalTabTemplate,
};
}
}
export class MongoDocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoDocumentsTabTemplate,
};
}
}
export class MongoQueryTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoQueryTabTemplate,
};
}
}
export class MongoShellTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoShellTabTemplate,
};
}
}
export class QueryTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTabTemplate,
};
}
}
export class QueryTablesTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTablesTabTemplate,
};
}
}
export class SettingsTabV2 {
constructor() {
return {
viewModel: TabComponent,
template: SettingsTabV2Template,
};
}
}
export class DatabaseSettingsTab {
constructor() {
return {
viewModel: TabComponent,
template: DatabaseSettingsTabTemplate,
};
}
}
export class StoredProcedureTab {
constructor() {
return {
viewModel: TabComponent,
template: StoredProcedureTabTemplate,
};
}
}
export class TriggerTab {
constructor() {
return {
viewModel: TabComponent,
template: TriggerTabTemplate,
};
}
}
export class UserDefinedFunctionTab {
constructor() {
return {
viewModel: TabComponent,
template: UserDefinedFunctionTabTemplate,
};
}
}
export class GalleryTab {
constructor() {
return {
viewModel: TabComponent,
template: GalleryTabTemplate,
};
}
}
export class NotebookViewerTab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookViewerTabTemplate,
};
}
}

View File

@@ -1,210 +1,210 @@
import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import ThemeUtility from "../../Common/ThemeUtility";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
// TODO: Use specific actions for logging telemetry data
export default class TabsBase extends WaitsForTemplateViewModel {
public closeTabButton: ViewModels.Button;
public node: ViewModels.TreeNode;
public collection: ViewModels.CollectionBase;
public database: ViewModels.Database;
public rid: string;
public hasFocus: ko.Observable<boolean>;
public isActive: ko.Observable<boolean>;
public isMouseOver: ko.Observable<boolean>;
public tabId: string;
public tabKind: ViewModels.CollectionTabKind;
public tabTitle: ko.Observable<string>;
public tabPath: ko.Observable<string>;
public closeButtonTabIndex: ko.Computed<number>;
public errorDetailsTabIndex: ko.Computed<number>;
public hashLocation: ko.Observable<string>;
public isExecutionError: ko.Observable<boolean>;
public isExecuting: ko.Observable<boolean>;
public pendingNotification?: ko.Observable<DataModels.Notification>;
protected _theme: string;
public onLoadStartKey: number;
constructor(options: ViewModels.TabOptions) {
super();
const id = new Date().getTime().toString();
this._theme = ThemeUtility.getMonacoTheme(options.theme);
this.node = options.node;
this.collection = options.collection;
this.database = options.database;
this.rid = options.rid || (this.collection && this.collection.rid) || "";
this.hasFocus = ko.observable<boolean>(false);
this.isActive = options.isActive || ko.observable<boolean>(false);
this.isMouseOver = ko.observable<boolean>(false);
this.tabId = `tab${id}`;
this.tabKind = options.tabKind;
this.tabTitle = ko.observable<string>(options.title);
this.tabPath =
(options.tabPath && ko.observable<string>(options.tabPath)) ||
(this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.isExecutionError = ko.observable<boolean>(false);
this.isExecuting = ko.observable<boolean>(false);
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
this.onLoadStartKey = options.onLoadStartKey;
this.hashLocation = ko.observable<string>(options.hashLocation || "");
this.hashLocation.subscribe((newLocation: string) => this.updateGlobalHash(newLocation));
this.isActive.subscribe((isActive: boolean) => {
if (isActive) {
this.onActivate();
}
});
this.closeTabButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
})
};
}
public onCloseTabButtonClick(): void {
const explorer = this.getContainer();
explorer.tabsManager.closeTab(this.tabId, explorer);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
tabId: this.tabId
});
}
public onTabClick(): void {
this.getContainer().tabsManager.activateTab(this);
}
protected updateSelectedNode(): void {
const relatedDatabase = (this.collection && this.collection.getDatabase()) || this.database;
if (relatedDatabase && !relatedDatabase.isDatabaseExpanded()) {
this.getContainer().selectedNode(relatedDatabase);
} else if (this.collection && !this.collection.isCollectionExpanded()) {
this.getContainer().selectedNode(this.collection);
} else {
this.getContainer().selectedNode(this.node);
}
}
private onSpaceOrEnterKeyPress(event: KeyboardEvent, callback: () => void): boolean {
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
callback();
event.stopPropagation();
return false;
}
return true;
}
public onKeyPressActivate = (source: any, event: KeyboardEvent): boolean => {
return this.onSpaceOrEnterKeyPress(event, () => this.onTabClick());
};
public onKeyPressClose = (source: any, event: KeyboardEvent): boolean => {
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
};
public onActivate(): void {
this.updateSelectedNode();
if (!!this.collection) {
this.collection.selectedSubnodeKind(this.tabKind);
}
if (!!this.database) {
this.database.selectedSubnodeKind(this.tabKind);
}
this.hasFocus(true);
this.updateGlobalHash(this.hashLocation());
this.updateNavbarWithTabsButtons();
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
tabId: this.tabId
});
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
if (this.collection && this.collection.container) {
this.collection.container.expandConsole();
}
if (this.database && this.database.container) {
this.database.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 refresh(): Q.Promise<any> {
location.reload();
return Q();
}
protected getContainer(): Explorer {
return (this.collection && this.collection.container) || (this.database && this.database.container);
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return JSON.stringify(value, replacer, space);
}
private updateGlobalHash(newHash: string): void {
RouteHandler.getInstance().updateRouteHashLocation(newHash);
}
/**
* @return buttons that are displayed in the navbar
*/
protected getTabsButtons(): CommandButtonComponentProps[] {
return [];
}
protected updateNavbarWithTabsButtons = (): void => {
if (this.isActive()) {
this.getContainer().onUpdateTabsButtons(this.getTabsButtons());
}
};
}
interface EditorPosition {
line: number;
column: number;
}
import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import ThemeUtility from "../../Common/ThemeUtility";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
// TODO: Use specific actions for logging telemetry data
export default class TabsBase extends WaitsForTemplateViewModel {
public closeTabButton: ViewModels.Button;
public node: ViewModels.TreeNode;
public collection: ViewModels.CollectionBase;
public database: ViewModels.Database;
public rid: string;
public hasFocus: ko.Observable<boolean>;
public isActive: ko.Observable<boolean>;
public isMouseOver: ko.Observable<boolean>;
public tabId: string;
public tabKind: ViewModels.CollectionTabKind;
public tabTitle: ko.Observable<string>;
public tabPath: ko.Observable<string>;
public closeButtonTabIndex: ko.Computed<number>;
public errorDetailsTabIndex: ko.Computed<number>;
public hashLocation: ko.Observable<string>;
public isExecutionError: ko.Observable<boolean>;
public isExecuting: ko.Observable<boolean>;
public pendingNotification?: ko.Observable<DataModels.Notification>;
protected _theme: string;
public onLoadStartKey: number;
constructor(options: ViewModels.TabOptions) {
super();
const id = new Date().getTime().toString();
this._theme = ThemeUtility.getMonacoTheme(options.theme);
this.node = options.node;
this.collection = options.collection;
this.database = options.database;
this.rid = options.rid || (this.collection && this.collection.rid) || "";
this.hasFocus = ko.observable<boolean>(false);
this.isActive = options.isActive || ko.observable<boolean>(false);
this.isMouseOver = ko.observable<boolean>(false);
this.tabId = `tab${id}`;
this.tabKind = options.tabKind;
this.tabTitle = ko.observable<string>(options.title);
this.tabPath =
(options.tabPath && ko.observable<string>(options.tabPath)) ||
(this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.isExecutionError = ko.observable<boolean>(false);
this.isExecuting = ko.observable<boolean>(false);
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
this.onLoadStartKey = options.onLoadStartKey;
this.hashLocation = ko.observable<string>(options.hashLocation || "");
this.hashLocation.subscribe((newLocation: string) => this.updateGlobalHash(newLocation));
this.isActive.subscribe((isActive: boolean) => {
if (isActive) {
this.onActivate();
}
});
this.closeTabButton = {
enabled: ko.computed<boolean>(() => {
return true;
}),
visible: ko.computed<boolean>(() => {
return true;
}),
};
}
public onCloseTabButtonClick(): void {
const explorer = this.getContainer();
explorer.tabsManager.closeTab(this.tabId, explorer);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
tabId: this.tabId,
});
}
public onTabClick(): void {
this.getContainer().tabsManager.activateTab(this);
}
protected updateSelectedNode(): void {
const relatedDatabase = (this.collection && this.collection.getDatabase()) || this.database;
if (relatedDatabase && !relatedDatabase.isDatabaseExpanded()) {
this.getContainer().selectedNode(relatedDatabase);
} else if (this.collection && !this.collection.isCollectionExpanded()) {
this.getContainer().selectedNode(this.collection);
} else {
this.getContainer().selectedNode(this.node);
}
}
private onSpaceOrEnterKeyPress(event: KeyboardEvent, callback: () => void): boolean {
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
callback();
event.stopPropagation();
return false;
}
return true;
}
public onKeyPressActivate = (source: any, event: KeyboardEvent): boolean => {
return this.onSpaceOrEnterKeyPress(event, () => this.onTabClick());
};
public onKeyPressClose = (source: any, event: KeyboardEvent): boolean => {
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
};
public onActivate(): void {
this.updateSelectedNode();
if (!!this.collection) {
this.collection.selectedSubnodeKind(this.tabKind);
}
if (!!this.database) {
this.database.selectedSubnodeKind(this.tabKind);
}
this.hasFocus(true);
this.updateGlobalHash(this.hashLocation());
this.updateNavbarWithTabsButtons();
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
tabId: this.tabId,
});
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
if (this.collection && this.collection.container) {
this.collection.container.expandConsole();
}
if (this.database && this.database.container) {
this.database.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 refresh(): Q.Promise<any> {
location.reload();
return Q();
}
protected getContainer(): Explorer {
return (this.collection && this.collection.container) || (this.database && this.database.container);
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return JSON.stringify(value, replacer, space);
}
private updateGlobalHash(newHash: string): void {
RouteHandler.getInstance().updateRouteHashLocation(newHash);
}
/**
* @return buttons that are displayed in the navbar
*/
protected getTabsButtons(): CommandButtonComponentProps[] {
return [];
}
protected updateNavbarWithTabsButtons = (): void => {
if (this.isActive()) {
this.getContainer().onUpdateTabsButtons(this.getTabsButtons());
}
};
}
interface EditorPosition {
line: number;
column: number;
}

View File

@@ -24,13 +24,13 @@ describe("Tabs manager tests", () => {
type: "",
kind: "",
tags: "",
properties: undefined
properties: undefined,
});
database = {
container: explorer,
id: ko.observable<string>("test"),
isDatabaseShared: () => false
isDatabaseShared: () => false,
} as ViewModels.Database;
database.isDatabaseExpanded = ko.observable<boolean>(true);
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
@@ -38,7 +38,7 @@ describe("Tabs manager tests", () => {
collection = {
container: explorer,
databaseId: "test",
id: ko.observable<string>("test")
id: ko.observable<string>("test"),
} as ViewModels.Collection;
collection.getDatabase = (): ViewModels.Database => database;
collection.isCollectionExpanded = ko.observable<boolean>(true);
@@ -52,7 +52,7 @@ describe("Tabs manager tests", () => {
tabPath: "",
isActive: ko.observable<boolean>(false),
hashLocation: "",
onUpdateTabsButtons: undefined
onUpdateTabsButtons: undefined,
});
documentsTab = new DocumentsTab({
@@ -64,7 +64,7 @@ describe("Tabs manager tests", () => {
tabPath: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: undefined
onUpdateTabsButtons: undefined,
});
// make sure tabs have different tabId
@@ -112,7 +112,7 @@ describe("Tabs manager tests", () => {
const documentsTabs = tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
tab => tab.tabId === documentsTab.tabId
(tab) => tab.tabId === documentsTab.tabId
);
expect(documentsTabs.length).toBe(1);
expect(documentsTabs[0]).toEqual(documentsTab);
@@ -129,7 +129,7 @@ describe("Tabs manager tests", () => {
expect(queryTab.isActive()).toBe(true);
expect(documentsTab.isActive()).toBe(false);
tabsManager.closeTabsByComparator(tab => tab.tabId === queryTab.tabId);
tabsManager.closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
expect(tabsManager.openedTabs().length).toBe(0);
expect(tabsManager.activeTab()).toEqual(undefined);
expect(queryTab.isActive()).toBe(false);

View File

@@ -90,6 +90,6 @@ function TabsManagerWrapperViewModel(params: { data: TabsManager }) {
export function TabsManagerKOComponent(): unknown {
return {
viewModel: TabsManagerWrapperViewModel,
template: TabsManagerTemplate
template: TabsManagerTemplate,
};
}

View File

@@ -91,7 +91,7 @@ export default class TerminalTab extends TabsBase {
const info: DataModels.NotebookWorkspaceConnectionInfo = options.container.notebookServerInfo();
return {
authToken: info.authToken,
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
};
}
}

View File

@@ -1,39 +1,39 @@
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- Trigger Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">Trigger Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Enter the new trigger id"
size="40"
data-bind="textInput: id"
aria-label="Trigger Id"
/>
</span>
<div class="formTitleFirst">Trigger Type</div>
<span class="formTitleTextbox">
<select class="formTree" required data-bind="value: triggerType" aria-label="Trigger Type">
<option>Pre</option>
<option>Post</option>
</select>
</span>
<div class="formTitleFirst">Trigger Operation</div>
<span class="formTitleTextbox">
<select class="formTree" required data-bind="value: triggerOperation" aria-label="Trigger Operation">
<option>All</option>
<option>Create</option>
<option>Delete</option>
<option>Replace</option>
</select>
</span>
<div class="spUdfTriggerHeader">Trigger Body</div>
<div class="storedUdfTriggerEditor " data-bind=" setTemplateReady: true, attr: { id: editorId } "></div>
</div>
<!-- Trigger Tab Form - End -->
</div>
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- Trigger Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">Trigger Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Enter the new trigger id"
size="40"
data-bind="textInput: id"
aria-label="Trigger Id"
/>
</span>
<div class="formTitleFirst">Trigger Type</div>
<span class="formTitleTextbox">
<select class="formTree" required data-bind="value: triggerType" aria-label="Trigger Type">
<option>Pre</option>
<option>Post</option>
</select>
</span>
<div class="formTitleFirst">Trigger Operation</div>
<span class="formTitleTextbox">
<select class="formTree" required data-bind="value: triggerOperation" aria-label="Trigger Operation">
<option>All</option>
<option>Create</option>
<option>Delete</option>
<option>Replace</option>
</select>
</span>
<div class="spUdfTriggerHeader">Trigger Body</div>
<div class="storedUdfTriggerEditor" data-bind=" setTemplateReady: true, attr: { id: editorId } "></div>
</div>
<!-- Trigger Tab Form - End -->
</div>

View File

@@ -33,7 +33,7 @@ export default class TriggerTab extends ScriptTabBase {
id: this.id(),
body: this.editorContent(),
triggerOperation: this.triggerOperation() as TriggerOperation,
triggerType: this.triggerType() as TriggerType
triggerType: this.triggerType() as TriggerType,
});
};
@@ -44,17 +44,17 @@ export default class TriggerTab extends ScriptTabBase {
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateTrigger, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
return updateTrigger(this.collection.databaseId, this.collection.id(), {
id: this.id(),
body: this.editorContent(),
triggerOperation: this.triggerOperation() as TriggerOperation,
triggerType: this.triggerType() as TriggerType
triggerType: this.triggerType() as TriggerType,
})
.then(
createdResource => {
(createdResource) => {
this.resource(createdResource);
this.tabTitle(createdResource.id);
@@ -68,7 +68,7 @@ export default class TriggerTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
@@ -89,7 +89,7 @@ export default class TriggerTab extends ScriptTabBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError)
errorStack: getErrorStack(createError),
},
startKey
);
@@ -128,12 +128,12 @@ export default class TriggerTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
return createTrigger(this.collection.databaseId, this.collection.id(), resource)
.then(
createdResource => {
(createdResource) => {
this.tabTitle(createdResource.id);
this.isNew(false);
this.resource(createdResource);
@@ -156,7 +156,7 @@ export default class TriggerTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
@@ -173,7 +173,7 @@ export default class TriggerTab extends ScriptTabBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError)
errorStack: getErrorStack(createError),
},
startKey
);
@@ -188,7 +188,7 @@ export default class TriggerTab extends ScriptTabBase {
id: this.id(),
body: this.editorContent(),
triggerOperation: this.triggerOperation(),
triggerType: this.triggerType()
triggerType: this.triggerType(),
};
}
}

View File

@@ -1,30 +1,30 @@
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- User Defined Function Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">User Defined Function Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="User defined function id"
placeholder="Enter the new user defined function id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">User Defined Function Body</div>
<div
class="storedUdfTriggerEditor"
data-bind="
setTemplateReady: true,
attr: {
id: editorId
}"
></div>
</div>
<!-- User Defined Function Tab Form - End -->
</div>
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- User Defined Function Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">User Defined Function Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="User defined function id"
placeholder="Enter the new user defined function id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">User Defined Function Body</div>
<div
class="storedUdfTriggerEditor"
data-bind="
setTemplateReady: true,
attr: {
id: editorId
}"
></div>
</div>
<!-- User Defined Function Tab Form - End -->
</div>

View File

@@ -33,12 +33,12 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
return updateUserDefinedFunction(this.collection.databaseId, this.collection.id(), data)
.then(
createdResource => {
(createdResource) => {
this.resource(createdResource);
this.tabTitle(createdResource.id);
@@ -50,7 +50,7 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
@@ -71,7 +71,7 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError)
errorStack: getErrorStack(createError),
},
startKey
);
@@ -104,12 +104,12 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
});
return createUserDefinedFunction(this.collection.databaseId, this.collection.id(), resource)
.then(
createdResource => {
(createdResource) => {
this.tabTitle(createdResource.id);
this.isNew(false);
this.resource(createdResource);
@@ -131,7 +131,7 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
dataExplorerArea: Constants.Areas.Tab,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
tabTitle: this.tabTitle()
tabTitle: this.tabTitle(),
},
startKey
);
@@ -148,7 +148,7 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError)
errorStack: getErrorStack(createError),
},
startKey
);
@@ -163,7 +163,7 @@ export default class UserDefinedFunctionTab extends ScriptTabBase {
_rid: this.resource()._rid,
_self: this.resource()._self,
id: this.id(),
body: this.editorContent()
body: this.editorContent(),
};
return resource;