Prettier 2.0 (#393)

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

View File

@@ -1,123 +1,123 @@
import * as _ from "underscore";
import * as ko from "knockout";
enum ScrollPosition {
Top,
Bottom
}
export class AccessibleVerticalList {
private items: any[] = [];
private onSelect?: (item: any) => void;
public currentItemIndex: ko.Observable<number>;
public currentItem: ko.Computed<any>;
constructor(initialSetOfItems: any[]) {
this.items = initialSetOfItems;
this.currentItemIndex = this.items != null && this.items.length > 0 ? ko.observable(0) : ko.observable(-1);
this.currentItem = ko.computed<any>(() => this.items[this.currentItemIndex()]);
}
public setOnSelect(onSelect: (item: any) => void): void {
this.onSelect = onSelect;
}
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
const targetContainer: Element = <Element>event.target;
if (this.items == null || this.items.length === 0) {
// no items so this should be a noop
return true;
}
if (event.keyCode === 32 || event.keyCode === 13) {
// on space or enter keydown
this.onSelect && this.onSelect(this.currentItem());
event.stopPropagation();
return false;
}
if (event.keyCode === 38) {
// on UpArrow keydown
event.preventDefault();
this.selectPreviousItem();
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
if (event.keyCode === 40) {
// on DownArrow keydown
event.preventDefault();
this.selectNextItem();
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
return true;
};
public updateItemList(newItemList: any[]) {
if (newItemList == null || newItemList.length === 0) {
this.currentItemIndex(-1);
this.items = [];
return;
} else if (this.currentItemIndex() < 0) {
this.currentItemIndex(0);
}
this.items = newItemList;
}
public updateCurrentItem(item: any) {
const updatedIndex: number = this.isItemListEmpty() ? -1 : _.indexOf(this.items, item);
this.currentItemIndex(updatedIndex);
}
private isElementVisibleInContainer(element: Element, container: Element): boolean {
const elementTop = element.getBoundingClientRect().top;
const elementBottom = element.getBoundingClientRect().bottom;
const containerTop = container.getBoundingClientRect().top;
const containerBottom = container.getBoundingClientRect().bottom;
return elementTop >= containerTop && elementBottom <= containerBottom;
}
private scrollElementIntoContainerViewIfNeeded(
element: Element,
container: Element,
scrollPosition: ScrollPosition
): void {
if (!this.isElementVisibleInContainer(element, container)) {
if (scrollPosition === ScrollPosition.Top) {
container.scrollTop =
element.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
} else {
container.scrollTop =
element.getBoundingClientRect().bottom - element.getBoundingClientRect().top + container.scrollTop;
}
}
}
private selectPreviousItem(): void {
if (this.currentItemIndex() <= 0 || this.isItemListEmpty()) {
return;
}
this.currentItemIndex(this.currentItemIndex() - 1);
}
private selectNextItem(): void {
if (this.isItemListEmpty() || this.currentItemIndex() === this.items.length - 1) {
return;
}
this.currentItemIndex(this.currentItemIndex() + 1);
}
private isItemListEmpty(): boolean {
return this.items == null || this.items.length === 0;
}
}
import * as _ from "underscore";
import * as ko from "knockout";
enum ScrollPosition {
Top,
Bottom,
}
export class AccessibleVerticalList {
private items: any[] = [];
private onSelect?: (item: any) => void;
public currentItemIndex: ko.Observable<number>;
public currentItem: ko.Computed<any>;
constructor(initialSetOfItems: any[]) {
this.items = initialSetOfItems;
this.currentItemIndex = this.items != null && this.items.length > 0 ? ko.observable(0) : ko.observable(-1);
this.currentItem = ko.computed<any>(() => this.items[this.currentItemIndex()]);
}
public setOnSelect(onSelect: (item: any) => void): void {
this.onSelect = onSelect;
}
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
const targetContainer: Element = <Element>event.target;
if (this.items == null || this.items.length === 0) {
// no items so this should be a noop
return true;
}
if (event.keyCode === 32 || event.keyCode === 13) {
// on space or enter keydown
this.onSelect && this.onSelect(this.currentItem());
event.stopPropagation();
return false;
}
if (event.keyCode === 38) {
// on UpArrow keydown
event.preventDefault();
this.selectPreviousItem();
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
if (event.keyCode === 40) {
// on DownArrow keydown
event.preventDefault();
this.selectNextItem();
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
return true;
};
public updateItemList(newItemList: any[]) {
if (newItemList == null || newItemList.length === 0) {
this.currentItemIndex(-1);
this.items = [];
return;
} else if (this.currentItemIndex() < 0) {
this.currentItemIndex(0);
}
this.items = newItemList;
}
public updateCurrentItem(item: any) {
const updatedIndex: number = this.isItemListEmpty() ? -1 : _.indexOf(this.items, item);
this.currentItemIndex(updatedIndex);
}
private isElementVisibleInContainer(element: Element, container: Element): boolean {
const elementTop = element.getBoundingClientRect().top;
const elementBottom = element.getBoundingClientRect().bottom;
const containerTop = container.getBoundingClientRect().top;
const containerBottom = container.getBoundingClientRect().bottom;
return elementTop >= containerTop && elementBottom <= containerBottom;
}
private scrollElementIntoContainerViewIfNeeded(
element: Element,
container: Element,
scrollPosition: ScrollPosition
): void {
if (!this.isElementVisibleInContainer(element, container)) {
if (scrollPosition === ScrollPosition.Top) {
container.scrollTop =
element.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
} else {
container.scrollTop =
element.getBoundingClientRect().bottom - element.getBoundingClientRect().top + container.scrollTop;
}
}
}
private selectPreviousItem(): void {
if (this.currentItemIndex() <= 0 || this.isItemListEmpty()) {
return;
}
this.currentItemIndex(this.currentItemIndex() - 1);
}
private selectNextItem(): void {
if (this.isItemListEmpty() || this.currentItemIndex() === this.items.length - 1) {
return;
}
this.currentItemIndex(this.currentItemIndex() + 1);
}
private isItemListEmpty(): boolean {
return this.items == null || this.items.length === 0;
}
}

View File

@@ -1,112 +1,112 @@
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import Collection from "./Collection";
import Explorer from "../Explorer";
jest.mock("monaco-editor");
describe("Collection", () => {
function generateCollection(
container: Explorer,
databaseId: string,
data: DataModels.Collection,
offer: DataModels.Offer
): Collection {
return new Collection(container, databaseId, data);
}
function generateMockCollectionsDataModelWithPartitionKey(
partitionKey: DataModels.PartitionKey
): DataModels.Collection {
return {
defaultTtl: 1,
indexingPolicy: {} as DataModels.IndexingPolicy,
partitionKey,
_rid: "",
_self: "",
_etag: "",
_ts: 1,
id: ""
};
}
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
const mockContainer = {} as Explorer;
mockContainer.isPreferredApiMongoDB = ko.computed(() => {
return false;
});
mockContainer.isPreferredApiCassandra = ko.computed(() => {
return false;
});
mockContainer.isDatabaseNodeOrNoneSelected = () => {
return false;
};
mockContainer.isPreferredApiDocumentDB = ko.computed(() => {
return true;
});
mockContainer.isPreferredApiGraph = ko.computed(() => {
return false;
});
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
}
describe("Partition key path parsing", () => {
let collection: Collection;
it("should strip out multiple forward slashes from partition key paths", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey/anotherPartitionKey"],
kind: "Hash",
version: 2
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyProperty).toBe("somePartitionKey.anotherPartitionKey");
});
it("should strip out forward slashes from single partition key paths", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey"],
kind: "Hash",
version: 2
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyProperty).toBe("somePartitionKey");
});
});
describe("Partition key path header", () => {
let collection: Collection;
it("should preserve forward slashes on partition keys", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey/anotherPartitionKey"],
kind: "Hash",
version: 2
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey/anotherPartitionKey");
});
it("should preserve forward slash on a single partition key", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey"],
kind: "Hash",
version: 2
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey");
});
it("should be null if there is no partition key", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
version: 2,
paths: [],
kind: "Hash"
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBeNull;
});
});
});
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import Collection from "./Collection";
import Explorer from "../Explorer";
jest.mock("monaco-editor");
describe("Collection", () => {
function generateCollection(
container: Explorer,
databaseId: string,
data: DataModels.Collection,
offer: DataModels.Offer
): Collection {
return new Collection(container, databaseId, data);
}
function generateMockCollectionsDataModelWithPartitionKey(
partitionKey: DataModels.PartitionKey
): DataModels.Collection {
return {
defaultTtl: 1,
indexingPolicy: {} as DataModels.IndexingPolicy,
partitionKey,
_rid: "",
_self: "",
_etag: "",
_ts: 1,
id: "",
};
}
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
const mockContainer = {} as Explorer;
mockContainer.isPreferredApiMongoDB = ko.computed(() => {
return false;
});
mockContainer.isPreferredApiCassandra = ko.computed(() => {
return false;
});
mockContainer.isDatabaseNodeOrNoneSelected = () => {
return false;
};
mockContainer.isPreferredApiDocumentDB = ko.computed(() => {
return true;
});
mockContainer.isPreferredApiGraph = ko.computed(() => {
return false;
});
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
}
describe("Partition key path parsing", () => {
let collection: Collection;
it("should strip out multiple forward slashes from partition key paths", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey/anotherPartitionKey"],
kind: "Hash",
version: 2,
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyProperty).toBe("somePartitionKey.anotherPartitionKey");
});
it("should strip out forward slashes from single partition key paths", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey"],
kind: "Hash",
version: 2,
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyProperty).toBe("somePartitionKey");
});
});
describe("Partition key path header", () => {
let collection: Collection;
it("should preserve forward slashes on partition keys", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey/anotherPartitionKey"],
kind: "Hash",
version: 2,
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey/anotherPartitionKey");
});
it("should preserve forward slash on a single partition key", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
paths: ["/somePartitionKey"],
kind: "Hash",
version: 2,
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey");
});
it("should be null if there is no partition key", () => {
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
version: 2,
paths: [],
kind: "Hash",
});
collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBeNull;
});
});
});

