mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 09:20:16 +00:00
Prettier 2.0 (#393)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} /> : <></>;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -90,6 +90,6 @@ function TabsManagerWrapperViewModel(params: { data: TabsManager }) {
|
||||
export function TabsManagerKOComponent(): unknown {
|
||||
return {
|
||||
viewModel: TabsManagerWrapperViewModel,
|
||||
template: TabsManagerTemplate
|
||||
template: TabsManagerTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user