View File

@@ -122,10 +122,7 @@ export default class Collection implements ViewModels.Collection {
this.partitionKey.paths &&
this.partitionKey.paths.length &&
this.partitionKey.paths.length > 0 &&
this.partitionKey.paths[0]
.replace(/[/]+/g, ".")
.substr(1)
.replace(/[']+/g, "")) ||
this.partitionKey.paths[0].replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "")) ||
null;
this.partitionKeyPropertyHeader =
(this.partitionKey &&
@@ -155,28 +152,28 @@ export default class Collection implements ViewModels.Collection {
this.focusedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
this.documentsFocused = ko.observable<boolean>();
this.documentsFocused.subscribe(focus => {
this.documentsFocused.subscribe((focus) => {
console.log("Focus set on Documents: " + focus);
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Documents);
});
this.settingsFocused = ko.observable<boolean>(false);
this.settingsFocused.subscribe(focus => {
this.settingsFocused.subscribe((focus) => {
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Settings);
});
this.storedProceduresFocused = ko.observable<boolean>(false);
this.storedProceduresFocused.subscribe(focus => {
this.storedProceduresFocused.subscribe((focus) => {
this.focusedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
});
this.userDefinedFunctionsFocused = ko.observable<boolean>(false);
this.userDefinedFunctionsFocused.subscribe(focus => {
this.userDefinedFunctionsFocused.subscribe((focus) => {
this.focusedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
});
this.triggersFocused = ko.observable<boolean>(false);
this.triggersFocused.subscribe(focus => {
this.triggersFocused.subscribe((focus) => {
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
});
@@ -184,20 +181,20 @@ export default class Collection implements ViewModels.Collection {
this.storedProcedures = ko.computed(() => {
return this.children()
.filter(node => node.nodeKind === "StoredProcedure")
.map(node => <StoredProcedure>node);
.filter((node) => node.nodeKind === "StoredProcedure")
.map((node) => <StoredProcedure>node);
});
this.userDefinedFunctions = ko.computed(() => {
return this.children()
.filter(node => node.nodeKind === "UserDefinedFunction")
.map(node => <UserDefinedFunction>node);
.filter((node) => node.nodeKind === "UserDefinedFunction")
.map((node) => <UserDefinedFunction>node);
});
this.triggers = ko.computed(() => {
return this.children()
.filter(node => node.nodeKind === "Trigger")
.map(node => <Trigger>node);
.filter((node) => node.nodeKind === "Trigger")
.map((node) => <Trigger>node);
});
const showScriptsMenus: boolean = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
@@ -228,7 +225,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
if (this.isCollectionExpanded()) {
this.collapseCollection();
@@ -237,7 +234,7 @@ export default class Collection implements ViewModels.Collection {
}
this.container.onUpdateTabsButtons([]);
this.container.tabsManager.refreshActiveTab(
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
);
}
@@ -253,7 +250,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
@@ -269,7 +266,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
return Q.resolve();
@@ -284,12 +281,12 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
@@ -302,7 +299,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Items"
tabTitle: "Items",
});
this.documentIds([]);
@@ -317,7 +314,7 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(documentsTab);
@@ -333,12 +330,12 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Conflicts,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as ConflictsTab[];
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
@@ -351,7 +348,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Conflicts"
tabTitle: "Conflicts",
});
this.documentIds([]);
@@ -366,7 +363,7 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(conflictsTab);
@@ -382,7 +379,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
if (this.container.isPreferredApiCassandra() && !this.cassandraKeys) {
@@ -393,7 +390,7 @@ export default class Collection implements ViewModels.Collection {
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.QueryTables,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as QueryTablesTab[];
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
@@ -411,7 +408,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
queryTablesTab = new QueryTablesTab({
@@ -425,7 +422,7 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTablesTab);
@@ -441,12 +438,12 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Graph,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as GraphTab[];
let graphTab: GraphTab = graphTabs && graphTabs[0];
@@ -461,7 +458,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
graphTab = new GraphTab({
@@ -480,7 +477,7 @@ export default class Collection implements ViewModels.Collection {
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(graphTab);
@@ -496,12 +493,12 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as MongoDocumentsTab[];
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
@@ -514,7 +511,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Documents"
tabTitle: "Documents",
});
this.documentIds([]);
@@ -531,7 +528,7 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
}
@@ -546,12 +543,12 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, tab => {
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => {
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
});
@@ -561,7 +558,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: tabTitle
tabTitle: tabTitle,
};
const settingsTabOptions: ViewModels.TabOptions = {
@@ -572,7 +569,7 @@ export default class Collection implements ViewModels.Collection {
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
};
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
@@ -587,7 +584,7 @@ export default class Collection implements ViewModels.Collection {
): void => {
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
...settingsTabOptions,
getPendingNotification: getPendingNotification
getPendingNotification: getPendingNotification,
};
if (!settingsTabV2) {
@@ -611,7 +608,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
const queryTab: QueryTab = new QueryTab({
@@ -625,7 +622,7 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTab);
@@ -642,7 +639,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
@@ -655,7 +652,7 @@ export default class Collection implements ViewModels.Collection {
isActive: ko.observable(false),
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoQueryTab);
@@ -671,7 +668,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
const graphTab: GraphTab = new GraphTab({
@@ -689,7 +686,7 @@ export default class Collection implements ViewModels.Collection {
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(graphTab);
@@ -705,7 +702,7 @@ export default class Collection implements ViewModels.Collection {
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoShellTab);
@@ -767,7 +764,7 @@ export default class Collection implements ViewModels.Collection {
this.expandStoredProcedures();
}
this.container.tabsManager.refreshActiveTab(
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
);
}
@@ -785,10 +782,10 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
},
error => {
(error) => {
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
description: "Stored procedures node",
databaseAccountName: this.container.databaseAccount().name,
@@ -796,7 +793,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error)
error: getErrorMessage(error),
});
}
);
@@ -814,7 +811,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
@@ -826,7 +823,7 @@ export default class Collection implements ViewModels.Collection {
this.expandUserDefinedFunctions();
}
this.container.tabsManager.refreshActiveTab(
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
);
}
@@ -844,10 +841,10 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
},
error => {
(error) => {
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
description: "UDF node",
databaseAccountName: this.container.databaseAccount().name,
@@ -855,7 +852,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error)
error: getErrorMessage(error),
});
}
);
@@ -873,7 +870,7 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
@@ -885,7 +882,7 @@ export default class Collection implements ViewModels.Collection {
this.expandTriggers();
}
this.container.tabsManager.refreshActiveTab(
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
);
}
@@ -903,10 +900,10 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
},
error => {
(error) => {
this.isTriggersExpanded(true);
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
description: "Triggers node",
@@ -915,7 +912,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error)
error: getErrorMessage(error),
});
}
);
@@ -933,36 +930,36 @@ export default class Collection implements ViewModels.Collection {
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public loadStoredProcedures(): Promise<any> {
return readStoredProcedures(this.databaseId, this.id()).then(storedProcedures => {
return readStoredProcedures(this.databaseId, this.id()).then((storedProcedures) => {
const storedProceduresNodes: ViewModels.TreeNode[] = storedProcedures.map(
storedProcedure => new StoredProcedure(this.container, this, storedProcedure)
(storedProcedure) => new StoredProcedure(this.container, this, storedProcedure)
);
const otherNodes = this.children().filter(node => node.nodeKind !== "StoredProcedure");
const otherNodes = this.children().filter((node) => node.nodeKind !== "StoredProcedure");
const allNodes = otherNodes.concat(storedProceduresNodes);
this.children(allNodes);
});
}
public loadUserDefinedFunctions(): Promise<any> {
return readUserDefinedFunctions(this.databaseId, this.id()).then(userDefinedFunctions => {
return readUserDefinedFunctions(this.databaseId, this.id()).then((userDefinedFunctions) => {
const userDefinedFunctionsNodes: ViewModels.TreeNode[] = userDefinedFunctions.map(
udf => new UserDefinedFunction(this.container, this, udf)
(udf) => new UserDefinedFunction(this.container, this, udf)
);
const otherNodes = this.children().filter(node => node.nodeKind !== "UserDefinedFunction");
const otherNodes = this.children().filter((node) => node.nodeKind !== "UserDefinedFunction");
const allNodes = otherNodes.concat(userDefinedFunctionsNodes);
this.children(allNodes);
});
}
public loadTriggers(): Promise<any> {
return readTriggers(this.databaseId, this.id()).then(triggers => {
const triggerNodes: ViewModels.TreeNode[] = triggers.map(trigger => new Trigger(this.container, this, trigger));
const otherNodes = this.children().filter(node => node.nodeKind !== "Trigger");
return readTriggers(this.databaseId, this.id()).then((triggers) => {
const triggerNodes: ViewModels.TreeNode[] = triggers.map((trigger) => new Trigger(this.container, this, trigger));
const otherNodes = this.children().filter((node) => node.nodeKind !== "Trigger");
const allNodes = otherNodes.concat(triggerNodes);
this.children(allNodes);
});
@@ -1039,8 +1036,8 @@ export default class Collection implements ViewModels.Collection {
endpoint: userContext.endpoint,
accessToken: userContext.accessToken,
platform: configContext.platform,
databaseAccount: userContext.databaseAccount
}
databaseAccount: userContext.databaseAccount,
},
};
documentUploader.postMessage(uploaderMessage);
@@ -1072,7 +1069,7 @@ export default class Collection implements ViewModels.Collection {
const reader = new FileReader();
reader.onload = (evt: any): void => {
const fileData: string = evt.target.result;
this._createDocumentsFromFile(file.name, fileData).then(record => {
this._createDocumentsFromFile(file.name, fileData).then((record) => {
deferred.resolve(record);
});
};
@@ -1082,7 +1079,7 @@ export default class Collection implements ViewModels.Collection {
fileName: file.name,
numSucceeded: 0,
numFailed: 1,
errors: [(evt as any).error.message]
errors: [(evt as any).error.message],
});
};
@@ -1096,7 +1093,7 @@ export default class Collection implements ViewModels.Collection {
fileName: fileName,
numSucceeded: 0,
numFailed: 0,
errors: []
errors: [],
};
try {
@@ -1104,7 +1101,7 @@ export default class Collection implements ViewModels.Collection {
if (Array.isArray(content)) {
await Promise.all(
content.map(async documentContent => {
content.map(async (documentContent) => {
await createDocument(this, documentContent);
record.numSucceeded++;
})
@@ -1153,7 +1150,7 @@ export default class Collection implements ViewModels.Collection {
error: getErrorMessage(error),
accountName: this.container && this.container.databaseAccount(),
databaseName: this.databaseId,
collectionName: this.id()
collectionName: this.id(),
}),
"Settings tree node"
);
@@ -1247,13 +1244,13 @@ export default class Collection implements ViewModels.Collection {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience()
defaultExperience: this.container.defaultExperience(),
});
const params: DataModels.ReadCollectionOfferParams = {
collectionId: this.id(),
collectionResourceId: this.self,
databaseId: this.databaseId
databaseId: this.databaseId,
};
try {
@@ -1266,7 +1263,7 @@ export default class Collection implements ViewModels.Collection {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience()
defaultExperience: this.container.defaultExperience(),
},
startKey
);
@@ -1279,7 +1276,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);

View File

@@ -139,7 +139,7 @@ export default class ConflictId {
id,
partitionKeyValue: partitionKeyValueResolved,
partitionKeyProperty: this.partitionKeyProperty,
partitionKey: this.partitionKey
partitionKey: this.partitionKey,
},
partitionKeyValueResolved
);

View File

@@ -25,9 +25,9 @@ updateUserContext({
documentEndpoint: "fakeEndpoint",
tableEndpoint: "fakeEndpoint",
gremlinEndpoint: "fakeEndpoint",
cassandraEndpoint: "fakeEndpoint"
}
}
cassandraEndpoint: "fakeEndpoint",
},
},
});
describe("Add Schema", () => {
@@ -70,7 +70,7 @@ describe("Add Schema", () => {
resourceGroup: userContext.resourceGroup,
accountName: userContext.databaseAccount.name,
resource: `dbs/${database.id}/colls/${collection.id}`,
status: "new"
status: "new",
});
expect(checkForSchema).not.toBeNull();
expect(database.junoClient.getSchema).toBeCalledWith(

View File

@@ -1,357 +1,357 @@
import * as _ from "underscore";
import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
import Collection from "./Collection";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as Logger from "../../Common/Logger";
import Explorer from "../Explorer";
import { readCollections } from "../../Common/dataAccess/readCollections";
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
import { userContext } from "../../UserContext";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class Database implements ViewModels.Database {
public nodeKind: string;
public container: Explorer;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public offer: ko.Observable<DataModels.Offer>;
public collections: ko.ObservableArray<Collection>;
public isDatabaseExpanded: ko.Observable<boolean>;
public isDatabaseShared: ko.Computed<boolean>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public junoClient: JunoClient;
constructor(container: Explorer, data: any) {
this.nodeKind = "Database";
this.container = container;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.offer = ko.observable();
this.collections = ko.observableArray<Collection>();
this.isDatabaseExpanded = ko.observable<boolean>(false);
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
this.isDatabaseShared = ko.pureComputed(() => {
return this.offer && !!this.offer();
});
this.junoClient = new JunoClient();
}
public onSettingsClick = () => {
this.container.selectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Settings node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.DatabaseSettings,
tab => tab.node?.id() === this.id()
);
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Scale"
});
pendingNotificationsPromise.then(
(data: any) => {
const pendingNotification: DataModels.Notification = data && data[0];
settingsTab = new DatabaseSettingsTab({
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
title: "Scale",
tabPath: "",
node: this,
rid: this.rid,
database: this,
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateNewTab(settingsTab);
},
(error: any) => {
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.Tab,
{
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.id(),
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Scale",
error: errorMessage,
errorStack: getErrorStack(error)
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while fetching database settings for database ${this.id()}: ${errorMessage}`
);
throw error;
}
);
} else {
pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => {
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateTab(settingsTab);
},
(error: any) => {
settingsTab.pendingNotification(undefined);
this.container.tabsManager.activateTab(settingsTab);
}
);
}
};
public isDatabaseNodeSelected(): boolean {
return (
!this.isDatabaseExpanded() &&
this.container.selectedNode &&
this.container.selectedNode() &&
this.container.selectedNode().nodeKind === "Database" &&
this.container.selectedNode().id() === this.id()
);
}
public onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
this.container.deleteDatabaseConfirmationPane.open();
}
public selectDatabase() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public async expandDatabase() {
if (this.isDatabaseExpanded()) {
return;
}
await this.loadOffer();
await this.loadCollections();
this.isDatabaseExpanded(true);
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public collapseDatabase() {
if (!this.isDatabaseExpanded()) {
return;
}
this.isDatabaseExpanded(false);
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public async loadCollections(): Promise<void> {
const collectionVMs: Collection[] = [];
const collections: DataModels.Collection[] = await readCollections(this.id());
const deltaCollections = this.getDeltaCollections(collections);
collections.forEach((collection: DataModels.Collection) => {
this.addSchema(collection);
});
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
const collectionVM: Collection = new Collection(this.container, this.id(), collection);
collectionVMs.push(collectionVM);
});
//merge collections
this.addCollectionsToList(collectionVMs);
this.deleteCollectionsFromList(deltaCollections.toDelete);
}
public openAddCollection(database: Database, event: MouseEvent) {
database.container.addCollectionPane.databaseId(database.id());
database.container.addCollectionPane.open();
}
public findCollectionWithId(collectionId: string): ViewModels.Collection {
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
}
public async loadOffer(): Promise<void> {
if (!this.container.isServerlessEnabled() && !this.offer()) {
const params: DataModels.ReadDatabaseOfferParams = {
databaseId: this.id(),
databaseResourceId: this.self
};
this.offer(await readDatabaseOffer(params));
}
}
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
if (!this.container) {
return Q.resolve(undefined);
}
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
fetchPortalNotifications().then(
notifications => {
if (!notifications || notifications.length === 0) {
deferred.resolve(undefined);
return;
}
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
return (
notification.kind === "message" &&
!notification.collectionName &&
notification.databaseName === this.id() &&
notification.description &&
throughputUpdateRegExp.test(notification.description)
);
});
deferred.resolve(pendingNotification);
},
(error: any) => {
Logger.logError(
JSON.stringify({
error: getErrorMessage(error),
accountName: this.container && this.container.databaseAccount(),
databaseName: this.id(),
collectionName: this.id()
}),
"Settings tree node"
);
deferred.resolve(undefined);
}
);
return deferred.promise;
}
private getDeltaCollections(
updatedCollectionsList: DataModels.Collection[]
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
const collectionsToAdd: DataModels.Collection[] = _.filter(
updatedCollectionsList,
(collection: DataModels.Collection) => {
const collectionExists = _.some(
this.collections(),
(existingCollection: Collection) => existingCollection.id() === collection.id
);
return !collectionExists;
}
);
let collectionsToDelete: Collection[] = [];
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
const collectionPresentInUpdatedList = _.some(
updatedCollectionsList,
(coll: DataModels.Collection) => coll.id === collection.id()
);
if (!collectionPresentInUpdatedList) {
collectionsToDelete.push(collection);
}
});
return { toAdd: collectionsToAdd, toDelete: collectionsToDelete };
}
private addCollectionsToList(collections: Collection[]): void {
this.collections(
this.collections()
.concat(collections)
.sort((collection1, collection2) => collection1.id().localeCompare(collection2.id()))
);
}
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
if (collectionsToRemove.length === 0) {
return;
}
const collectionsToKeep: Collection[] = [];
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.id() === collection.id());
if (!shouldRemoveCollection) {
collectionsToKeep.push(collection);
}
});
this.collections(collectionsToKeep);
}
public addSchema(collection: DataModels.Collection, interval?: number): NodeJS.Timeout {
let checkForSchema: NodeJS.Timeout = null;
interval = interval || 5000;
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
collection.requestSchema = () => {
this.junoClient.requestSchema({
id: undefined,
subscriptionId: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
accountName: userContext.databaseAccount.name,
resource: `dbs/${this.id}/colls/${collection.id}`,
status: "new"
});
checkForSchema = setInterval(async () => {
const response: IJunoResponse<DataModels.ISchema> = await this.junoClient.getSchema(
userContext.databaseAccount.name,
this.id(),
collection.id
);
if (response.status >= 404) {
clearInterval(checkForSchema);
}
if (response.data !== null) {
clearInterval(checkForSchema);
collection.schema = response.data;
}
}, interval);
};
collection.requestSchema();
}
return checkForSchema;
}
}
import * as _ from "underscore";
import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
import Collection from "./Collection";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as Logger from "../../Common/Logger";
import Explorer from "../Explorer";
import { readCollections } from "../../Common/dataAccess/readCollections";
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
import { userContext } from "../../UserContext";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class Database implements ViewModels.Database {
public nodeKind: string;
public container: Explorer;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public offer: ko.Observable<DataModels.Offer>;
public collections: ko.ObservableArray<Collection>;
public isDatabaseExpanded: ko.Observable<boolean>;
public isDatabaseShared: ko.Computed<boolean>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public junoClient: JunoClient;
constructor(container: Explorer, data: any) {
this.nodeKind = "Database";
this.container = container;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.offer = ko.observable();
this.collections = ko.observableArray<Collection>();
this.isDatabaseExpanded = ko.observable<boolean>(false);
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
this.isDatabaseShared = ko.pureComputed(() => {
return this.offer && !!this.offer();
});
this.junoClient = new JunoClient();
}
public onSettingsClick = () => {
this.container.selectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Settings node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.DatabaseSettings,
(tab) => tab.node?.id() === this.id()
);
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Scale",
});
pendingNotificationsPromise.then(
(data: any) => {
const pendingNotification: DataModels.Notification = data && data[0];
settingsTab = new DatabaseSettingsTab({
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
title: "Scale",
tabPath: "",
node: this,
rid: this.rid,
database: this,
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateNewTab(settingsTab);
},
(error: any) => {
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.Tab,
{
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.id(),
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Scale",
error: errorMessage,
errorStack: getErrorStack(error),
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while fetching database settings for database ${this.id()}: ${errorMessage}`
);
throw error;
}
);
} else {
pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => {
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateTab(settingsTab);
},
(error: any) => {
settingsTab.pendingNotification(undefined);
this.container.tabsManager.activateTab(settingsTab);
}
);
}
};
public isDatabaseNodeSelected(): boolean {
return (
!this.isDatabaseExpanded() &&
this.container.selectedNode &&
this.container.selectedNode() &&
this.container.selectedNode().nodeKind === "Database" &&
this.container.selectedNode().id() === this.id()
);
}
public onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
this.container.deleteDatabaseConfirmationPane.open();
}
public selectDatabase() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public async expandDatabase() {
if (this.isDatabaseExpanded()) {
return;
}
await this.loadOffer();
await this.loadCollections();
this.isDatabaseExpanded(true);
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public collapseDatabase() {
if (!this.isDatabaseExpanded()) {
return;
}
this.isDatabaseExpanded(false);
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
description: "Database node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public async loadCollections(): Promise<void> {
const collectionVMs: Collection[] = [];
const collections: DataModels.Collection[] = await readCollections(this.id());
const deltaCollections = this.getDeltaCollections(collections);
collections.forEach((collection: DataModels.Collection) => {
this.addSchema(collection);
});
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
const collectionVM: Collection = new Collection(this.container, this.id(), collection);
collectionVMs.push(collectionVM);
});
//merge collections
this.addCollectionsToList(collectionVMs);
this.deleteCollectionsFromList(deltaCollections.toDelete);
}
public openAddCollection(database: Database, event: MouseEvent) {
database.container.addCollectionPane.databaseId(database.id());
database.container.addCollectionPane.open();
}
public findCollectionWithId(collectionId: string): ViewModels.Collection {
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
}
public async loadOffer(): Promise<void> {
if (!this.container.isServerlessEnabled() && !this.offer()) {
const params: DataModels.ReadDatabaseOfferParams = {
databaseId: this.id(),
databaseResourceId: this.self,
};
this.offer(await readDatabaseOffer(params));
}
}
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
if (!this.container) {
return Q.resolve(undefined);
}
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
fetchPortalNotifications().then(
(notifications) => {
if (!notifications || notifications.length === 0) {
deferred.resolve(undefined);
return;
}
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
return (
notification.kind === "message" &&
!notification.collectionName &&
notification.databaseName === this.id() &&
notification.description &&
throughputUpdateRegExp.test(notification.description)
);
});
deferred.resolve(pendingNotification);
},
(error: any) => {
Logger.logError(
JSON.stringify({
error: getErrorMessage(error),
accountName: this.container && this.container.databaseAccount(),
databaseName: this.id(),
collectionName: this.id(),
}),
"Settings tree node"
);
deferred.resolve(undefined);
}
);
return deferred.promise;
}
private getDeltaCollections(
updatedCollectionsList: DataModels.Collection[]
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
const collectionsToAdd: DataModels.Collection[] = _.filter(
updatedCollectionsList,
(collection: DataModels.Collection) => {
const collectionExists = _.some(
this.collections(),
(existingCollection: Collection) => existingCollection.id() === collection.id
);
return !collectionExists;
}
);
let collectionsToDelete: Collection[] = [];
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
const collectionPresentInUpdatedList = _.some(
updatedCollectionsList,
(coll: DataModels.Collection) => coll.id === collection.id()
);
if (!collectionPresentInUpdatedList) {
collectionsToDelete.push(collection);
}
});
return { toAdd: collectionsToAdd, toDelete: collectionsToDelete };
}
private addCollectionsToList(collections: Collection[]): void {
this.collections(
this.collections()
.concat(collections)
.sort((collection1, collection2) => collection1.id().localeCompare(collection2.id()))
);
}
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
if (collectionsToRemove.length === 0) {
return;
}
const collectionsToKeep: Collection[] = [];
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.id() === collection.id());
if (!shouldRemoveCollection) {
collectionsToKeep.push(collection);
}
});
this.collections(collectionsToKeep);
}
public addSchema(collection: DataModels.Collection, interval?: number): NodeJS.Timeout {
let checkForSchema: NodeJS.Timeout = null;
interval = interval || 5000;
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
collection.requestSchema = () => {
this.junoClient.requestSchema({
id: undefined,
subscriptionId: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
accountName: userContext.databaseAccount.name,
resource: `dbs/${this.id}/colls/${collection.id}`,
status: "new",
});
checkForSchema = setInterval(async () => {
const response: IJunoResponse<DataModels.ISchema> = await this.junoClient.getSchema(
userContext.databaseAccount.name,
this.id(),
collection.id
);
if (response.status >= 404) {
clearInterval(checkForSchema);
}
if (response.data !== null) {
clearInterval(checkForSchema);
collection.schema = response.data;
}
}, interval);
};
collection.requestSchema();
}
return checkForSchema;
}
}

View File

@@ -1,71 +1,71 @@
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import DocumentsTab from "../Tabs/DocumentsTab";
export default class DocumentId {
public container: DocumentsTab;
public rid: string;
public self: string;
public ts: string;
public id: ko.Observable<string>;
public partitionKeyProperty: string;
public partitionKey: DataModels.PartitionKey;
public partitionKeyValue: any;
public stringPartitionKeyValue: string;
public isDirty: ko.Observable<boolean>;
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
this.container = container;
this.self = data._self;
this.rid = data._rid;
this.ts = data._ts;
this.partitionKeyValue = partitionKeyValue;
this.partitionKeyProperty = container && container.partitionKeyProperty;
this.partitionKey = container && container.partitionKey;
this.stringPartitionKeyValue = this.getPartitionKeyValueAsString();
this.id = ko.observable(data.id);
this.isDirty = ko.observable(false);
}
public click() {
if (!this.container.isEditorDirty() || window.confirm("Your unsaved changes will be lost.")) {
this.loadDocument();
}
return;
}
public partitionKeyHeader(): Object {
if (!this.partitionKeyProperty) {
return undefined;
}
if (this.partitionKeyValue === undefined) {
return [{}];
}
return [this.partitionKeyValue];
}
public getPartitionKeyValueAsString(): string {
const partitionKeyValue: any = this.partitionKeyValue;
const typeOfPartitionKeyValue: string = typeof partitionKeyValue;
if (
typeOfPartitionKeyValue === "undefined" ||
typeOfPartitionKeyValue === "null" ||
typeOfPartitionKeyValue === "object"
) {
return "";
}
if (typeOfPartitionKeyValue === "string") {
return partitionKeyValue;
}
return JSON.stringify(partitionKeyValue);
}
public async loadDocument(): Promise<void> {
await this.container.selectDocument(this);
}
}
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import DocumentsTab from "../Tabs/DocumentsTab";
export default class DocumentId {
public container: DocumentsTab;
public rid: string;
public self: string;
public ts: string;
public id: ko.Observable<string>;
public partitionKeyProperty: string;
public partitionKey: DataModels.PartitionKey;
public partitionKeyValue: any;
public stringPartitionKeyValue: string;
public isDirty: ko.Observable<boolean>;
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
this.container = container;
this.self = data._self;
this.rid = data._rid;
this.ts = data._ts;
this.partitionKeyValue = partitionKeyValue;
this.partitionKeyProperty = container && container.partitionKeyProperty;
this.partitionKey = container && container.partitionKey;
this.stringPartitionKeyValue = this.getPartitionKeyValueAsString();
this.id = ko.observable(data.id);
this.isDirty = ko.observable(false);
}
public click() {
if (!this.container.isEditorDirty() || window.confirm("Your unsaved changes will be lost.")) {
this.loadDocument();
}
return;
}
public partitionKeyHeader(): Object {
if (!this.partitionKeyProperty) {
return undefined;
}
if (this.partitionKeyValue === undefined) {
return [{}];
}
return [this.partitionKeyValue];
}
public getPartitionKeyValueAsString(): string {
const partitionKeyValue: any = this.partitionKeyValue;
const typeOfPartitionKeyValue: string = typeof partitionKeyValue;
if (
typeOfPartitionKeyValue === "undefined" ||
typeOfPartitionKeyValue === "null" ||
typeOfPartitionKeyValue === "object"
) {
return "";
}
if (typeOfPartitionKeyValue === "string") {
return partitionKeyValue;
}
return JSON.stringify(partitionKeyValue);
}
public async loadDocument(): Promise<void> {
await this.container.selectDocument(this);
}
}

View File

@@ -1,14 +1,14 @@
import * as ko from "knockout";
import DocumentId from "./DocumentId";
import DocumentsTab from "../Tabs/DocumentsTab";
export default class ObjectId extends DocumentId {
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
super(container, data, partitionKeyValue);
if (typeof data._id === "object") {
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
} else {
this.id = ko.observable(data._id);
}
}
}
import * as ko from "knockout";
import DocumentId from "./DocumentId";
import DocumentsTab from "../Tabs/DocumentsTab";
export default class ObjectId extends DocumentId {
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
super(container, data, partitionKeyValue);
if (typeof data._id === "object") {
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
} else {
this.id = ko.observable(data._id);
}
}
}

View File

@@ -53,7 +53,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
return Q.resolve();
@@ -71,7 +71,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
@@ -85,7 +85,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
tabTitle: title,
});
const queryTab: QueryTab = new QueryTab({
@@ -100,7 +100,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
partitionKey: collection.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTab);
@@ -115,7 +115,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
dataExplorerArea: Constants.Areas.ResourceTree,
});
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
@@ -135,7 +135,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Items"
tabTitle: "Items",
});
documentsTab = new DocumentsTab({
@@ -150,7 +150,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(documentsTab);

View File

@@ -10,15 +10,15 @@ describe("ResourceTreeAdapter", () => {
selectedNode: ko.observable<ViewModels.TreeNode>({
nodeKind: "nodeKind",
rid: "rid",
id: ko.observable<string>("id")
id: ko.observable<string>("id"),
}),
tabsManager: {
activeTab: ko.observable<TabsBase>({
tabKind: ViewModels.CollectionTabKind.Documents
} as TabsBase)
tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase),
},
isNotebookEnabled: ko.observable<boolean>(true),
nonSystemDatabases: ko.observable<ViewModels.Database[]>([])
nonSystemDatabases: ko.observable<ViewModels.Database[]>([]),
} as unknown) as Explorer);
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
@@ -52,11 +52,11 @@ describe("ResourceTreeAdapter", () => {
nodeKind: "Database",
rid: "dbrid",
id: ko.observable<string>("dbid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", undefined, [
ViewModels.CollectionTabKind.Documents
ViewModels.CollectionTabKind.Documents,
]);
expect(isDataNodeSelected).toBeTruthy();
});
@@ -65,14 +65,14 @@ describe("ResourceTreeAdapter", () => {
let subNodeKind = ViewModels.CollectionTabKind.Documents;
const explorer = mockContainer();
explorer.tabsManager.activeTab({
tabKind: subNodeKind
tabKind: subNodeKind,
} as TabsBase);
explorer.selectedNode(({
nodeKind: "Collection",
rid: "collrid",
databaseId: "dbid",
id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
@@ -80,14 +80,14 @@ describe("ResourceTreeAdapter", () => {
subNodeKind = ViewModels.CollectionTabKind.Graph;
explorer.tabsManager.activeTab({
tabKind: subNodeKind
tabKind: subNodeKind,
} as TabsBase);
explorer.selectedNode(({
nodeKind: "Collection",
rid: "collrid",
databaseId: "dbid",
id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
} as unknown) as ViewModels.TreeNode);
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
expect(isDataNodeSelected).toBeTruthy();
@@ -100,14 +100,14 @@ describe("ResourceTreeAdapter", () => {
rid: "collrid",
databaseId: "dbid",
id: ko.observable<string>("collid"),
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents),
} as unknown) as ViewModels.TreeNode);
explorer.tabsManager.activeTab({
tabKind: ViewModels.CollectionTabKind.Documents
tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase);
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [
ViewModels.CollectionTabKind.Settings
ViewModels.CollectionTabKind.Settings,
]);
expect(isDataNodeSelected).toBeFalsy();
});

View File

@@ -16,196 +16,196 @@ const schema: DataModels.ISchema = {
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "_rid",
path: "_rid",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 11,
name: "Int64"
name: "Int64",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "_ts",
path: "_ts",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "id",
path: "id",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "pk",
path: "pk",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "other",
path: "other",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "name",
path: "nested.name",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 11,
name: "Int64"
name: "Int64",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "someNumber",
path: "nested.someNumber",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 17,
name: "Double"
name: "Double",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "anotherNumber",
path: "nested.anotherNumber",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
maxDefinitionLevel: 1,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "name",
path: "items.list.items.name",
maxRepetitionLevel: 1,
maxDefinitionLevel: 3
maxDefinitionLevel: 3,
},
{
dataType: {
code: 11,
name: "Int64"
name: "Int64",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "someNumber",
path: "items.list.items.someNumber",
maxRepetitionLevel: 1,
maxDefinitionLevel: 3
maxDefinitionLevel: 3,
},
{
dataType: {
code: 17,
name: "Double"
name: "Double",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "anotherNumber",
path: "items.list.items.anotherNumber",
maxRepetitionLevel: 1,
maxDefinitionLevel: 3
maxDefinitionLevel: 3,
},
{
dataType: {
code: 15,
name: "String"
name: "String",
},
hasNulls: true,
isArray: false,
schemaType: {
code: 0,
name: "Data"
name: "Data",
},
name: "_etag",
path: "_etag",
maxRepetitionLevel: 0,
maxDefinitionLevel: 1
}
]
maxDefinitionLevel: 1,
},
],
};
const createMockContainer = (): Explorer => {
@@ -243,7 +243,7 @@ describe("Resource tree for schema", () => {
const rootNode: TreeNode = resourceTree.buildSchemaNode(createMockCollection());
const props: TreeComponentProps = {
rootNode,
className: "dataResourceTree"
className: "dataResourceTree",
};
const wrapper = shallow(<TreeComponent {...props} />);
expect(wrapper).toMatchSnapshot();

View File

@@ -57,7 +57,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender());
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender());
this.koSubsDatabaseIdMap = new ArrayHashMap();
this.koSubsCollectionIdMap = new ArrayHashMap();
@@ -80,7 +80,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
if (myNotebooksTree.children) {
// Count 1st generation children (tree is lazy-loaded)
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
myNotebooksTree.children.forEach(treeNode => {
myNotebooksTree.children.forEach((treeNode) => {
switch ((treeNode as NotebookContentItem).type) {
case NotebookContentItemType.File:
nodeCounts.files++;
@@ -129,13 +129,13 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.galleryContentRoot = {
name: "Gallery",
path: "Gallery",
type: NotebookContentItemType.File
type: NotebookContentItemType.File,
};
this.myNotebooksContentRoot = {
name: ResourceTreeAdapter.MyNotebooksTitle,
path: this.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory
type: NotebookContentItemType.Directory,
};
// Only if notebook server is available we can refresh
@@ -152,7 +152,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.gitHubNotebooksContentRoot = {
name: ResourceTreeAdapter.GitHubReposTitle,
path: ResourceTreeAdapter.PseudoDirPath,
type: NotebookContentItemType.Directory
type: NotebookContentItemType.Directory,
};
} else {
this.gitHubNotebooksContentRoot = undefined;
@@ -164,20 +164,20 @@ export class ResourceTreeAdapter implements ReactAdapter {
public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void {
if (this.gitHubNotebooksContentRoot) {
this.gitHubNotebooksContentRoot.children = [];
pinnedRepos?.forEach(pinnedRepo => {
pinnedRepos?.forEach((pinnedRepo) => {
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
const repoTreeItem: NotebookContentItem = {
name: repoFullName,
path: ResourceTreeAdapter.PseudoDirPath,
type: NotebookContentItemType.Directory,
children: []
children: [],
};
pinnedRepo.branches.forEach(branch => {
pinnedRepo.branches.forEach((branch) => {
repoTreeItem.children.push({
name: branch.name,
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
type: NotebookContentItemType.Directory
type: NotebookContentItemType.Directory,
});
});
@@ -198,7 +198,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
children: [],
isSelected: () => this.isDataNodeSelected(database.id()),
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container),
onClick: async isExpanded => {
onClick: async (isExpanded) => {
// Rewritten version of expandCollapseDatabase():
if (isExpanded) {
database.collapseDatabase();
@@ -213,7 +213,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.container.onUpdateTabsButtons([]);
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
},
onContextMenuOpen: () => this.container.selectedNode(database)
onContextMenuOpen: () => this.container.selectedNode(database),
};
if (database.isDatabaseShared()) {
@@ -221,7 +221,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Scale",
isSelected: () =>
this.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
onClick: database.onSettingsClick.bind(database)
onClick: database.onSettingsClick.bind(database),
});
}
@@ -244,7 +244,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
return {
label: undefined,
isExpanded: true,
children: databaseTreeNodes
children: databaseTreeNodes,
};
}
@@ -269,16 +269,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
description: "Data",
data: {
databaseId: collection.databaseId,
collectionId: collection.id()
}
collectionId: collection.id(),
},
});
},
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Documents,
ViewModels.CollectionTabKind.Graph
ViewModels.CollectionTabKind.Graph,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
});
if (!this.container.isPreferredApiCassandra() || !this.container.isServerlessEnabled()) {
@@ -286,7 +286,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
onClick: collection.onSettingsClick.bind(collection),
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings])
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
});
}
@@ -315,7 +315,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Conflicts",
onClick: collection.onConflictsClick.bind(collection),
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts])
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
});
}
@@ -343,7 +343,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
}
},
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
onContextMenuOpen: () => this.container.selectedNode(collection)
onContextMenuOpen: () => this.container.selectedNode(collection),
};
}
@@ -355,9 +355,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
onClick: sp.open.bind(sp),
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.StoredProcedures
ViewModels.CollectionTabKind.StoredProcedures,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
@@ -365,7 +365,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
}
},
};
}
@@ -377,9 +377,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
onClick: udf.open.bind(udf),
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.UserDefinedFunctions
ViewModels.CollectionTabKind.UserDefinedFunctions,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
this.container,
udf
),
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
@@ -387,7 +390,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
}
},
};
}
@@ -399,7 +402,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
onClick: trigger.open.bind(trigger),
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
@@ -407,7 +410,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
}
},
};
}
@@ -428,7 +431,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.container.tabsManager.refreshActiveTab(
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
);
}
},
};
}
@@ -484,7 +487,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
let notebooksTree: TreeNode = {
label: undefined,
isExpanded: true,
children: []
children: [],
};
if (this.galleryContentRoot) {
@@ -497,7 +500,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
if (this.gitHubNotebooksContentRoot) {
// collapse all other notebook nodes
notebooksTree.children.forEach(node => (node.isExpanded = false));
notebooksTree.children.forEach((node) => (node.isExpanded = false));
notebooksTree.children.push(this.buildGitHubNotebooksTree());
}
@@ -523,7 +526,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
this.triggerRender();
},
setInitialFocus: true
setInitialFocus: true,
};
const openGalleryProps: ILinkProps = {
@@ -531,7 +534,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
this.container.openGallery();
this.triggerRender();
}
},
};
return (
@@ -559,7 +562,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () => {
const activeTab = this.container.tabsManager.activeTab();
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
}
},
};
}
@@ -567,7 +570,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.myNotebooksContentRoot,
(item: NotebookContentItem) => {
this.container.openNotebook(item).then(hasOpened => {
this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) {
this.pushItemToMostRecent(item);
}
@@ -580,7 +583,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
myNotebooksTree.isExpanded = true;
myNotebooksTree.isAlphaSorted = true;
// Remove "Delete" menu item from context menu
myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter(menuItem => menuItem.label !== "Delete");
myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete");
return myNotebooksTree;
}
@@ -588,7 +591,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.gitHubNotebooksContentRoot,
(item: NotebookContentItem) => {
this.container.openNotebook(item).then(hasOpened => {
this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) {
this.pushItemToMostRecent(item);
}
@@ -601,7 +604,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
gitHubNotebooksTree.contextMenu = [
{
label: "Manage GitHub settings",
onClick: () => this.container.gitHubReposPane.open()
onClick: () => this.container.gitHubReposPane.open(),
},
{
label: "Disconnect from GitHub",
@@ -609,11 +612,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook
dataExplorerArea: Areas.Notebook,
});
this.container.notebookManager?.gitHubOAuthService.logout();
}
}
},
},
];
gitHubNotebooksTree.isExpanded = true;
@@ -629,8 +632,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
description: "Notebook",
data: {
name: item.name,
path: item.path
}
path: item.path,
},
});
}
@@ -643,7 +646,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
if (!item || !item.children) {
return [];
} else {
return item.children.map(item => {
return item.children.map((item) => {
const result =
item.type === NotebookContentItemType.Directory
? this.buildNotebookDirectoryNode(item, onFileClick, createDirectoryContextMenu, createFileContextMenu)
@@ -676,7 +679,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
);
},
contextMenu: createFileContextMenu && this.createFileContextMenu(item),
data: item
data: item,
};
}
@@ -685,7 +688,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
{
label: "Rename",
iconSrc: NotebookIcon,
onClick: () => this.container.renameNotebook(item)
onClick: () => this.container.renameNotebook(item),
},
{
label: "Delete",
@@ -699,23 +702,23 @@ export class ResourceTreeAdapter implements ReactAdapter {
"Cancel",
undefined
);
}
},
},
{
label: "Copy to ...",
iconSrc: CopyIcon,
onClick: () => this.copyNotebook(item)
onClick: () => this.copyNotebook(item),
},
{
label: "Download",
iconSrc: NotebookIcon,
onClick: () => this.container.downloadFile(item)
}
onClick: () => this.container.downloadFile(item),
},
];
// "Copy to ..." isn't needed if github locations are not available
if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
items = items.filter(item => item.label !== "Copy to ...");
items = items.filter((item) => item.label !== "Copy to ...");
}
return items;
@@ -733,7 +736,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
{
label: "Refresh",
iconSrc: RefreshIcon,
onClick: () => this.container.refreshContentItem(item).then(() => this.triggerRender())
onClick: () => this.container.refreshContentItem(item).then(() => this.triggerRender()),
},
{
label: "Delete",
@@ -747,34 +750,34 @@ export class ResourceTreeAdapter implements ReactAdapter {
"Cancel",
undefined
);
}
},
},
{
label: "Rename",
iconSrc: NotebookIcon,
onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender())
onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender()),
},
{
label: "New Directory",
iconSrc: NewNotebookIcon,
onClick: () => this.container.onCreateDirectory(item)
onClick: () => this.container.onCreateDirectory(item),
},
{
label: "New Notebook",
iconSrc: NewNotebookIcon,
onClick: () => this.container.onNewNotebookClicked(item)
onClick: () => this.container.onNewNotebookClicked(item),
},
{
label: "Upload File",
iconSrc: NewNotebookIcon,
onClick: () => this.container.onUploadToNotebookServerClicked(item)
}
onClick: () => this.container.onUploadToNotebookServerClicked(item),
},
];
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
if (GitHubUtils.fromContentUri(item.path)) {
items = items.filter(
item =>
(item) =>
item.label !== "Delete" &&
item.label !== "Rename" &&
item.label !== "New Directory" &&
@@ -818,7 +821,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
? this.createDirectoryContextMenu(item)
: undefined,
data: item,
children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu)
children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu),
};
}

View File

@@ -41,7 +41,7 @@ describe("Resource tree for resource token", () => {
const rootNode: TreeNode = resourceTree.buildCollectionNode();
const props: TreeComponentProps = {
rootNode,
className: "dataResourceTree"
className: "dataResourceTree",
};
const wrapper = shallow(<TreeComponent {...props} />);
expect(wrapper).toMatchSnapshot();

View File

@@ -34,7 +34,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
return {
label: undefined,
isExpanded: true,
children: []
children: [],
};
}
@@ -50,12 +50,12 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
description: "Data",
data: {
databaseId: collection.databaseId,
collectionId: collection.id()
}
collectionId: collection.id(),
},
});
},
isSelected: () =>
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents)
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents),
});
const collectionNode: TreeNode = {
@@ -69,16 +69,16 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]);
this.container.tabsManager.refreshActiveTab(
tab => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);
},
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id())
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
};
return {
label: undefined,
isExpanded: true,
children: [collectionNode]
children: [collectionNode],
};
}

View File

@@ -1,175 +1,175 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
function sample(prefix) {
var collection = getContext().getCollection();
// Query documents and take 1st item.
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM root r',
function (err, feed, options) {
if (err) throw err;
// Check the feed and if empty, set the body to 'no docs found', 
// else take 1st element from feed
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
var response = getContext().getResponse();
var body = { prefix: prefix, feed: feed[0] };
response.setBody(JSON.stringify(body));
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}`;
export default class StoredProcedure {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
public isExecuteEnabled: boolean;
constructor(container: Explorer, collection: ViewModels.Collection, data: StoredProcedureDefinition & Resource) {
this.nodeKind = "StoredProcedure";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body as string);
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
const storedProcedure = <StoredProcedureDefinition>{
id: "",
body: sampleStoredProcedureBody
};
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
resource: storedProcedure,
isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: `New Stored Procedure ${id}`,
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons
});
source.container.tabsManager.activateNewTab(storedProcedureTab);
}
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Stored procedure node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public open = () => {
this.select();
const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
if (storedProcedureTab) {
this.container.tabsManager.activateTab(storedProcedureTab);
} else {
const storedProcedureData = <StoredProcedureDefinition>{
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body()
};
storedProcedureTab = new StoredProcedureTab({
resource: storedProcedureData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: storedProcedureData.id,
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(storedProcedureTab);
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
return;
}
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
reason => {}
);
}
public execute(params: string[], partitionKeyValue?: string): void {
const sprocTabs = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
const sprocTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true);
this.container &&
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
.then(
(result: any) => {
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
},
(error: any) => {
sprocTab.onExecuteSprocsError(getErrorMessage(error));
}
)
.finally(() => {
sprocTab.isExecuting(false);
this.onFocusAfterExecute();
});
}
public onFocusAfterExecute(): void {
const focusElement = document.getElementById("execute-storedproc-toggles");
focusElement && focusElement.focus();
}
}
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
function sample(prefix) {
var collection = getContext().getCollection();
// Query documents and take 1st item.
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM root r',
function (err, feed, options) {
if (err) throw err;
// Check the feed and if empty, set the body to 'no docs found', 
// else take 1st element from feed
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
var response = getContext().getResponse();
var body = { prefix: prefix, feed: feed[0] };
response.setBody(JSON.stringify(body));
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}`;
export default class StoredProcedure {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
public isExecuteEnabled: boolean;
constructor(container: Explorer, collection: ViewModels.Collection, data: StoredProcedureDefinition & Resource) {
this.nodeKind = "StoredProcedure";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body as string);
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
const storedProcedure = <StoredProcedureDefinition>{
id: "",
body: sampleStoredProcedureBody,
};
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
resource: storedProcedure,
isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: `New Stored Procedure ${id}`,
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(storedProcedureTab);
}
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Stored procedure node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public open = () => {
this.select();
const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
if (storedProcedureTab) {
this.container.tabsManager.activateTab(storedProcedureTab);
} else {
const storedProcedureData = <StoredProcedureDefinition>{
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body(),
};
storedProcedureTab = new StoredProcedureTab({
resource: storedProcedureData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: storedProcedureData.id,
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(storedProcedureTab);
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
return;
}
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
);
}
public execute(params: string[], partitionKeyValue?: string): void {
const sprocTabs = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
const sprocTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true);
this.container &&
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
.then(
(result: any) => {
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
},
(error: any) => {
sprocTab.onExecuteSprocsError(getErrorMessage(error));
}
)
.finally(() => {
sprocTab.isExecuting(false);
this.onFocusAfterExecute();
});
}
public onFocusAfterExecute(): void {
const focusElement = document.getElementById("execute-storedproc-toggles");
focusElement && focusElement.focus();
}
}

View File

@@ -1,123 +1,123 @@
import { StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import TriggerTab from "../Tabs/TriggerTab";
export default class Trigger {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
public triggerType: ko.Observable<string>;
public triggerOperation: ko.Observable<string>;
constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
this.nodeKind = "Trigger";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body);
this.triggerOperation = ko.observable(data.triggerOperation);
this.triggerType = ko.observable(data.triggerType);
}
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Trigger node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = <StoredProcedureDefinition>{
id: "",
body: "function trigger(){}",
triggerOperation: "All",
triggerType: "Pre"
};
const triggerTab: TriggerTab = new TriggerTab({
resource: trigger,
isNew: true,
tabKind: ViewModels.CollectionTabKind.Triggers,
title: `New Trigger ${id}`,
tabPath: "",
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons
});
source.container.tabsManager.activateNewTab(triggerTab);
}
public open = () => {
this.select();
const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers,
tab => tab.node && tab.node.rid === this.rid
) as TriggerTab[];
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
if (triggerTab) {
this.container.tabsManager.activateTab(triggerTab);
} else {
const triggerData = <StoredProcedureDefinition>{
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body(),
triggerOperation: this.triggerOperation(),
triggerType: this.triggerType()
};
triggerTab = new TriggerTab({
resource: triggerData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.Triggers,
title: triggerData.id,
tabPath: "",
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/triggers/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(triggerTab);
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the trigger?")) {
return;
}
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator(tab => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
reason => {}
);
}
}
import { StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import TriggerTab from "../Tabs/TriggerTab";
export default class Trigger {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
public triggerType: ko.Observable<string>;
public triggerOperation: ko.Observable<string>;
constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
this.nodeKind = "Trigger";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body);
this.triggerOperation = ko.observable(data.triggerOperation);
this.triggerType = ko.observable(data.triggerType);
}
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Trigger node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = <StoredProcedureDefinition>{
id: "",
body: "function trigger(){}",
triggerOperation: "All",
triggerType: "Pre",
};
const triggerTab: TriggerTab = new TriggerTab({
resource: trigger,
isNew: true,
tabKind: ViewModels.CollectionTabKind.Triggers,
title: `New Trigger ${id}`,
tabPath: "",
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(triggerTab);
}
public open = () => {
this.select();
const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers,
(tab) => tab.node && tab.node.rid === this.rid
) as TriggerTab[];
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
if (triggerTab) {
this.container.tabsManager.activateTab(triggerTab);
} else {
const triggerData = <StoredProcedureDefinition>{
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body(),
triggerOperation: this.triggerOperation(),
triggerType: this.triggerType(),
};
triggerTab = new TriggerTab({
resource: triggerData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.Triggers,
title: triggerData.id,
tabPath: "",
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/triggers/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(triggerTab);
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the trigger?")) {
return;
}
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
);
}
}

View File

@@ -1,116 +1,116 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
export default class UserDefinedFunction {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
constructor(container: Explorer, collection: ViewModels.Collection, data: UserDefinedFunctionDefinition & Resource) {
this.nodeKind = "UserDefinedFunction";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body as string);
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = {
id: "",
body: "function userDefinedFunction(){}"
};
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunction,
isNew: true,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
title: `New UDF ${id}`,
tabPath: "",
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons
});
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
}
public open = () => {
this.select();
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions,
tab => tab.node?.rid === this.rid
) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
if (userDefinedFunctionTab) {
this.container.tabsManager.activateTab(userDefinedFunctionTab);
} else {
const userDefinedFunctionData = {
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body()
};
userDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunctionData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
title: userDefinedFunctionData.id,
tabPath: "",
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/udfs/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
}
};
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "UDF item node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
}
public delete() {
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
return;
}
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator(tab => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
reason => {}
);
}
}
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
export default class UserDefinedFunction {
public nodeKind: string;
public container: Explorer;
public collection: ViewModels.Collection;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public body: ko.Observable<string>;
constructor(container: Explorer, collection: ViewModels.Collection, data: UserDefinedFunctionDefinition & Resource) {
this.nodeKind = "UserDefinedFunction";
this.container = container;
this.collection = collection;
this.self = data._self;
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body as string);
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = {
id: "",
body: "function userDefinedFunction(){}",
};
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunction,
isNew: true,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
title: `New UDF ${id}`,
tabPath: "",
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
}
public open = () => {
this.select();
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions,
(tab) => tab.node?.rid === this.rid
) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
if (userDefinedFunctionTab) {
this.container.tabsManager.activateTab(userDefinedFunctionTab);
} else {
const userDefinedFunctionData = {
_rid: this.rid,
_self: this.self,
id: this.id(),
body: this.body(),
};
userDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunctionData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
title: userDefinedFunctionData.id,
tabPath: "",
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/udfs/${this.id()}`,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
}
};
public select() {
this.container.selectedNode(this);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "UDF item node",
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
public delete() {
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
return;
}
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
);
}
}