Compare commits

...

20 Commits

Author SHA1 Message Date
Jordi Bunster
881cef6157 Typescript strict mode in editor
This does not change the strictness of what we build. It doesn't
make things more lax either, that stays exactly the same. But the
editor will now complain more.
2021-04-30 14:04:12 -07:00
victor-meng
4efacace16 add collection panel improvements (#630)
Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-04-30 10:23:34 -07:00
victor-meng
9878bf0d5e Fix table entity boolean and number type property values (#737) 2021-04-29 19:23:21 -05:00
Jordi Bunster
5e0523c7d9 Remove GraphExplorerAdapter (#736) 2021-04-29 16:46:31 -05:00
Jordi Bunster
9d0bc86197 Remove 'explorer' from a few Panes (#650)
While working on #549 I realized there were a few places where 'explorer' was only needed to expand the notifications console, so I stripped those out where it was easy.
2021-04-29 10:20:57 -07:00
Sunil Kumar Yadav
531df811da Remove userContext.defaultExperience (#730)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-28 14:25:04 -05:00
Steve Faulkner
5a019eb431 Remove Explorer.isPreferredAPIMongo (#557)
Co-authored-by: hardiknai-techm <HN00734461@TechMahindra.com>
2021-04-27 20:50:01 -05:00
Hardikkumar Nai
8f3cb7282b Migrate Publish Notebook Pane to React (#641)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-27 19:40:03 -05:00
Jordi Bunster
154db1dcd5 Get our previously strict files a bit tighter (#604)
Now they meet noUnusedParameters
2021-04-27 15:27:17 -07:00
Tanuj Mittal
e8b79d6260 Use postRobot to listen for GitHub OAuth messages (#729) 2021-04-27 22:22:52 +05:30
Jordi Bunster
10c4dd0f19 This is creating a warning in tests (#731) 2021-04-27 09:05:25 -07:00
Jordi Bunster
5cf16d01b5 use ES6 Map if we can (#602) 2021-04-27 08:14:21 -07:00
Jordi Bunster
127784abdd Bypass Knockout and adapters in GalleryTab (#728) 2021-04-27 08:14:07 -07:00
Jordi Bunster
c7b9ff6794 Lazy loaded Monaco (#720)
Lazy loaded Monaco
2021-04-25 21:31:10 -07:00
Hardikkumar Nai
71e7ad4547 Migrate String Input Pane to React (#678)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-25 20:22:46 -05:00
Sunil Kumar Yadav
67062c18aa Migration/edit table entity panel to react (#690)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-25 17:51:27 -05:00
Sunil Kumar Yadav
ab283cb8ff Update webpack v4.46.0 (#718) 2021-04-24 18:54:59 -05:00
Jordi Bunster
045a28b7a4 Remove 'any' from existing lazy loaded tabs (#721)
* Typesafe lazy loaded GalleryTab

* Typesafe lazy loaded NotebookViewerTab

* Typesafe lazy loaded NotebookManager
2021-04-23 19:54:21 -07:00
Jordi Bunster
b7c911d19a Remove Tabs from ComponentRegisterer (#713)
Now that Tabs are being rendered via Tabs.tsx the knockout component names are not needed either.
2021-04-23 19:53:48 -07:00
Jordi Bunster
5323f6ca4b Lazy load SchemaAnalyzerTab (#722) 2021-04-23 19:52:18 -07:00
187 changed files with 40686 additions and 5367 deletions

View File

@@ -5,7 +5,6 @@ src/Api/Apis.ts
src/AuthType.ts src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts src/Bindings/BindingHandlersRegisterer.ts
src/Bindings/ReactBindingHandler.ts src/Bindings/ReactBindingHandler.ts
src/Common/ArrayHashMap.ts
src/Common/Constants.ts src/Common/Constants.ts
src/Common/CosmosClient.test.ts src/Common/CosmosClient.test.ts
src/Common/CosmosClient.ts src/Common/CosmosClient.ts
@@ -13,15 +12,12 @@ src/Common/DataAccessUtilityBase.test.ts
src/Common/DataAccessUtilityBase.ts src/Common/DataAccessUtilityBase.ts
src/Common/EditableUtility.ts src/Common/EditableUtility.ts
src/Common/HashMap.test.ts src/Common/HashMap.test.ts
src/Common/HashMap.ts
src/Common/Logger.test.ts src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts src/Common/MessageHandler.ts
src/Common/MongoProxyClient.test.ts src/Common/MongoProxyClient.test.ts
src/Common/MongoUtility.ts src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts src/Common/NotificationsClientBase.ts
src/Common/ObjectCache.test.ts
src/Common/ObjectCache.ts
src/Common/QueriesClient.ts src/Common/QueriesClient.ts
src/Common/Splitter.ts src/Common/Splitter.ts
src/Config.ts src/Config.ts
@@ -125,7 +121,7 @@ src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts # src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/StringInputPane.ts src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/EditTableEntityPane.ts src/Explorer/Panes/Tables/EditTableEntityPane.ts
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts src/Explorer/Panes/Tables/EntityPropertyViewModel.ts

30519
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -176,7 +176,7 @@
"typescript": "4.2.4", "typescript": "4.2.4",
"url-loader": "1.1.1", "url-loader": "1.1.1",
"wait-on": "4.0.2", "wait-on": "4.0.2",
"webpack": "4.43.0", "webpack": "4.46.0",
"webpack-bundle-analyzer": "3.6.1", "webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10", "webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.0" "webpack-dev-server": "3.11.0"
@@ -195,7 +195,7 @@
"watch": "npm run start", "watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase", "build:ase": "gulp build:ase",
"compile": "tsc", "compile": "tsc -p ./tsconfig.build.json",
"compile:contracts": "tsc -p ./tsconfig.contracts.json", "compile:contracts": "tsc -p ./tsconfig.contracts.json",
"compile:strict": "tsc -p ./tsconfig.strict.json", "compile:strict": "tsc -p ./tsconfig.strict.json",
"format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"", "format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
@@ -204,7 +204,7 @@
"build:contracts": "npm run compile:contracts", "build:contracts": "npm run compile:contracts",
"strict:find": "node ./strict-null-checks/find.js", "strict:find": "node ./strict-null-checks/find.js",
"strict:add": "node ./strict-null-checks/auto-add.js", "strict:add": "node ./strict-null-checks/auto-add.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks", "compile:fullStrict": "tsc",
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts" "generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
}, },
"repository": { "repository": {

View File

@@ -22,13 +22,7 @@ export interface ReactAdapter {
export class Registerer { export class Registerer {
public static register(): void { public static register(): void {
ko.bindingHandlers.react = { ko.bindingHandlers.react = {
init: ( init: (element: any, wrappedValueAccessor: () => any) => {
element: any,
wrappedValueAccessor: () => any,
allBindings?: ko.AllBindings,
viewModel?: any,
bindingContext?: ko.BindingContext
) => {
const adapter: ReactAdapter = wrappedValueAccessor(); const adapter: ReactAdapter = wrappedValueAccessor();
if (adapter.setElement) { if (adapter.setElement) {

View File

@@ -1,49 +1,9 @@
import { HashMap } from "./HashMap";
/** /**
* Hash map of arrays which allows to: * Hash map of arrays which allows to:
* - push an item by key: add to array and create array if needed * - push an item by key: add to array and create array if needed
* - remove item by key: remove from array and delete array if needed * - remove item by key: remove from array and delete array if needed
*/ */
export class ArrayHashMap<T> extends Map<string, T[]> {
export class ArrayHashMap<T> {
private store: HashMap<T[]>;
constructor() {
this.store = new HashMap();
}
public has(key: string): boolean {
return this.store.has(key);
}
public get(key: string): T[] {
return this.store.get(key);
}
public size(): number {
return this.store.size();
}
public clear(): void {
this.store.clear();
}
public keys(): string[] {
return this.store.keys();
}
public delete(key: string): boolean {
return this.store.delete(key);
}
public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key);
if (values) {
values.forEach((value) => iteratorFct(value));
}
}
/** /**
* Insert item into array. * Insert item into array.
* If no array, create one. * If no array, create one.
@@ -52,16 +12,8 @@ export class ArrayHashMap<T> {
* @param item * @param item
*/ */
public push(key: string, item: T): void { public push(key: string, item: T): void {
let itemsArray: T[] = this.store.get(key); const array = this.get(key);
if (!itemsArray) { array ? array.includes(item) || array.push(item) : this.set(key, [item]);
itemsArray = [item];
this.store.set(key, itemsArray);
return;
}
if (itemsArray.indexOf(item) === -1) {
itemsArray.push(item);
}
} }
/** /**
@@ -70,18 +22,11 @@ export class ArrayHashMap<T> {
* @param key * @param key
* @param itemToRemove * @param itemToRemove
*/ */
public remove(key: string, itemToRemove: T) { public remove(key: string, itemToRemove: T): void {
if (!this.store.has(key)) { const array = this.get(key);
return; if (array) {
} const remaining = array.filter((item) => item !== itemToRemove);
remaining.length ? this.set(key, remaining) : this.delete(key);
const itemsArray = this.store.get(key);
const index = itemsArray.indexOf(itemToRemove);
if (index >= 0) {
itemsArray.splice(index, 1);
if (itemsArray.length === 0) {
this.store.delete(key);
}
} }
} }
} }

View File

@@ -1,8 +1,7 @@
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export const getEntityName = (): string => { export const getEntityName = (): string => {
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) { if (userContext.apiType === "Mongo") {
return "document"; return "document";
} }

View File

@@ -6,6 +6,7 @@ export interface TableEntityProps {
entityValuePlaceholder: string; entityValuePlaceholder: string;
entityValue: string | Date; entityValue: string | Date;
isEntityTypeDate: boolean; isEntityTypeDate: boolean;
isEntityValueDisable?: boolean;
entityTimeValue: string; entityTimeValue: string;
entityValueType: string; entityValueType: string;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
@@ -22,6 +23,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
entityValueType, entityValueType,
onEntityValueChange, onEntityValueChange,
onSelectDate, onSelectDate,
isEntityValueDisable,
onEntityTimeValueChange, onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => { }: TableEntityProps): JSX.Element => {
if (isEntityTypeDate) { if (isEntityTypeDate) {
@@ -33,6 +35,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
value={entityValue && new Date(entityValue)} value={entityValue && new Date(entityValue)}
ariaLabel={entityValuePlaceholder} ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
disabled={isEntityValueDisable}
/> />
<TextField <TextField
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
@@ -41,6 +44,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
type="time" type="time"
value={entityTimeValue} value={entityTimeValue}
onChange={onEntityTimeValueChange} onChange={onEntityTimeValueChange}
disabled={isEntityValueDisable}
/> />
</> </>
); );
@@ -52,6 +56,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
className="addEntityTextField" className="addEntityTextField"
id="entityValueId" id="entityValueId"
autoFocus autoFocus
disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" && entityValue} value={typeof entityValue === "string" && entityValue}

View File

@@ -1,70 +0,0 @@
import { HashMap } from "./HashMap";
describe("HashMap", () => {
it("should test if key/val exists", () => {
const map = new HashMap<number>();
map.set("a", 123);
expect(map.has("a")).toBe(true);
expect(map.has("b")).toBe(false);
});
it("should get object back", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("a", "456");
expect(map.get("a")).toBe("456");
expect(map.get("a")).not.toBe("123");
});
it("should return the right size", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("b", "456");
expect(map.size()).toBe(2);
});
it("should be iterable", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
map.set("c", 100);
map.set("d", 1000);
let i = 0;
map.forEach((key: string, value: number) => {
i += value;
});
expect(i).toBe(1111);
});
it("should be deleted", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
expect(map.delete("a")).toBe(true);
expect(map.delete("c")).toBe(false);
expect(map.has("a")).toBe(false);
expect(map.has("b")).toBe(true);
});
it("should clear", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.clear();
expect(map.size()).toBe(0);
expect(map.has("a")).toBe(false);
});
it("should return all keys", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 1);
expect(map.keys()).toEqual(["a", "b"]);
map.clear();
expect(map.keys().length).toBe(0);
});
});

View File

@@ -1,45 +0,0 @@
/**
* Simple hashmap implementation that doesn't rely on ES6 Map nor polyfills
*/
export class HashMap<T> {
constructor(private container: { [key: string]: T } = {}) {}
public has(key: string): boolean {
return this.container.hasOwnProperty(key);
}
public set(key: string, value: T): void {
this.container[key] = value;
}
public get(key: string): T {
return this.container[key];
}
public size(): number {
return Object.keys(this.container).length;
}
public delete(key: string): boolean {
if (this.has(key)) {
delete this.container[key];
return true;
}
return false;
}
public clear(): void {
this.container = {};
}
public keys(): string[] {
return Object.keys(this.container);
}
public forEach(iteratorFct: (key: string, value: T) => void) {
for (const k in this.container) {
iteratorFct(k, this.container[k]);
}
}
}

View File

@@ -7,7 +7,7 @@ describe("Object cache", () => {
cache.set("b", 2); cache.set("b", 2);
cache.set("c", 3); cache.set("c", 3);
cache.set("d", 4); cache.set("d", 4);
expect(cache.size()).toBe(2); expect(cache.size).toBe(2);
}); });
it("should remove first added element to keep size at limit", () => { it("should remove first added element to keep size at limit", () => {

View File

@@ -1,56 +1,27 @@
import { HashMap } from "./HashMap"; export class ObjectCache<T> extends Map<string, T> {
constructor(private limit: number) {
export class ObjectCache<T> extends HashMap<T> {
private keyQueue: string[]; // Last touched key FIFO to purge cache if too big.
private maxNbElements: number;
public constructor(maxNbElements: number) {
super(); super();
this.keyQueue = [];
this.maxNbElements = maxNbElements;
this.clear();
} }
public clear(): void { public get(key: string): T | undefined {
super.clear(); return this.touch(key);
this.keyQueue = [];
} }
public get(key: string): T { public set(key: string, value: T): this {
this.markKeyAsTouched(key); if (this.size === this.limit) {
return super.get(key); this.delete(this.keys().next().value);
}
public set(key: string, value: T): void {
super.set(key, value);
this.markKeyAsTouched(key);
if (super.size() > this.maxNbElements && key !== this.keyQueue[0]) {
this.reduceCacheSize();
} }
return this.touch(key, value), this;
} }
/** private touch(key: string, value = super.get(key)) {
* Invalidate elements to keep the total number below the limit // Map keeps (re) insertion order according to ES6 spec
*/ if (value) {
private reduceCacheSize(): void { this.delete(key);
// remove a key super.set(key, value);
const oldKey = this.keyQueue.shift();
if (oldKey) {
super.delete(oldKey);
} }
}
/** return value;
* Bubble up this key as new.
* @param key
*/
private markKeyAsTouched(key: string) {
const n = this.keyQueue.indexOf(key);
if (n > -1) {
this.keyQueue.splice(n, 1);
}
this.keyQueue.push(key);
} }
} }

View File

@@ -5,16 +5,16 @@ import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab"; import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../Utils/QueryUtils"; import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { createCollection } from "./dataAccess/createCollection"; import { createCollection } from "./dataAccess/createCollection";
import { handleError } from "./ErrorHandlingUtils";
import { createDocument } from "./dataAccess/createDocument"; import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument"; import { deleteDocument } from "./dataAccess/deleteDocument";
import { queryDocuments } from "./dataAccess/queryDocuments"; import { queryDocuments } from "./dataAccess/queryDocuments";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { handleError } from "./ErrorHandlingUtils";
export class QueriesClient { export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -211,7 +211,7 @@ export class QueriesClient {
} }
private fetchQueriesQuery(): string { private fetchQueriesQuery(): string {
if (this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return QueriesClient.FetchMongoQuery; return QueriesClient.FetchMongoQuery;
} }
return QueriesClient.FetchQuery; return QueriesClient.FetchQuery;

View File

@@ -73,7 +73,7 @@ export class Splitter {
$(this.leftSide).resizable(splitterOptions); $(this.leftSide).resizable(splitterOptions);
} }
private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStart: JQueryUI.ResizableEvent = () => {
if (this.direction === SplitterDirection.Vertical) { if (this.direction === SplitterDirection.Vertical) {
$(".ui-resizable-helper").height("100%"); $(".ui-resizable-helper").height("100%");
} else { } else {
@@ -82,9 +82,7 @@ export class Splitter {
$("iframe").css("pointer-events", "none"); $("iframe").css("pointer-events", "none");
}; };
private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
$("iframe").css("pointer-events", "auto");
};
public collapseLeft() { public collapseLeft() {
this.lastX = $(this.splitter).position().left; this.lastX = $(this.splitter).position().left;

View File

@@ -32,6 +32,7 @@ export interface TableEntityProps {
options: { key: string; text: string }[]; options: { key: string; text: string }[];
isPropertyTypeDisable: boolean; isPropertyTypeDisable: boolean;
entityTimeValue: string; entityTimeValue: string;
isEntityValueDisable?: boolean;
onDeleteEntity?: () => void; onDeleteEntity?: () => void;
onEditEntity?: () => void; onEditEntity?: () => void;
onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
@@ -55,6 +56,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
isPropertyTypeDisable, isPropertyTypeDisable,
isEntityTypeDate, isEntityTypeDate,
entityTimeValue, entityTimeValue,
isEntityValueDisable,
onEditEntity, onEditEntity,
onDeleteEntity, onDeleteEntity,
onEntityPropertyChange, onEntityPropertyChange,
@@ -113,6 +115,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
<EntityValue <EntityValue
entityValueLabel={entityValueLabel} entityValueLabel={entityValueLabel}
entityValueType={getEntityValueType()} entityValueType={getEntityValueType()}
isEntityValueDisable={isEntityValueDisable}
entityValuePlaceholder={entityValuePlaceholder} entityValuePlaceholder={entityValuePlaceholder}
entityValue={entityValue} entityValue={entityValue}
isEntityTypeDate={isEntityTypeDate} isEntityTypeDate={isEntityTypeDate}
@@ -121,10 +124,11 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
onEntityTimeValueChange={onEntityTimeValueChange} onEntityTimeValueChange={onEntityTimeValueChange}
/> />
<TooltipHost content="Edit property" id="editTooltip"> {!isEntityValueDisable && (
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} /> <TooltipHost content="Edit property" id="editTooltip">
</TooltipHost> <Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
</TooltipHost>
)}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && ( {isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip"> <TooltipHost content="Delete property" id="deleteTooltip">
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} /> <Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels"; import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { createCollection, constructRpOptions } from "./createCollection"; import { constructRpOptions, createCollection } from "./createCollection";
import { updateUserContext } from "../../UserContext";
describe("createCollection", () => { describe("createCollection", () => {
const createCollectionParams: CreateCollectionParams = { const createCollectionParams: CreateCollectionParams = {
@@ -22,7 +21,7 @@ describe("createCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,33 +1,32 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos"; import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest"; import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { AuthType } from "../../AuthType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import * as DataModels from "../../Contracts/DataModels";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { client } from "../CosmosClient"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { createMongoCollectionWithProxy } from "../MongoProxyClient"; import { userContext } from "../../UserContext";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
createUpdateCassandraTable, createUpdateCassandraTable,
getCassandraTable, getCassandraTable,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
createUpdateGremlinGraph, createUpdateGremlinGraph,
getGremlinGraph, getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { userContext } from "../../UserContext"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createDatabase } from "./createDatabase"; import { client } from "../CosmosClient";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createDatabase } from "./createDatabase";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const clearMessage = logConsoleProgress( const clearMessage = logConsoleProgress(
@@ -46,7 +45,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
await createDatabase(createDatabaseParams); await createDatabase(createDatabaseParams);
} }
collection = await createCollectionWithARM(params); collection = await createCollectionWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) { } else if (userContext.apiType === "Mongo") {
collection = await createMongoCollectionWithProxy(params); collection = await createMongoCollectionWithProxy(params);
} else { } else {
collection = await createCollectionWithSDK(params); collection = await createCollectionWithSDK(params);
@@ -63,17 +62,17 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
}; };
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return createSqlContainer(params); return createSqlContainer(params);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return createMongoCollection(params); return createMongoCollection(params);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return createCassandraTable(params); return createCassandraTable(params);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return createGraph(params); return createGraph(params);
case DefaultAccountExperienceType.Table: case "Tables":
return createTable(params); return createTable(params);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);

View File

@@ -1,37 +1,36 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DatabaseResponse } from "@azure/cosmos"; import { DatabaseResponse } from "@azure/cosmos";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { AuthType } from "../../AuthType";
import { import * as DataModels from "../../Contracts/DataModels";
CassandraKeyspaceCreateUpdateParameters, import { userContext } from "../../UserContext";
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
CreateUpdateOptions,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
createUpdateCassandraKeyspace, createUpdateCassandraKeyspace,
getCassandraKeyspace, getCassandraKeyspace,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
createUpdateGremlinDatabase, createUpdateGremlinDatabase,
getGremlinDatabase, getGremlinDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
CassandraKeyspaceCreateUpdateParameters,
CreateUpdateOptions,
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try { try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.apiType === "Tables") {
throw new Error("Creating database resources is not allowed for tables accounts"); throw new Error("Creating database resources is not allowed for tables accounts");
} }
const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
@@ -49,15 +48,15 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
} }
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return createSqlDatabase(params); return createSqlDatabase(params);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return createMongoDatabase(params); return createMongoDatabase(params);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return createCassandraKeyspace(params); return createCassandraKeyspace(params);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return createGremlineDatabase(params); return createGremlineDatabase(params);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlStoredProcedureCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createStoredProcedure( export async function createStoredProcedure(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function createStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlStoredProcedure( const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,15 +1,14 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlTriggerCreateUpdateParameters, SqlTriggerCreateUpdateParameters,
SqlTriggerResource, SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createTrigger( export async function createTrigger(
databaseId: string, databaseId: string,
@@ -18,11 +17,7 @@ export async function createTrigger(
): Promise<TriggerDefinition & Resource> { ): Promise<TriggerDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlTrigger( const getResponse = await getSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlUserDefinedFunctionCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createUserDefinedFunction( export async function createUserDefinedFunction(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function createUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlUserDefinedFunction( const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,13 +1,12 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler"); jest.mock("../MessageHandler");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { deleteCollection } from "./deleteCollection";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteCollection } from "./deleteCollection";
describe("deleteCollection", () => { describe("deleteCollection", () => {
beforeAll(() => { beforeAll(() => {
@@ -15,7 +14,7 @@ describe("deleteCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,14 +1,13 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> { export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
@@ -31,18 +30,18 @@ function deleteCollectionWithARM(databaseId: string, collectionId: string): Prom
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Table: case "Tables":
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId); return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);

View File

@@ -1,13 +1,12 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler"); jest.mock("../MessageHandler");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { deleteDatabase } from "./deleteDatabase";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteDatabase } from "./deleteDatabase";
describe("deleteDatabase", () => { describe("deleteDatabase", () => {
beforeAll(() => { beforeAll(() => {
@@ -15,7 +14,7 @@ describe("deleteDatabase", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,19 +1,18 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteDatabase(databaseId: string): Promise<void> { export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try { try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.apiType === "Tables") {
throw new Error("Deleting database resources is not allowed for tables accounts"); throw new Error("Deleting database resources is not allowed for tables accounts");
} }
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
@@ -34,16 +33,16 @@ function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId); return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);

View File

@@ -1,10 +1,9 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteStoredProcedure( export async function deleteStoredProcedure(
databaseId: string, databaseId: string,
@@ -13,11 +12,7 @@ export async function deleteStoredProcedure(
): Promise<void> { ): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`); const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlStoredProcedure( await deleteSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,19 +1,14 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> { export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`); const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlTrigger( await deleteSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,19 +1,14 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> { export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`); const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlUserDefinedFunction( await deleteSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,10 +1,9 @@
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readCollection } from "./readCollection"; import { readCollection } from "./readCollection";
import { updateUserContext } from "../../UserContext";
describe("readCollection", () => { describe("readCollection", () => {
beforeAll(() => { beforeAll(() => {
@@ -13,7 +12,7 @@ describe("readCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,25 +1,20 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => { export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`); const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionOfferWithARM(params.databaseId, params.collectionId); return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
} }
@@ -36,12 +31,12 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await getSqlContainerThroughput( rpResponse = await getSqlContainerThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -50,7 +45,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await getMongoDBCollectionThroughput( rpResponse = await getMongoDBCollectionThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -59,7 +54,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await getCassandraTableThroughput( rpResponse = await getCassandraTableThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -68,7 +63,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await getGremlinGraphThroughput( rpResponse = await getGremlinGraphThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -77,7 +72,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId); rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break; break;
default: default:

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readCollections } from "./readCollections"; import { readCollections } from "./readCollections";
import { updateUserContext } from "../../UserContext";
describe("readCollections", () => { describe("readCollections", () => {
beforeAll(() => { beforeAll(() => {
@@ -14,7 +13,7 @@ describe("readCollections", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,6 +1,5 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
@@ -14,11 +13,7 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> { export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionsWithARM(databaseId); return await readCollectionsWithARM(databaseId);
} }
@@ -37,22 +32,22 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
rpResponse = await listTables(subscriptionId, resourceGroup, accountName); rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break; break;
default: default:

View File

@@ -1,24 +1,19 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { userContext } from "../../UserContext";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => { export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readDatabaseOfferWithARM(params.databaseId); return await readDatabaseOfferWithARM(params.databaseId);
} }
@@ -35,21 +30,21 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
default: default:

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readDatabases } from "./readDatabases"; import { readDatabases } from "./readDatabases";
import { updateUserContext } from "../../UserContext";
describe("readDatabases", () => { describe("readDatabases", () => {
beforeAll(() => { beforeAll(() => {
@@ -14,7 +13,7 @@ describe("readDatabases", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,24 +1,19 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> { export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client().databases.readAll().fetchAll(); const sdkResponse = await client().databases.readAll().fetchAll();
@@ -37,19 +32,19 @@ async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName); rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break; break;
default: default:

View File

@@ -1,11 +1,10 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readStoredProcedures( export async function readStoredProcedures(
databaseId: string, databaseId: string,
@@ -13,11 +12,7 @@ export async function readStoredProcedures(
): Promise<(StoredProcedureDefinition & Resource)[]> { ): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlStoredProcedures( const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,10 +1,9 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers( export async function readTriggers(
@@ -13,11 +12,7 @@ export async function readTriggers(
): Promise<(TriggerDefinition & Resource)[]> { ): Promise<(TriggerDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlTriggers( const rpResponse = await listSqlTriggers(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,11 +1,10 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readUserDefinedFunctions( export async function readUserDefinedFunctions(
databaseId: string, databaseId: string,
@@ -13,11 +12,7 @@ export async function readUserDefinedFunctions(
): Promise<(UserDefinedFunctionDefinition & Resource)[]> { ): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlUserDefinedFunctions( const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -2,7 +2,6 @@ import { ContainerDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels"; import { Collection } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { import {
createUpdateCassandraTable, createUpdateCassandraTable,
@@ -38,11 +37,7 @@ export async function updateCollection(
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`); const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection); collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()
@@ -71,18 +66,18 @@ async function updateCollectionWithARM(
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Table: case "Tables":
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return updateMongoDBCollection( return updateMongoDBCollection(
databaseId, databaseId,
collectionId, collectionId,

View File

@@ -1,54 +1,53 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos"; import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient"; import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import { import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale, migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput, migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale, migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput, migrateCassandraTableToManualThroughput,
updateCassandraKeyspaceThroughput,
updateCassandraTableThroughput, updateCassandraTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale, migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput, migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale, migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput, migrateGremlinGraphToManualThroughput,
updateGremlinDatabaseThroughput,
updateGremlinGraphThroughput, updateGremlinGraphThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext"; import {
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
updateMongoDBCollectionThroughput,
updateMongoDBDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
updateSqlContainerThroughput,
updateSqlDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
migrateTableToAutoscale, migrateTableToAutoscale,
migrateTableToManualThroughput, migrateTableToManualThroughput,
updateTableThroughput, updateTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => { export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer; let updatedOffer: Offer;
@@ -61,7 +60,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) { if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params); updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { } else if (userContext.apiType === "Tables") {
// update table's database offer with SDK since RP doesn't support it // update table's database offer with SDK since RP doesn't support it
updatedOffer = await updateOfferWithSDK(params); updatedOffer = await updateOfferWithSDK(params);
} else { } else {
@@ -82,24 +81,24 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => { const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try { try {
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
await updateSqlContainerOffer(params); await updateSqlContainerOffer(params);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
await updateMongoCollectionOffer(params); await updateMongoCollectionOffer(params);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
await updateCassandraTableOffer(params); await updateCassandraTableOffer(params);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
await updateGremlinGraphOffer(params); await updateGremlinGraphOffer(params);
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
await updateTableOffer(params); await updateTableOffer(params);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`); throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "MethodNotAllowed") { if (error.code !== "MethodNotAllowed") {
@@ -116,21 +115,21 @@ const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => { const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try { try {
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
await updateSqlDatabaseOffer(params); await updateSqlDatabaseOffer(params);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
await updateMongoDatabaseOffer(params); await updateMongoDatabaseOffer(params);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
await updateCassandraKeyspaceOffer(params); await updateCassandraKeyspaceOffer(params);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
await updateGremlinDatabaseOffer(params); await updateGremlinDatabaseOffer(params);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`); throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "MethodNotAllowed") { if (error.code !== "MethodNotAllowed") {

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlStoredProcedureCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateStoredProcedure( export async function updateStoredProcedure(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure( const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,15 +1,14 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlTriggerCreateUpdateParameters, SqlTriggerCreateUpdateParameters,
SqlTriggerResource, SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateTrigger( export async function updateTrigger(
databaseId: string, databaseId: string,
@@ -18,11 +17,7 @@ export async function updateTrigger(
): Promise<TriggerDefinition> { ): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlTrigger( const getResponse = await getSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlUserDefinedFunctionCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateUserDefinedFunction( export async function updateUserDefinedFunction(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function updateUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlUserDefinedFunction( const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,8 +0,0 @@
export enum DefaultAccountExperienceType {
DocumentDB = "DocumentDB",
Graph = "Graph",
MongoDB = "MongoDB",
Table = "Table",
Cassandra = "Cassandra",
ApiForMongoDB = "Azure Cosmos DB for MongoDB API",
}

View File

@@ -20,51 +20,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true); expect(ko.components.isRegistered("json-editor")).toBe(true);
}); });
it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true);
});
it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
});
it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true);
});
it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("database-settings-tab-v2")).toBe(true);
expect(ko.components.isRegistered("collection-settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
});
it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should registeradd-collection-pane component", () => { it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true); expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
}); });
@@ -73,10 +28,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true); expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
}); });
it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
});
it("should register dynamic-list component", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });

View File

@@ -8,21 +8,6 @@ import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import ConflictsTab from "./Tabs/ConflictsTab";
import DocumentsTab from "./Tabs/DocumentsTab";
import GalleryTab from "./Tabs/GalleryTab";
import GraphTab from "./Tabs/GraphTab";
import MongoShellTab from "./Tabs/MongoShellTab";
import NotebookTabV2 from "./Tabs/NotebookV2Tab";
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
import QueryTab from "./Tabs/QueryTab";
import QueryTablesTab from "./Tabs/QueryTablesTab";
import SchemaAnalyzerTab from "./Tabs/SchemaAnalyzerTab";
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
import TerminalTab from "./Tabs/TerminalTab";
import TriggerTab from "./Tabs/TriggerTab";
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());
@@ -33,26 +18,6 @@ ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
// Collection Tabs
[
DocumentsTab,
StoredProcedureTab,
TriggerTab,
UserDefinedFunctionTab,
SettingsTabV2,
QueryTab,
QueryTablesTab,
GraphTab,
MongoShellTab,
ConflictsTab,
NotebookTabV2,
TerminalTab,
GalleryTab,
NotebookViewerTab,
DatabaseSettingsTabV2,
SchemaAnalyzerTab,
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
// Panes // Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
@@ -60,5 +25,4 @@ ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPane
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -10,7 +10,6 @@ import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg"; import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg"; import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent"; import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer"; import Explorer from "./Explorer";
@@ -30,16 +29,16 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS) * New resource tree (in ReactJS)
*/ */
export class ResourceTreeContextMenuButtonFactory { export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] { public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [ const items: TreeNodeMenuItem[] = [
{ {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(), onClick: () => container.onNewCollectionClicked(databaseId),
label: container.addCollectionText(), label: container.addCollectionText(),
}, },
]; ];
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) { if (userContext.apiType !== "Tables") {
items.push({ items.push({
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: () => container.openDeleteDatabaseConfirmationPane(), onClick: () => container.openDeleteDatabaseConfirmationPane(),
@@ -63,7 +62,7 @@ export class ResourceTreeContextMenuButtonFactory {
}); });
} }
if (container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),

View File

@@ -5,6 +5,7 @@ import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps { export interface CollapsibleSectionProps {
title: string; title: string;
isExpandedByDefault: boolean; isExpandedByDefault: boolean;
onExpand?: () => void;
} }
export interface CollapsibleSectionState { export interface CollapsibleSectionState {
@@ -23,6 +24,12 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };
public componentDidUpdate(): void {
if (this.state.isExpanded && this.props.onExpand) {
this.props.onExpand();
}
}
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<> <>

View File

@@ -1,6 +1,6 @@
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { loadMonaco, monaco } from "../../LazyMonaco";
import template from "./diff-editor-component.html"; import template from "./diff-editor-component.html";
import * as monaco from "monaco-editor";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
@@ -92,7 +92,7 @@ export class DiffEditorViewModel {
/** /**
* Create the monaco editor on diff mode and attach to DOM * Create the monaco editor on diff mode and attach to DOM
*/ */
protected createDiffEditor( protected async createDiffEditor(
originalContent: string, originalContent: string,
modifiedContent: string, modifiedContent: string,
createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void
@@ -111,7 +111,7 @@ export class DiffEditorViewModel {
} }
const language = this.params.editorLanguage || "json"; const language = this.params.editorLanguage || "json";
const monaco = await loadMonaco();
const originalModel = monaco.editor.createModel(originalContent, language); const originalModel = monaco.editor.createModel(originalContent, language);
const modifiedModel = monaco.editor.createModel(modifiedContent, language); const modifiedModel = monaco.editor.createModel(modifiedContent, language);
const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor( const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor(

View File

@@ -1,7 +1,6 @@
import { loadMonaco, monaco } from "../../LazyMonaco";
import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent"; import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent";
import template from "./editor-component.html"; import template from "./editor-component.html";
import * as monaco from "monaco-editor";
import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
@@ -49,15 +48,17 @@ class EditorViewModel extends JsonEditorViewModel {
return this.params.contentType; return this.params.contentType;
} }
protected registerCompletionItemProvider() { protected async registerCompletionItemProvider() {
let sqlCompletionItemProvider = new SqlCompletionItemProvider();
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) { if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider); const { SqlCompletionItemProvider } = await import("@azure/cosmos-language-service");
const monaco = await loadMonaco();
monaco.languages.registerCompletionItemProvider("sql", new SqlCompletionItemProvider());
EditorViewModel.providerRegistered.push("sql"); EditorViewModel.providerRegistered.push("sql");
} }
} }
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
const { ErrorMarkProvider } = await import("@azure/cosmos-language-service");
return ErrorMarkProvider.getErrorMark(input); return ErrorMarkProvider.getErrorMark(input);
} }
} }

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import * as monaco from "monaco-editor"; import { loadMonaco, monaco } from "../../LazyMonaco";
export interface EditorReactProps { export interface EditorReactProps {
language: string; language: string;
@@ -61,7 +61,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
/** /**
* Create the monaco editor and attach to DOM * Create the monaco editor and attach to DOM
*/ */
private createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { private async createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
const options: monaco.editor.IEditorConstructionOptions = { const options: monaco.editor.IEditorConstructionOptions = {
value: this.props.content, value: this.props.content,
language: this.props.language, language: this.props.language,
@@ -74,6 +74,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.rootNode, options)); createCallback(monaco.editor.create(this.rootNode, options));
} }

View File

@@ -128,21 +128,21 @@ class InputTypeaheadViewModel {
}, },
}, },
callback: { callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => { onClick: (_node: unknown, _a: unknown, item: OnClickItem) => {
cache.selection = item; cache.selection = item;
if (params.selection) { if (params.selection) {
params.selection(item); params.selection(item);
} }
}, },
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) { onResult(_node: unknown, query: any) {
cache.inputValue = query; cache.inputValue = query;
if (params.inputValue) { if (params.inputValue) {
params.inputValue(query); params.inputValue(query);
} }
}, },
}, },
template: (query: string, item: any) => { template: (_query: string, item: any) => {
// Don't display id if caption *IS* the id // Don't display id if caption *IS* the id
return item.caption === item.value return item.caption === item.value
? "<span>{{caption}}</span>" ? "<span>{{caption}}</span>"

View File

@@ -1,6 +1,5 @@
import Q from "q";
import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { loadMonaco, monaco } from "../../LazyMonaco";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import template from "./json-editor-component.html"; import template from "./json-editor-component.html";
@@ -88,7 +87,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
/** /**
* Create the monaco editor and attach to DOM * Create the monaco editor and attach to DOM
*/ */
protected createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { protected async createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
this.registerCompletionItemProvider(); this.registerCompletionItemProvider();
this.editorContainer = document.getElementById(this.getEditorId()); this.editorContainer = document.getElementById(this.getEditorId());
const options: monaco.editor.IEditorConstructionOptions = { const options: monaco.editor.IEditorConstructionOptions = {
@@ -102,6 +101,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
}; };
this.editorContainer.innerHTML = ""; this.editorContainer.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.editorContainer, options)); createCallback(monaco.editor.create(this.editorContainer, options));
} }
@@ -109,15 +109,16 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected registerCompletionItemProvider() {} protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel. // Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected async getErrorMarkers(_: string): Promise<monaco.editor.IMarkerData[]> {
return Q.Promise(() => {}); return [];
} }
protected getEditorLanguage(): string { protected getEditorLanguage(): string {
return "json"; return "json";
} }
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { protected async configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
const monaco = await loadMonaco();
this.editor = editor; this.editor = editor;
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
if (!this.params.isReadOnly && this.params.updatedContent) { if (!this.params.isReadOnly && this.params.updatedContent) {

View File

@@ -1,25 +1,25 @@
import { Card } from "@uifabric/react-cards"; import { Card } from "@uifabric/react-cards";
import { import {
BaseButton,
Button,
FontWeights, FontWeights,
Icon, Icon,
IconButton, IconButton,
Image, Image,
ImageFit, ImageFit,
Persona,
Text,
Link, Link,
BaseButton,
Button,
LinkBase, LinkBase,
Persona,
Separator, Separator,
TooltipHost,
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Text,
TooltipHost,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import React, { FunctionComponent, useState } from "react";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil"; import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
export interface GalleryCardComponentProps { export interface GalleryCardComponentProps {
data: IGalleryItem; data: IGalleryItem;
@@ -34,166 +34,48 @@ export interface GalleryCardComponentProps {
onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void; onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
} }
interface GalleryCardComponentState { export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps> = ({
isDeletingPublishedNotebook: boolean; data,
} isFavorite,
showDownload,
showDelete,
onClick,
onTagClick,
onFavoriteClick,
onUnfavoriteClick,
onDownloadClick,
onDeleteClick,
}: GalleryCardComponentProps) => {
const CARD_WIDTH = 256;
const cardImageHeight = 144;
const cardDescriptionMaxChars = 80;
const cardItemGapBig = 10;
const cardItemGapSmall = 8;
const cardDeleteSpinnerHeight = 360;
const smallTextLineHeight = 18;
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> { const [isDeletingPublishedNotebook, setIsDeletingPublishedNotebook] = useState<boolean>(false);
public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
GalleryCardComponent.cardImageHeight / GalleryCardComponent.CARD_WIDTH;
private static readonly cardDescriptionMaxChars = 80;
private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
private static readonly smallTextLineHeight = 18;
constructor(props: GalleryCardComponentProps) { const cardButtonsVisible = isFavorite !== undefined || showDownload || showDelete;
super(props); const options: Intl.DateTimeFormatOptions = {
this.state = { year: "numeric",
isDeletingPublishedNotebook: false, month: "short",
}; day: "numeric",
} };
const dateString = new Date(data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
public render(): JSX.Element { const renderTruncatedDescription = (): string => {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete; let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
const options: Intl.DateTimeFormatOptions = { if (data.description.length > cardDescriptionMaxChars) {
year: "numeric",
month: "short",
day: "numeric",
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return (
<Card
style={{ background: "white" }}
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)}
>
{this.state.isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!this.state.isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Item>
<Image
src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap styles={{ root: { height: GalleryCardComponent.smallTextLineHeight } }}>
{this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: GalleryCardComponent.smallTextLineHeight * 2 } }}>
{this.renderTruncatedDescription()}
</Text>
<span>
{this.props.data.views !== undefined &&
this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
this.props.onDeleteClick(
() => this.setState({ isDeletingPublishedNotebook: true }),
() => this.setState({ isDeletingPublishedNotebook: false })
)
)}
</span>
</Card.Section>
)}
</>
)}
</Card>
);
}
private renderTruncatedDescription = (): string => {
let truncatedDescription = this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars);
if (this.props.data.description.length > GalleryCardComponent.cardDescriptionMaxChars) {
truncatedDescription = `${truncatedDescription} ...`; truncatedDescription = `${truncatedDescription} ...`;
} }
return truncatedDescription; return truncatedDescription;
}; };
private generateIconText = (iconName: string, text: string): JSX.Element => { const generateIconText = (iconName: string, text: string): JSX.Element => {
return ( return (
<Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: GalleryCardComponent.cardItemGapSmall } }}> <Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: cardItemGapSmall } }}>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text} <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text> </Text>
); );
@@ -203,7 +85,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is * Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button) * to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)
*/ */
private generateIconButtonWithTooltip = ( const generateIconButtonWithTooltip = (
iconName: string, iconName: string,
title: string, title: string,
horizontalAlign: "right" | "left", horizontalAlign: "right" | "left",
@@ -220,13 +102,13 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
iconProps={{ iconName }} iconProps={{ iconName }}
title={title} title={title}
ariaLabel={title} ariaLabel={title}
onClick={(event) => this.onClick(event, activate)} onClick={(event) => handlerOnClick(event, activate)}
/> />
</TooltipHost> </TooltipHost>
); );
}; };
private onClick = ( const handlerOnClick = (
event: event:
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent> | React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
| React.MouseEvent< | React.MouseEvent<
@@ -239,4 +121,112 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
event.preventDefault(); event.preventDefault();
activate(); activate();
}; };
}
return (
<Card
style={{ background: "white" }}
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => handlerOnClick(event, onClick)}
>
{isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Persona imageUrl={data.isSample && CosmosDBLogo} text={data.author} secondaryText={dateString} />
</Card.Item>
<Card.Item>
<Image
src={data.thumbnailUrl}
width={CARD_WIDTH}
height={cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: cardItemGapBig } }}>
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight } }}>
{data.tags ? (
data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => handlerOnClick(event, () => onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: cardItemGapSmall,
paddingBottom: cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: smallTextLineHeight * 2 } }}>
{renderTruncatedDescription()}
</Text>
<span>
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: cardItemGapBig,
marginRight: cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{isFavorite !== undefined &&
generateIconButtonWithTooltip(
isFavorite ? "HeartFill" : "Heart",
isFavorite ? "Unfavorite" : "Favorite",
"left",
isFavorite ? onUnfavoriteClick : onFavoriteClick
)}
{showDownload && generateIconButtonWithTooltip("Download", "Download", "left", onDownloadClick)}
{showDelete &&
generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
onDeleteClick(
() => setIsDeletingPublishedNotebook(true),
() => setIsDeletingPublishedNotebook(false)
)
)}
</span>
</Card.Section>
)}
</>
)}
</Card>
);
};

View File

@@ -1,123 +0,0 @@
import * as React from "react";
import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
interface CodeOfConductComponentState {
readCodeOfConduct: boolean;
}
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
private viewCodeOfConductTraced: boolean;
private descriptionPara1: string;
private descriptionPara2: string;
private descriptionPara3: string;
private link1: { label: string; url: string };
constructor(props: CodeOfConductComponentProps) {
super(props);
this.state = {
readCodeOfConduct: false,
};
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
}
private async acceptCodeOfConduct(): Promise<void> {
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
try {
const response = await this.props.junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
this.props.onAcceptCodeOfConduct(response.data);
} catch (error) {
traceFailure(
Action.NotebooksGalleryAcceptCodeOfConduct,
{
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
}
}
private onChangeCheckbox = (): void => {
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
};
public render(): JSX.Element {
if (!this.viewCodeOfConductTraced) {
this.viewCodeOfConductTraced = true;
trace(Action.NotebooksGalleryViewCodeOfConduct);
}
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{this.descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{this.descriptionPara3}
<Link href={this.link1.url} target="_blank">
{this.link1.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: 12,
},
}}
label="I have read and accept the code of conduct."
onChange={this.onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await this.acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!this.state.readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
}
}

View File

@@ -1,9 +1,9 @@
jest.mock("../../../Juno/JunoClient"); jest.mock("../../../../Juno/JunoClient");
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent"; import { CodeOfConductComponent, CodeOfConductComponentProps } from ".";
import { JunoClient } from "../../../Juno/JunoClient"; import { HttpStatusCodes } from "../../../../Common/Constants";
import { HttpStatusCodes } from "../../../Common/Constants"; import { JunoClient } from "../../../../Juno/JunoClient";
describe("CodeOfConductComponent", () => { describe("CodeOfConductComponent", () => {
let codeOfConductProps: CodeOfConductComponentProps; let codeOfConductProps: CodeOfConductComponentProps;

View File

@@ -0,0 +1,110 @@
import { Checkbox, Link, PrimaryButton, Stack, Text } from "office-ui-fabric-react";
import React, { FunctionComponent, useEffect, useState } from "react";
import { CodeOfConductEndpoints, HttpStatusCodes } from "../../../../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../../../../Common/ErrorHandlingUtils";
import { JunoClient } from "../../../../Juno/JunoClient";
import { Action } from "../../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
export const CodeOfConductComponent: FunctionComponent<CodeOfConductComponentProps> = ({
junoClient,
onAcceptCodeOfConduct,
}: CodeOfConductComponentProps) => {
const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
const link1: { label: string; url: string } = {
label: "code of conduct.",
url: CodeOfConductEndpoints.codeOfConduct,
};
const [readCodeOfConduct, setReadCodeOfConduct] = useState<boolean>(false);
const acceptCodeOfConduct = async (): Promise<void> => {
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
try {
const response = await junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
onAcceptCodeOfConduct(response.data);
} catch (error) {
traceFailure(
Action.NotebooksGalleryAcceptCodeOfConduct,
{
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
}
};
const onChangeCheckbox = (): void => {
setReadCodeOfConduct(!readCodeOfConduct);
};
useEffect(() => {
trace(Action.NotebooksGalleryViewCodeOfConduct);
}, []);
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{descriptionPara3}
<Link href={link1.url} target="_blank">
{link1.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: 12,
},
}}
label="I have read and accept the code of conduct."
onChange={onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
};

View File

@@ -1,29 +0,0 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent,
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
private key: string;
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.reset();
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent key={this.key} {...this.props} />;
}
public reset(): void {
this.key = `GalleryAndNotebookViewerComponent-${Date.now()}`;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -34,6 +34,7 @@ import { CodeOfConductComponent } from "./CodeOfConductComponent";
import "./GalleryViewerComponent.less"; import "./GalleryViewerComponent.less";
import { InfoComponent } from "./InfoComponent/InfoComponent"; import { InfoComponent } from "./InfoComponent/InfoComponent";
const CARD_WIDTH = 256;
export interface GalleryViewerComponentProps { export interface GalleryViewerComponentProps {
container?: Explorer; container?: Explorer;
junoClient: JunoClient; junoClient: JunoClient;
@@ -643,7 +644,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => { private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
if (itemIndex === 0) { if (itemIndex === 0) {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount; this.columnCount = Math.floor(visibleRect.width / CARD_WIDTH) || this.columnCount;
this.rowCount = GalleryViewerComponent.rowsPerPage; this.rowCount = GalleryViewerComponent.rowsPerPage;
} }

View File

@@ -136,15 +136,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.container = this.collection?.container; this.container = this.collection?.container;
this.offer = this.collection?.offer(); this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl(); this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
this.shouldShowIndexingPolicyEditor = this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
this.container && userContext.apiType !== "Cassandra" && !this.container.isPreferredApiMongoDB();
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy; this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
// Mongo container with system partition key still treat as "Fixed" // Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer = this.isFixedContainer =
this.container.isPreferredApiMongoDB() && userContext.apiType === "Mongo" && (!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
(!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
} else { } else {
this.database = this.props.settingsTab.database; this.database = this.props.settingsTab.database;
this.container = this.database?.container; this.container = this.database?.container;
@@ -236,7 +234,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
public loadMongoIndexes = async (): Promise<void> => { public loadMongoIndexes = async (): Promise<void> => {
if ( if (
this.container.isPreferredApiMongoDB() && userContext.apiType === "Mongo" &&
this.container.isEnableMongoCapabilityPresent() && this.container.isEnableMongoCapabilityPresent() &&
this.container.databaseAccount() this.container.databaseAccount()
) { ) {
@@ -1002,7 +1000,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
tab: SettingsV2TabTypes.IndexingPolicyTab, tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />, content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
}); });
} else if (this.container.isPreferredApiMongoDB()) { } else if (userContext.apiType === "Mongo") {
const mongoIndexTabContext = this.getMongoIndexTabContent(mongoIndexingPolicyComponentProps); const mongoIndexTabContext = this.getMongoIndexTabContent(mongoIndexingPolicyComponentProps);
if (mongoIndexTabContext) { if (mongoIndexTabContext) {
tabs.push({ tabs.push({

View File

@@ -1,9 +1,9 @@
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as monaco from "monaco-editor"; import { loadMonaco, monaco } from "../../../LazyMonaco";
import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface IndexingPolicyComponentProps { export interface IndexingPolicyComponentProps {
@@ -84,9 +84,9 @@ export class IndexingPolicyComponent extends React.Component<
return false; return false;
}; };
private createIndexingPolicyEditor = (): void => { private async createIndexingPolicyEditor(): Promise<void> {
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4); const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
const monaco = await loadMonaco();
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, { this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
value: value, value: value,
language: "json", language: "json",
@@ -98,7 +98,7 @@ export class IndexingPolicyComponent extends React.Component<
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
this.props.logIndexingPolicySuccessMessage(); this.props.logIndexingPolicySuccessMessage();
} }
}; }
private onEditorContentChange = (): void => { private onEditorContentChange = (): void => {
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();

View File

@@ -323,7 +323,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
userContext.apiType === "Cassandra" || userContext.apiType === "Cassandra" ||
userContext.apiType === "Tables" || userContext.apiType === "Tables" ||
!this.props.collection.partitionKeyProperty || !this.props.collection.partitionKeyProperty ||
(this.props.container.isPreferredApiMongoDB() && this.props.collection.partitionKey.systemKey) (userContext.apiType === "Mongo" && this.props.collection.partitionKey.systemKey)
) { ) {
return false; return false;
} }

View File

@@ -415,7 +415,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
</Text> </Text>
<Text> <Text>
<em> <em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em> </em>
</Text> </Text>
</Stack> </Stack>
@@ -689,7 +689,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
</Text> </Text>
<Text> <Text>
<em> <em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em> </em>
</Text> </Text>
</Stack> </Stack>

View File

@@ -177,40 +177,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
@@ -233,9 +199,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -258,20 +222,6 @@ exports[`SettingsComponent renders 1`] = `
"userTableQuery": [Function], "userTableQuery": [Function],
"visible": [Function], "visible": [Function],
}, },
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
], ],
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
@@ -426,9 +376,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -479,40 +427,6 @@ exports[`SettingsComponent renders 1`] = `
"defaultExperience": [Function], "defaultExperience": [Function],
"deleteCollectionText": [Function], "deleteCollectionText": [Function],
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
"container": [Circular], "container": [Circular],
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
@@ -544,7 +458,6 @@ exports[`SettingsComponent renders 1`] = `
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isPreferredApiMongoDB": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -576,21 +489,9 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": ArrayHashMap { "databaseCollectionIdMap": Map {},
"store": HashMap { "koSubsCollectionIdMap": Map {},
"container": Object {}, "koSubsDatabaseIdMap": Map {},
},
},
"koSubsCollectionIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"koSubsDatabaseIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
@@ -616,20 +517,6 @@ exports[`SettingsComponent renders 1`] = `
"onResizeStop": [Function], "onResizeStop": [Function],
"splitterId": "h_splitter1", "splitterId": "h_splitter1",
}, },
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
"openedTabs": [Function], "openedTabs": [Function],
@@ -805,40 +692,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
@@ -861,9 +714,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -886,20 +737,6 @@ exports[`SettingsComponent renders 1`] = `
"userTableQuery": [Function], "userTableQuery": [Function],
"visible": [Function], "visible": [Function],
}, },
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
], ],
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
@@ -1054,9 +891,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -1107,40 +942,6 @@ exports[`SettingsComponent renders 1`] = `
"defaultExperience": [Function], "defaultExperience": [Function],
"deleteCollectionText": [Function], "deleteCollectionText": [Function],
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
"container": [Circular], "container": [Circular],
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
@@ -1172,7 +973,6 @@ exports[`SettingsComponent renders 1`] = `
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isPreferredApiMongoDB": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -1204,21 +1004,9 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": ArrayHashMap { "databaseCollectionIdMap": Map {},
"store": HashMap { "koSubsCollectionIdMap": Map {},
"container": Object {}, "koSubsDatabaseIdMap": Map {},
},
},
"koSubsCollectionIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"koSubsDatabaseIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
@@ -1244,20 +1032,6 @@ exports[`SettingsComponent renders 1`] = `
"onResizeStop": [Function], "onResizeStop": [Function],
"splitterId": "h_splitter1", "splitterId": "h_splitter1",
}, },
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
"openedTabs": [Function], "openedTabs": [Function],
@@ -1446,40 +1220,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
@@ -1502,9 +1242,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -1527,20 +1265,6 @@ exports[`SettingsComponent renders 1`] = `
"userTableQuery": [Function], "userTableQuery": [Function],
"visible": [Function], "visible": [Function],
}, },
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
], ],
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
@@ -1695,9 +1419,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -1748,40 +1470,6 @@ exports[`SettingsComponent renders 1`] = `
"defaultExperience": [Function], "defaultExperience": [Function],
"deleteCollectionText": [Function], "deleteCollectionText": [Function],
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
"container": [Circular], "container": [Circular],
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
@@ -1813,7 +1501,6 @@ exports[`SettingsComponent renders 1`] = `
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isPreferredApiMongoDB": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -1845,21 +1532,9 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": ArrayHashMap { "databaseCollectionIdMap": Map {},
"store": HashMap { "koSubsCollectionIdMap": Map {},
"container": Object {}, "koSubsDatabaseIdMap": Map {},
},
},
"koSubsCollectionIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"koSubsDatabaseIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
@@ -1885,20 +1560,6 @@ exports[`SettingsComponent renders 1`] = `
"onResizeStop": [Function], "onResizeStop": [Function],
"splitterId": "h_splitter1", "splitterId": "h_splitter1",
}, },
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
"openedTabs": [Function], "openedTabs": [Function],
@@ -2074,40 +1735,6 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
@@ -2130,9 +1757,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -2155,20 +1780,6 @@ exports[`SettingsComponent renders 1`] = `
"userTableQuery": [Function], "userTableQuery": [Function],
"visible": [Function], "visible": [Function],
}, },
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
], ],
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
@@ -2323,9 +1934,7 @@ exports[`SettingsComponent renders 1`] = `
"keyspaceHasSharedOffer": [Function], "keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function], "keyspaceId": [Function],
"keyspaceIds": [Function], "keyspaceIds": [Function],
"keyspaceOffers": HashMap { "keyspaceOffers": Map {},
"container": Object {},
},
"keyspaceThroughput": [Function], "keyspaceThroughput": [Function],
"maxThroughputRU": [Function], "maxThroughputRU": [Function],
"minThroughputRU": [Function], "minThroughputRU": [Function],
@@ -2376,40 +1985,6 @@ exports[`SettingsComponent renders 1`] = `
"defaultExperience": [Function], "defaultExperience": [Function],
"deleteCollectionText": [Function], "deleteCollectionText": [Function],
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
"attributeValueLabel": "Value",
"canAdd": [Function],
"canApply": [Function],
"container": [Circular],
"dataTypeLabel": "Type",
"displayedAttributes": [Function],
"editAttribute": [Function],
"editButtonLabel": "Edit",
"editingProperty": [Function],
"edmTypes": [Function],
"finishEditingAttribute": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "edittableentitypane",
"insertAttribute": [Function],
"isEditing": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"onAddPropertyKeyDown": [Function],
"onBackButtonKeyDown": [Function],
"onDeletePropertyKeyDown": [Function],
"onEditPropertyKeyDown": [Function],
"onKeyUp": [Function],
"removeAttribute": [Function],
"removeButtonLabel": "Remove",
"scrollId": [Function],
"submitButtonText": [Function],
"title": [Function],
"visible": [Function],
},
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
"container": [Circular], "container": [Circular],
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
@@ -2441,7 +2016,6 @@ exports[`SettingsComponent renders 1`] = `
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isPreferredApiMongoDB": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -2473,21 +2047,9 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"databaseCollectionIdMap": ArrayHashMap { "databaseCollectionIdMap": Map {},
"store": HashMap { "koSubsCollectionIdMap": Map {},
"container": Object {}, "koSubsDatabaseIdMap": Map {},
},
},
"koSubsCollectionIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"koSubsDatabaseIdMap": ArrayHashMap {
"store": HashMap {
"container": Object {},
},
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
@@ -2513,20 +2075,6 @@ exports[`SettingsComponent renders 1`] = `
"onResizeStop": [Function], "onResizeStop": [Function],
"splitterId": "h_splitter1", "splitterId": "h_splitter1",
}, },
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager { "tabsManager": TabsManager {
"activeTab": [Function], "activeTab": [Function],
"openedTabs": [Function], "openedTabs": [Function],

View File

@@ -150,7 +150,7 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text> <Text>
<em> <em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em> </em>
</Text> </Text>
</Stack> </Stack>

View File

@@ -11,10 +11,6 @@
padding: 0 @LargeSpace 0 @SmallSpace; padding: 0 @LargeSpace 0 @SmallSpace;
} }
.throughputInputSpacing { .throughputInputSpacing > :not(:last-child) {
margin-bottom: @SmallSpace; margin-bottom: @DefaultSpace;
& > * {
margin-bottom: @SmallSpace;
}
} }

View File

@@ -3,11 +3,13 @@ import React from "react";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import * as SharedConstants from "../../../Shared/Constants"; import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils"; import * as PricingUtils from "../../../Utils/PricingUtils";
export interface ThroughputInputProps { export interface ThroughputInputProps {
isDatabase: boolean; isDatabase: boolean;
isSharded: boolean;
showFreeTierExceedThroughputTooltip: boolean; showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void; setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void; setIsAutoscale: (isAutoscale: boolean) => void;
@@ -18,6 +20,7 @@ export interface ThroughputInputState {
isAutoscaleSelected: boolean; isAutoscaleSelected: boolean;
throughput: number; throughput: number;
isCostAcknowledged: boolean; isCostAcknowledged: boolean;
throughputError: string;
} }
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> { export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
@@ -28,6 +31,7 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
isAutoscaleSelected: true, isAutoscaleSelected: true,
throughput: AutoPilotUtils.minAutoPilotThroughput, throughput: AutoPilotUtils.minAutoPilotThroughput,
isCostAcknowledged: false, isCostAcknowledged: false,
throughputError: undefined,
}; };
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput); this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
@@ -39,11 +43,11 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
<div className="throughputInputContainer throughputInputSpacing"> <div className="throughputInputContainer throughputInputSpacing">
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text variant="small" style={{ lineHeight: "20px" }}> <Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
{this.getThroughputLabelText()} {this.getThroughputLabelText()}
</Text> </Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}> <TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -74,7 +78,7 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
{this.state.isAutoscaleSelected && ( {this.state.isAutoscaleSelected && (
<Stack className="throughputInputSpacing"> <Stack className="throughputInputSpacing">
<Text variant="small"> <Text variant="small">
Provision maximum RU/s required by this resource. Estimate your required RU/s with&nbsp; Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/"> <Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
capacity calculator capacity calculator
</Link> </Link>
@@ -82,11 +86,11 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
</Text> </Text>
<Stack horizontal> <Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px" }}> <Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Max RU/s {this.props.isDatabase ? "Database" : getCollectionName()} max RU/s
</Text> </Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}> <TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -101,11 +105,12 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
min={AutoPilotUtils.minAutoPilotThroughput} min={AutoPilotUtils.minAutoPilotThroughput}
value={this.state.throughput.toString()} value={this.state.throughput.toString()}
aria-label="Max request units per second" aria-label="Max request units per second"
required={true} errorMessage={this.state.throughputError}
/> />
<Text variant="small"> <Text variant="small">
Your {this.props.isDatabase ? "database" : "container"} throughput will automatically scale from{" "} Your {this.props.isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will
automatically scale from{" "}
<b> <b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "} {AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
{this.state.throughput} RU/s {this.state.throughput} RU/s
@@ -147,6 +152,7 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
value={this.state.throughput.toString()} value={this.state.throughput.toString()}
aria-label="Max request units per second" aria-label="Max request units per second"
required={true} required={true}
errorMessage={this.state.throughputError}
/> />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -156,6 +162,7 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && ( {this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start"> <Stack horizontal verticalAlign="start">
<span className="mandatoryStar">*&nbsp;</span>
<Checkbox <Checkbox
checked={this.state.isCostAcknowledged} checked={this.state.isCostAcknowledged}
styles={{ styles={{
@@ -177,30 +184,35 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
} }
private getThroughputLabelText(): string { private getThroughputLabelText(): string {
let throughputHeaderText: string;
if (this.state.isAutoscaleSelected) { if (this.state.isAutoscaleSelected) {
return AutoPilotUtils.getAutoPilotHeaderText(); throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
} else {
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
} }
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString(); return `${this.props.isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
return this.state.isAutoscaleSelected
? AutoPilotUtils.getAutoPilotHeaderText()
: `Throughput (${minRU} - ${maxRU} RU/s)`;
} }
private onThroughputValueChange(newInput: string): void { private onThroughputValueChange(newInput: string): void {
const newThroughput = parseInt(newInput); const newThroughput = parseInt(newInput);
this.setState({ throughput: newThroughput }); this.setState({ throughput: newThroughput });
this.props.setThroughputValue(newThroughput); this.props.setThroughputValue(newThroughput);
if (!this.props.isSharded && newThroughput > 10000) {
this.setState({ throughputError: "Unsharded collections support up to 10,000 RUs" });
} else {
this.setState({ throughputError: undefined });
}
} }
private getAutoScaleTooltip(): string { private getAutoScaleTooltip(): string {
return `After the first ${AutoPilotUtils.getStorageBasedOnUserInput( const collectionName = getCollectionName().toLocaleLowerCase();
this.state.throughput return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
)} GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value.`;
} }
private getCostAcknowledgeText(): string { private getCostAcknowledgeText(): string {
@@ -271,10 +283,20 @@ const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props:
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier ? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
: PricingUtils.getPricePerRu(serverId) * multiplier; : PricingUtils.getPricePerRu(serverId) * multiplier;
const iconWithEstimatedCostDisclaimer: JSX.Element = (
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={PricingUtils.estimatedCostDisclaimer}
styles={{ root: { verticalAlign: "bottom" } }}
>
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
);
if (isAutoscale) { if (isAutoscale) {
return ( return (
<Text variant="small"> <Text variant="small">
Estimated monthly cost ({currency}):{" "} Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b> <b>
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "} {currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "} {currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
@@ -287,7 +309,7 @@ const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props:
return ( return (
<Text variant="small"> <Text variant="small">
Cost ({currency}):{" "} Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b> <b>
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "} {currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "} {currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
@@ -295,8 +317,6 @@ const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props:
</b> </b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "} ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU) {currencySign + pricePerRu}/RU)
<br />
<em>{PricingUtils.estimatedCostDisclaimer}</em>
</Text> </Text>
); );
}; };

View File

@@ -14,7 +14,6 @@ describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): Explorer => { const createExplorerStub = (database: ViewModels.Database): Explorer => {
const explorerStub = {} as Explorer; const explorerStub = {} as Explorer;
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]); explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false); explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
explorerStub.findDatabaseWithId = () => database; explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Q.resolve(); explorerStub.refreshAllDatabases = () => Q.resolve();

View File

@@ -6,7 +6,6 @@ import React from "react";
import _ from "underscore"; import _ from "underscore";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { ExplorerMetrics } from "../Common/Constants"; import { ExplorerMetrics } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
@@ -26,27 +25,28 @@ import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProvi
import { RouteHandler } from "../RouteHandlers/RouteHandler"; import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { trackEvent } from "../Shared/appInsights"; import { trackEvent } from "../Shared/appInsights";
import * as SharedConstants from "../Shared/Constants"; import * as SharedConstants from "../Shared/Constants";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager"; import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
import { updateUserContext, userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getCollectionName } from "../Utils/APITypeUtils";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { stringToBlob } from "../Utils/BlobUtils"; import { stringToBlob } from "../Utils/BlobUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import * as PricingUtils from "../Utils/PricingUtils";
import * as ComponentRegisterer from "./ComponentRegisterer"; import * as ComponentRegisterer from "./ComponentRegisterer";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker"; import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { DialogProps, TextFieldProps } from "./Controls/Dialog"; import { DialogProps, TextFieldProps } from "./Controls/Dialog";
import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import type NotebookManager from "./Notebook/NotebookManager";
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
import { NotebookUtil } from "./Notebook/NotebookUtil"; import { NotebookUtil } from "./Notebook/NotebookUtil";
import AddCollectionPane from "./Panes/AddCollectionPane"; import AddCollectionPane from "./Panes/AddCollectionPane";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
@@ -62,9 +62,9 @@ import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane";
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane/SettingsPane"; import { SettingsPane } from "./Panes/SettingsPane/SettingsPane";
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
import { StringInputPane } from "./Panes/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel"; import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import { EditTableEntityPanel } from "./Panes/Tables/EditTableEntityPanel";
import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel"; import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel";
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
@@ -73,7 +73,6 @@ import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
import QueryTablesTab from "./Tabs/QueryTablesTab"; import QueryTablesTab from "./Tabs/QueryTablesTab";
import TabsBase from "./Tabs/TabsBase";
import { TabsManager } from "./Tabs/TabsManager"; import { TabsManager } from "./Tabs/TabsManager";
import TerminalTab from "./Tabs/TerminalTab"; import TerminalTab from "./Tabs/TerminalTab";
import Database from "./Tree/Database"; import Database from "./Tree/Database";
@@ -118,11 +117,6 @@ export default class Explorer {
* Use userContext.apiType instead * Use userContext.apiType instead
* */ * */
public defaultExperience: ko.Observable<string>; public defaultExperience: ko.Observable<string>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Mongo"
* */
public isPreferredApiMongoDB: ko.Computed<boolean>;
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>; public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
/** /**
* @deprecated * @deprecated
@@ -165,19 +159,14 @@ export default class Explorer {
// Tabs // Tabs
public isTabsContentExpanded: ko.Observable<boolean>; public isTabsContentExpanded: ko.Observable<boolean>;
public galleryTab: any;
public notebookViewerTab: any;
public tabsManager: TabsManager; public tabsManager: TabsManager;
// Contextual panes // Contextual panes
public addDatabasePane: AddDatabasePane; public addDatabasePane: AddDatabasePane;
public addCollectionPane: AddCollectionPane; public addCollectionPane: AddCollectionPane;
public graphStylingPane: GraphStylingPane; public graphStylingPane: GraphStylingPane;
public editTableEntityPane: EditTableEntityPane;
public cassandraAddCollectionPane: CassandraAddCollectionPane; public cassandraAddCollectionPane: CassandraAddCollectionPane;
public stringInputPane: StringInputPane;
public gitHubReposPane: ContextualPaneBase; public gitHubReposPane: ContextualPaneBase;
public publishNotebookPaneAdapter: ReactAdapter;
// features // features
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
@@ -203,7 +192,7 @@ export default class Explorer {
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>; public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>; public isSynapseLinkUpdating: ko.Observable<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded public notebookManager?: NotebookManager;
public openDialog: ExplorerParams["openDialog"]; public openDialog: ExplorerParams["openDialog"];
public closeDialog: ExplorerParams["closeDialog"]; public closeDialog: ExplorerParams["closeDialog"];
@@ -383,16 +372,16 @@ export default class Explorer {
direction: SplitterDirection.Vertical, direction: SplitterDirection.Vertical,
}); });
this.defaultExperience = ko.observable<string>(); this.defaultExperience = ko.observable<string>();
this.databaseAccount.subscribe((databaseAccount) => { // this.databaseAccount.subscribe((databaseAccount) => {
const defaultExperience: string = DefaultExperienceUtility.getDefaultExperienceFromDatabaseAccount( // const defaultExperience: string = DefaultExperienceUtility.getDefaultExperienceFromDatabaseAccount(
databaseAccount // databaseAccount
); // );
this.defaultExperience(defaultExperience); // this.defaultExperience(defaultExperience);
// TODO. Remove this entirely // // TODO. Remove this entirely
updateUserContext({ // updateUserContext({
defaultExperience: DefaultExperienceUtility.mapDefaultExperienceStringToEnum(defaultExperience), // apiType: DefaultExperienceUtility.mapDefaultExperienceStringToEnum(defaultExperience),
}); // });
}); // });
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => { this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
if (userContext.features.enableFixedCollectionWithSharedThroughput) { if (userContext.features.enableFixedCollectionWithSharedThroughput) {
@@ -414,27 +403,6 @@ export default class Explorer {
) !== undefined ) !== undefined
); );
this.isPreferredApiMongoDB = ko.computed(() => {
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
if (defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.MongoDB.toLowerCase()) {
return true;
}
if (defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.ApiForMongoDB.toLowerCase()) {
return true;
}
if (
this.databaseAccount &&
this.databaseAccount() &&
this.databaseAccount().kind.toLowerCase() === Constants.AccountKind.MongoDB
) {
return true;
}
return false;
});
this.isEnableMongoCapabilityPresent = ko.computed(() => { this.isEnableMongoCapabilityPresent = ko.computed(() => {
const capabilities = this.databaseAccount && this.databaseAccount()?.properties?.capabilities; const capabilities = this.databaseAccount && this.databaseAccount()?.properties?.capabilities;
if (!capabilities) { if (!capabilities) {
@@ -500,13 +468,6 @@ export default class Explorer {
container: this, container: this,
}); });
this.editTableEntityPane = new EditTableEntityPane({
id: "edittableentitypane",
visible: ko.observable<boolean>(false),
container: this,
});
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({ this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
id: "cassandraaddcollectionpane", id: "cassandraaddcollectionpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -514,13 +475,6 @@ export default class Explorer {
container: this, container: this,
}); });
this.stringInputPane = new StringInputPane({
id: "stringinputpane",
visible: ko.observable<boolean>(false),
container: this,
});
this.tabsManager = params?.tabsManager ?? new TabsManager(); this.tabsManager = params?.tabsManager ?? new TabsManager();
this.tabsManager.openedTabs.subscribe((tabs) => { this.tabsManager.openedTabs.subscribe((tabs) => {
if (tabs.length === 0) { if (tabs.length === 0) {
@@ -533,9 +487,7 @@ export default class Explorer {
this.addDatabasePane, this.addDatabasePane,
this.addCollectionPane, this.addCollectionPane,
this.graphStylingPane, this.graphStylingPane,
this.editTableEntityPane,
this.cassandraAddCollectionPane, this.cassandraAddCollectionPane,
this.stringInputPane,
]; ];
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
this.isTabsContentExpanded = ko.observable(false); this.isTabsContentExpanded = ko.observable(false);
@@ -604,7 +556,6 @@ export default class Explorer {
this.addCollectionPane.collectionIdTitle("Table id"); this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables"); this.refreshTreeTitle("Refresh tables");
this.editTableEntityPane.title("Edit Table Entity");
this.tableDataClient = new TablesAPIDataClient(); this.tableDataClient = new TablesAPIDataClient();
break; break;
case "Cassandra": case "Cassandra":
@@ -618,7 +569,6 @@ export default class Explorer {
this.addCollectionPane.collectionIdTitle("Table id"); this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables"); this.refreshTreeTitle("Refresh tables");
this.editTableEntityPane.title("Edit Table Row");
this.tableDataClient = new CassandraAPIDataClient(); this.tableDataClient = new CassandraAPIDataClient();
break; break;
} }
@@ -636,10 +586,10 @@ export default class Explorer {
this.isNotebookEnabled = ko.observable(false); this.isNotebookEnabled = ko.observable(false);
this.isNotebookEnabled.subscribe(async () => { this.isNotebookEnabled.subscribe(async () => {
if (!this.notebookManager) { if (!this.notebookManager) {
const notebookManagerModule = await import( const NotebookManager = await (
/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager" await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager")
); ).default;
this.notebookManager = new notebookManagerModule.default(); this.notebookManager = new NotebookManager();
this.notebookManager.initialize({ this.notebookManager.initialize({
container: this, container: this,
notebookBasePath: this.notebookBasePath, notebookBasePath: this.notebookBasePath,
@@ -1424,10 +1374,13 @@ export default class Explorer {
return Promise.resolve(false); return Promise.resolve(false);
} }
public async publishNotebook(name: string, content: string | unknown, parentDomElement?: HTMLElement): Promise<void> { public async publishNotebook(
name: string,
content: NotebookPaneContent,
parentDomElement?: HTMLElement
): Promise<void> {
if (this.notebookManager) { if (this.notebookManager) {
await this.notebookManager.openPublishNotebookPane(name, content, parentDomElement); await this.notebookManager.openPublishNotebookPane(name, content, parentDomElement);
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
this.isPublishNotebookPaneEnabled(true); this.isPublishNotebookPaneEnabled(true);
} }
} }
@@ -1540,7 +1493,7 @@ export default class Explorer {
return true; return true;
} }
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> { public renameNotebook(notebookFile: NotebookContentItem): void {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled"; const error = "Attempt to rename notebook, but notebook is not enabled";
handleError(error, "Explorer/renameNotebook"); handleError(error, "Explorer/renameNotebook");
@@ -1556,57 +1509,59 @@ export default class Explorer {
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
return Q.reject(); } else {
this.openSidePanel(
"",
<StringInputPane
explorer={this}
closePanel={() => {
this.closeSidePanel();
this.resourceTree.triggerRender();
}}
inputLabel="Enter new notebook name"
submitButtonLabel="Rename"
errorMessage="Could not rename notebook"
inProgressMessage="Renaming notebook to"
successMessage="Renamed notebook to"
paneTitle="Rename Notebook"
defaultInput={FileSystemUtil.stripExtension(notebookFile.name, "ipynb")}
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
}
notebookFile={notebookFile}
/>
);
} }
const originalPath = notebookFile.path;
const result = this.stringInputPane
.openWithOptions<NotebookContentItem>({
errorMessage: "Could not rename notebook",
inProgressMessage: "Renaming notebook to",
successMessage: "Renamed notebook to",
inputLabel: "Enter new notebook name",
paneTitle: "Rename Notebook",
submitButtonLabel: "Rename",
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input),
})
.then((newNotebookFile) => {
const notebookTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
);
notebookTabs.forEach((tab) => {
tab.tabTitle(newNotebookFile.name);
tab.tabPath(newNotebookFile.path);
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
return newNotebookFile;
});
result.then(() => this.resourceTree.triggerRender());
return result;
} }
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> { public onCreateDirectory(parent: NotebookContentItem): void {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled"; const error = "Attempt to create notebook directory, but notebook is not enabled";
handleError(error, "Explorer/onCreateDirectory"); handleError(error, "Explorer/onCreateDirectory");
throw new Error(error); throw new Error(error);
} }
const result = this.stringInputPane.openWithOptions<NotebookContentItem>({ this.openSidePanel(
errorMessage: "Could not create directory ", "",
inProgressMessage: "Creating directory ", <StringInputPane
successMessage: "Created directory ", explorer={this}
inputLabel: "Enter new directory name", closePanel={() => {
paneTitle: "Create new directory", this.closeSidePanel();
submitButtonLabel: "Create", this.resourceTree.triggerRender();
defaultInput: "", }}
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input), errorMessage="Could not create directory "
}); inProgressMessage="Creating directory "
result.then(() => this.resourceTree.triggerRender()); successMessage="Created directory "
return result; inputLabel="Enter new directory name"
paneTitle="Create new directory"
submitButtonLabel="Create"
defaultInput=""
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input)
}
notebookFile={parent}
/>
);
} }
public readFile(notebookFile: NotebookContentItem): Promise<string> { public readFile(notebookFile: NotebookContentItem): Promise<string> {
@@ -1922,86 +1877,68 @@ export default class Explorer {
} }
public async openGallery( public async openGallery(
selectedTab?: GalleryTab, selectedTab?: GalleryTabKind,
notebookUrl?: string, notebookUrl?: string,
galleryItem?: IGalleryItem, galleryItem?: IGalleryItem,
isFavorite?: boolean isFavorite?: boolean
) { ) {
let title: string = "Gallery"; const title = "Gallery";
let hashLocation: string = "gallery"; const hashLocation = "gallery";
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
const galleryTab = this.tabsManager
.getTabs(ViewModels.CollectionTabKind.Gallery)
.find((tab) => tab.hashLocation() == hashLocation);
const galleryTabOptions: any = { if (galleryTab instanceof GalleryTab) {
// GalleryTabOptions
account: userContext.databaseAccount,
container: this,
junoClient: this.notebookManager?.junoClient,
selectedTab: selectedTab || GalleryTab.PublicGallery,
notebookUrl,
galleryItem,
isFavorite,
// TabOptions
tabKind: ViewModels.CollectionTabKind.Gallery,
title: title,
tabPath: title,
documentClientUtility: null,
isActive: ko.observable(false),
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
};
const galleryTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Gallery,
(tab) => tab.hashLocation() == hashLocation
);
let galleryTab = galleryTabs && galleryTabs[0];
if (galleryTab) {
this.tabsManager.activateTab(galleryTab); this.tabsManager.activateTab(galleryTab);
(galleryTab as any).reset(galleryTabOptions);
} else { } else {
if (!this.galleryTab) { this.tabsManager.activateNewTab(
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab"); new GalleryTab(
} {
const newTab = new this.galleryTab.default(galleryTabOptions); tabKind: ViewModels.CollectionTabKind.Gallery,
this.tabsManager.activateNewTab(newTab); title: title,
tabPath: title,
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
onLoadStartKey: null,
isTabsContentExpanded: ko.observable(true),
},
{
account: userContext.databaseAccount,
container: this,
junoClient: this.notebookManager?.junoClient,
selectedTab: selectedTab || GalleryTabKind.PublicGallery,
notebookUrl,
galleryItem,
isFavorite,
}
)
);
} }
} }
public async openNotebookViewer(notebookUrl: string) { public async openNotebookViewer(notebookUrl: string) {
const title = path.basename(notebookUrl); const title = path.basename(notebookUrl);
const hashLocation = notebookUrl; const hashLocation = notebookUrl;
const NotebookViewerTab = await (
await import(/* webpackChunkName: "NotebookViewerTab" */ "./Tabs/NotebookViewerTab")
).default;
if (!this.notebookViewerTab) { const notebookViewerTab = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2).find((tab) => {
this.notebookViewerTab = await import(/* webpackChunkName: "NotebookViewerTab" */ "./Tabs/NotebookViewerTab"); return tab.hashLocation() == hashLocation && tab instanceof NotebookViewerTab && tab.notebookUrl === notebookUrl;
}
const notebookViewerTabModule = this.notebookViewerTab;
let isNotebookViewerOpen = (tab: TabsBase) => {
const notebookViewerTab = tab as typeof notebookViewerTabModule.default;
return notebookViewerTab.notebookUrl === notebookUrl;
};
const notebookViewerTabs = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2, (tab) => {
return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab);
}); });
let notebookViewerTab = notebookViewerTabs && notebookViewerTabs[0];
if (notebookViewerTab) { if (notebookViewerTab) {
this.tabsManager.activateNewTab(notebookViewerTab); this.tabsManager.activateNewTab(notebookViewerTab);
} else { } else {
notebookViewerTab = new this.notebookViewerTab.default({ const notebookViewerTab = new NotebookViewerTab({
account: userContext.databaseAccount, account: userContext.databaseAccount,
tabKind: ViewModels.CollectionTabKind.NotebookViewer, tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null, node: null,
title: title, title: title,
tabPath: title, tabPath: title,
documentClientUtility: null,
collection: null, collection: null,
hashLocation: hashLocation, hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null, onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons, onUpdateTabsButtons: this.onUpdateTabsButtons,
@@ -2013,14 +1950,14 @@ export default class Explorer {
} }
} }
public onNewCollectionClicked(): void { public onNewCollectionClicked(databaseId?: string): void {
if (userContext.apiType === "Cassandra") { if (userContext.apiType === "Cassandra") {
this.cassandraAddCollectionPane.open(); this.cassandraAddCollectionPane.open();
} else if (userContext.features.enableReactPane) { } else if (userContext.features.enableKOPanel) {
this.openAddCollectionPanel();
} else {
this.addCollectionPane.open(this.selectedDatabaseId()); this.addCollectionPane.open(this.selectedDatabaseId());
document.getElementById("linkAddCollection").focus(); document.getElementById("linkAddCollection").focus();
} else {
this.openAddCollectionPanel(databaseId);
} }
} }
@@ -2124,14 +2061,9 @@ export default class Explorer {
} }
public openDeleteCollectionConfirmationPane(): void { public openDeleteCollectionConfirmationPane(): void {
let collectionName = PricingUtils.getCollectionName(userContext.defaultExperience);
this.openSidePanel( this.openSidePanel(
"Delete " + collectionName, "Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane <DeleteCollectionConfirmationPane explorer={this} closePanel={this.closeSidePanel} />
explorer={this}
collectionName={collectionName}
closePanel={this.closeSidePanel}
/>
); );
} }
@@ -2152,28 +2084,32 @@ export default class Explorer {
} }
public openSettingPane(): void { public openSettingPane(): void {
this.openSidePanel("Settings", <SettingsPane explorer={this} closePanel={this.closeSidePanel} />); this.openSidePanel(
"Settings",
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
);
} }
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void { public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
this.openSidePanel( this.openSidePanel(
"Input parameters", "Input parameters",
<ExecuteSprocParamsPane <ExecuteSprocParamsPane
explorer={this}
storedProcedure={storedProcedure} storedProcedure={storedProcedure}
expandConsole={() => this.expandConsole()}
closePanel={() => this.closeSidePanel()} closePanel={() => this.closeSidePanel()}
/> />
); );
} }
public async openAddCollectionPanel(): Promise<void> { public async openAddCollectionPanel(databaseId?: string): Promise<void> {
await this.loadDatabaseOffers(); await this.loadDatabaseOffers();
this.openSidePanel( this.openSidePanel(
"New Collection", "New " + getCollectionName(),
<AddCollectionPanel <AddCollectionPanel
explorer={this} explorer={this}
closePanel={() => this.closeSidePanel()} closePanel={() => this.closeSidePanel()}
openNotificationConsole={() => this.expandConsole()} openNotificationConsole={() => this.expandConsole()}
databaseId={databaseId}
/> />
); );
} }
@@ -2195,7 +2131,7 @@ export default class Explorer {
this.openSidePanel( this.openSidePanel(
"Upload File", "Upload File",
<UploadFilePane <UploadFilePane
explorer={this} expandConsole={() => this.expandConsole()}
closePanel={this.closeSidePanel} closePanel={this.closeSidePanel}
uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)} uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)}
/> />
@@ -2227,6 +2163,19 @@ export default class Explorer {
); );
} }
public openEditTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
this.openSidePanel(
"Edit Table Entity",
<EditTableEntityPanel
explorer={this}
closePanel={this.closeSidePanel}
queryTablesTab={queryTablesTab}
tableEntityListViewModel={tableEntityListViewModel}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
}
public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void { public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void {
this.openSidePanel( this.openSidePanel(
"Select Column", "Select Column",

View File

@@ -11,7 +11,6 @@ import * as ko from "knockout";
import Q from "q"; import Q from "q";
import _ from "underscore"; import _ from "underscore";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { HashMap } from "../../../Common/HashMap";
import { NeighborType } from "../../../Contracts/ViewModels"; import { NeighborType } from "../../../Contracts/ViewModels";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { GraphConfig } from "../../Tabs/GraphTab"; import { GraphConfig } from "../../Tabs/GraphTab";
@@ -195,8 +194,8 @@ export class D3ForceGraph implements GraphRenderer {
* Count edges and store in a hashmap: vertex id <--> number of links * Count edges and store in a hashmap: vertex id <--> number of links
* @param linkSelection * @param linkSelection
*/ */
public static countEdges(links: D3Link[]): HashMap<number> { public static countEdges(links: D3Link[]): Map<string, number> {
const countMap = new HashMap<number>(); const countMap = new Map<string, number>();
links.forEach((l: D3Link) => { links.forEach((l: D3Link) => {
let val = countMap.get(l.inV) || 0; let val = countMap.get(l.inV) || 0;
val += 1; val += 1;
@@ -407,7 +406,7 @@ export class D3ForceGraph implements GraphRenderer {
const rootId = graph.findRootNodeId(); const rootId = graph.findRootNodeId();
// Remember nodes current position // Remember nodes current position
const posMap = new HashMap<Point2D>(); const posMap = new Map<string, Point2D>();
this.simulation.nodes().forEach((d: D3Node) => { this.simulation.nodes().forEach((d: D3Node) => {
if (d.x == undefined || d.y == undefined) { if (d.x == undefined || d.y == undefined) {
return; return;
@@ -501,8 +500,8 @@ export class D3ForceGraph implements GraphRenderer {
if (!nodes || nodes.length === 0) { if (!nodes || nodes.length === 0) {
return; return;
} }
const nodeFinalPositionMap = new HashMap<Point2D>();
const nodeFinalPositionMap = new Map<string, Point2D>();
const viewCenter = this.viewCenter; const viewCenter = this.viewCenter;
const nonFixedNodes = _.filter(nodes, (node: D3Node) => { const nonFixedNodes = _.filter(nodes, (node: D3Node) => {
return !node._isFixedPosition && node.x === viewCenter.x && node.y === viewCenter.y; return !node._isFixedPosition && node.x === viewCenter.x && node.y === viewCenter.y;
@@ -559,7 +558,7 @@ export class D3ForceGraph implements GraphRenderer {
newNodes.selectAll(".loadmore").attr("visibility", "hidden").transition().delay(600).attr("visibility", "visible"); newNodes.selectAll(".loadmore").attr("visibility", "hidden").transition().delay(600).attr("visibility", "visible");
} }
private restartSimulation(graph: GraphData<D3Node, D3Link>, posMap: HashMap<Point2D>) { private restartSimulation(graph: GraphData<D3Node, D3Link>, posMap: Map<string, Point2D>) {
if (!graph) { if (!graph) {
return; return;
} }

View File

@@ -1,5 +1,5 @@
import { ObjectCache } from "../../../Common/ObjectCache"; import { ObjectCache } from "../../../Common/ObjectCache";
import { GremlinVertex, GraphData } from "./GraphData"; import { GraphData, GremlinVertex } from "./GraphData";
/** /**
* Remember vertex edge information * Remember vertex edge information
@@ -10,9 +10,8 @@ export class EdgeInfoCache extends ObjectCache<GremlinVertex> {
* @param vertex * @param vertex
*/ */
public addVertex(vertex: GremlinVertex): void { public addVertex(vertex: GremlinVertex): void {
let v: GremlinVertex; let v = super.get(vertex.id);
if (super.has(vertex.id)) { if (super.has(vertex.id) && v) {
v = super.get(vertex.id);
GraphData.addEdgeInfoToVertex(v, vertex); GraphData.addEdgeInfoToVertex(v, vertex);
v._outEdgeIds = vertex._outEdgeIds; v._outEdgeIds = vertex._outEdgeIds;
v._inEdgeIds = vertex._inEdgeIds; v._inEdgeIds = vertex._inEdgeIds;
@@ -29,8 +28,8 @@ export class EdgeInfoCache extends ObjectCache<GremlinVertex> {
* @param id * @param id
*/ */
public mergeEdgeInfo(target: GremlinVertex): void { public mergeEdgeInfo(target: GremlinVertex): void {
if (super.has(target.id)) { const cachedVertex = super.get(target.id);
const cachedVertex = super.get(target.id); if (super.has(target.id) && cachedVertex) {
GraphData.addEdgeInfoToVertex(target, cachedVertex); GraphData.addEdgeInfoToVertex(target, cachedVertex);
target._outEdgeIds = cachedVertex._outEdgeIds; target._outEdgeIds = cachedVertex._outEdgeIds;
target._inEdgeIds = cachedVertex._inEdgeIds; target._inEdgeIds = cachedVertex._inEdgeIds;

View File

@@ -1,63 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GraphConfig } from "../../Tabs/GraphTab";
import * as ViewModels from "../../../Contracts/ViewModels";
import { GraphExplorer, GraphAccessor } from "./GraphExplorer";
interface Parameter {
onIsNewVertexDisabledChange: (isEnabled: boolean) => void;
onGraphAccessorCreated: (instance: GraphAccessor) => void;
onIsFilterQueryLoading: (isFilterQueryLoading: boolean) => void;
onIsValidQuery: (isValidQuery: boolean) => void;
onIsPropertyEditing: (isEditing: boolean) => void;
onIsGraphDisplayed: (isDisplayed: boolean) => void;
onResetDefaultGraphConfigValues: () => void;
graphConfigUiData: ViewModels.GraphConfigUiData;
graphConfig?: GraphConfig;
collectionPartitionKeyProperty: string;
graphBackendEndpoint: string;
databaseId: string;
collectionId: string;
masterKey: string;
onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void;
resourceId: string;
}
export class GraphExplorerAdapter implements ReactAdapter {
public params: Parameter;
public parameters = {};
public isNewVertexDisabled: boolean;
public constructor(params: Parameter) {
this.params = params;
}
public renderComponent(): JSX.Element {
return (
<GraphExplorer
onIsNewVertexDisabledChange={this.params.onIsNewVertexDisabledChange}
onGraphAccessorCreated={this.params.onGraphAccessorCreated}
onIsFilterQueryLoadingChange={this.params.onIsFilterQueryLoading}
onIsValidQueryChange={this.params.onIsValidQuery}
onIsPropertyEditing={this.params.onIsPropertyEditing}
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
graphBackendEndpoint={this.params.graphBackendEndpoint}
databaseId={this.params.databaseId}
collectionId={this.params.collectionId}
masterKey={this.params.masterKey}
onLoadStartKey={this.params.onLoadStartKey}
onLoadStartKeyChange={this.params.onLoadStartKeyChange}
resourceId={this.params.resourceId}
/* TODO Figure out how to make this Knockout-free */
graphConfigUiData={this.params.graphConfigUiData}
graphConfig={this.params.graphConfig}
/>
);
}
}

View File

@@ -1,7 +1,7 @@
import * as sinon from "sinon"; import * as sinon from "sinon";
import { GremlinClient, GremlinClientParameters } from "./GremlinClient";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { GremlinClient, GremlinClientParameters } from "./GremlinClient";
describe("Gremlin Client", () => { describe("Gremlin Client", () => {
const emptyParams: GremlinClientParameters = { const emptyParams: GremlinClientParameters = {
@@ -70,7 +70,7 @@ describe("Gremlin Client", () => {
gremlinClient.execute("fake query"); gremlinClient.execute("fake query");
gremlinClient.execute("fake query"); gremlinClient.execute("fake query");
gremlinClient.execute("fake query"); gremlinClient.execute("fake query");
expect(gremlinClient.pendingResults.size()).toBe(3); expect(gremlinClient.pendingResults.size).toBe(3);
}); });
it("should clean up pending request ids after success", async () => { it("should clean up pending request ids after success", async () => {
@@ -89,7 +89,7 @@ describe("Gremlin Client", () => {
return requestId; return requestId;
}); });
await gremlinClient.execute("fake query"); await gremlinClient.execute("fake query");
expect(gremlinClient.pendingResults.size()).toBe(0); expect(gremlinClient.pendingResults.size).toBe(0);
}); });
it("should log and display error out on unknown requestId", () => { it("should log and display error out on unknown requestId", () => {
@@ -247,7 +247,7 @@ describe("Gremlin Client", () => {
sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId); sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
gremlinClient.execute("fake query").finally(() => { gremlinClient.execute("fake query").finally(() => {
try { try {
expect(gremlinClient.pendingResults.size()).toBe(0); expect(gremlinClient.pendingResults.size).toBe(0);
done(); done();
} catch (e) { } catch (e) {
done(e); done(e);

View File

@@ -4,7 +4,6 @@
import * as Q from "q"; import * as Q from "q";
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
import { HashMap } from "../../../Common/HashMap";
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils"; import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { GremlinSimpleClient, Result } from "./GremlinSimpleClient"; import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
@@ -30,7 +29,7 @@ interface PendingResultData {
export class GremlinClient { export class GremlinClient {
public client: GremlinSimpleClient; public client: GremlinSimpleClient;
public pendingResults: HashMap<PendingResultData>; // public for testing purposes public pendingResults: Map<string, PendingResultData>; // public for testing purposes
private maxResultSize: number; private maxResultSize: number;
private static readonly PENDING_REQUEST_TIMEOUT_MS = 6 /* minutes */ * 60 /* seconds */ * 1000 /* ms */; private static readonly PENDING_REQUEST_TIMEOUT_MS = 6 /* minutes */ * 60 /* seconds */ * 1000 /* ms */;
private static readonly TIMEOUT_ERROR_MSG = `Pending request timed out (${GremlinClient.PENDING_REQUEST_TIMEOUT_MS} ms)`; private static readonly TIMEOUT_ERROR_MSG = `Pending request timed out (${GremlinClient.PENDING_REQUEST_TIMEOUT_MS} ms)`;
@@ -38,7 +37,7 @@ export class GremlinClient {
public initialize(params: GremlinClientParameters) { public initialize(params: GremlinClientParameters) {
this.destroy(); this.destroy();
this.pendingResults = new HashMap(); this.pendingResults = new Map();
this.maxResultSize = params.maxResultSize; this.maxResultSize = params.maxResultSize;
this.client = new GremlinSimpleClient({ this.client = new GremlinSimpleClient({
@@ -68,9 +67,9 @@ export class GremlinClient {
// Fail all pending requests if no request id (fatal) // Fail all pending requests if no request id (fatal)
if (!requestId) { if (!requestId) {
this.pendingResults.keys().forEach((reqId: string) => { for (const reqId of this.pendingResults.keys()) {
this.abortPendingRequest(reqId, errorMessage, null); this.abortPendingRequest(reqId, errorMessage, null);
}); }
} }
} else { } else {
this.abortPendingRequest(requestId, errorMessage, result.requestCharge); this.abortPendingRequest(requestId, errorMessage, result.requestCharge);

View File

@@ -0,0 +1,5 @@
import type * as monaco from "monaco-editor/esm/vs/editor/editor.api";
export type { monaco };
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const loadMonaco = () => import(/* webpackChunkName: "lazy-monaco" */ "monaco-editor/esm/vs/editor/editor.api");

View File

@@ -29,7 +29,6 @@ export class CommandBarComponentAdapter implements ReactAdapter {
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates // These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [ const toWatch = [
container.isPreferredApiMongoDB,
container.deleteCollectionText, container.deleteCollectionText,
container.deleteDatabaseText, container.deleteDatabaseText,
container.addCollectionText, container.addCollectionText,

View File

@@ -23,7 +23,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
@@ -67,7 +66,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
@@ -128,6 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer; mockExplorer = {} as Explorer;
mockExplorer.addDatabaseText = ko.observable("mockText");
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
@@ -143,16 +142,25 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
afterAll(() => {
updateUserContext({
apiType: "SQL",
});
});
beforeEach(() => { beforeEach(() => {
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => true); updateUserContext({
apiType: "Mongo",
});
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false); mockExplorer.isRunningOnNationalCloud = ko.observable(false);
}); });
it("Mongo Api not available - button should be hidden", () => { it("Mongo Api not available - button should be hidden", () => {
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); updateUserContext({
apiType: "SQL",
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeUndefined(); expect(openMongoShellBtn).toBeUndefined();
@@ -222,7 +230,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
@@ -321,7 +328,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);

View File

@@ -70,7 +70,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
buttons.push(createEnableNotebooksButton(container)); buttons.push(createEnableNotebooksButton(container));
} }
if (container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
buttons.push(createOpenMongoTerminalButton(container)); buttons.push(createOpenMongoTerminalButton(container));
} }
@@ -97,7 +97,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
} }
const isSupportedOpenQueryApi = const isSupportedOpenQueryApi =
userContext.apiType === "SQL" || container.isPreferredApiMongoDB() || userContext.apiType === "Gremlin"; userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Gremlin";
const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) { if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
const openQueryBtn = createOpenQueryButton(container); const openQueryBtn = createOpenQueryButton(container);
@@ -133,7 +133,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) { if (!container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = "New Shell"; const label = "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
@@ -145,7 +145,7 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB(), disabled: container.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
}; };
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }

View File

@@ -11,7 +11,7 @@ let fakeAjaxResponse: AjaxResponse = {
responseType: "json", responseType: "json",
}; };
export const sessions = { export const sessions = {
create: (serverConfig: unknown, body: object): Observable<AjaxResponse> => of(fakeAjaxResponse), create: (): Observable<AjaxResponse> => of(fakeAjaxResponse),
__setResponse: (response: AjaxResponse) => { __setResponse: (response: AjaxResponse) => {
fakeAjaxResponse = response; fakeAjaxResponse = response;
}, },

View File

@@ -220,7 +220,7 @@ export class NotebookContentClient {
return this.contentProvider return this.contentProvider
.remove(this.getServerConfig(), path) .remove(this.getServerConfig(), path)
.toPromise() .toPromise()
.then((xhr: AjaxResponse) => path); .then(() => path);
} }
/** /**

View File

@@ -3,7 +3,7 @@
*/ */
import { ImmutableNotebook } from "@nteract/commutable"; import { ImmutableNotebook } from "@nteract/commutable";
import { IContentProvider } from "@nteract/core"; import type { IContentProvider } from "@nteract/core";
import ko from "knockout"; import ko from "knockout";
import React from "react"; import React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
@@ -22,12 +22,16 @@ import Explorer from "../Explorer";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase"; import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPane } from "../Panes/GitHubReposPane"; import { GitHubReposPane } from "../Panes/GitHubReposPane";
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter"; import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider"; import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContainerClient } from "./NotebookContainerClient";
import { NotebookContentClient } from "./NotebookContentClient"; import { NotebookContentClient } from "./NotebookContentClient";
type NotebookPaneContent = string | ImmutableNotebook;
export type { NotebookPaneContent };
export interface NotebookManagerOptions { export interface NotebookManagerOptions {
container: Explorer; container: Explorer;
notebookBasePath: ko.Observable<string>; notebookBasePath: ko.Observable<string>;
@@ -49,7 +53,6 @@ export default class NotebookManager {
private gitHubClient: GitHubClient; private gitHubClient: GitHubClient;
public gitHubReposPane: ContextualPaneBase; public gitHubReposPane: ContextualPaneBase;
public publishNotebookPaneAdapter: PublishNotebookPaneAdapter;
public initialize(params: NotebookManagerOptions): void { public initialize(params: NotebookManagerOptions): void {
this.params = params; this.params = params;
@@ -87,8 +90,6 @@ export default class NotebookManager {
this.notebookContentProvider this.notebookContentProvider
); );
this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient);
this.gitHubOAuthService.getTokenObservable().subscribe((token) => { this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
this.gitHubClient.setToken(token?.access_token); this.gitHubClient.setToken(token?.access_token);
@@ -116,10 +117,23 @@ export default class NotebookManager {
public async openPublishNotebookPane( public async openPublishNotebookPane(
name: string, name: string,
content: string | ImmutableNotebook, content: NotebookPaneContent,
parentDomElement: HTMLElement parentDomElement: HTMLElement
): Promise<void> { ): Promise<void> {
await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement); const explorer = this.params.container;
explorer.openSidePanel(
"New Collection",
<PublishNotebookPane
explorer={this.params.container}
junoClient={this.junoClient}
closePanel={this.params.container.closeSidePanel}
openNotificationConsole={this.params.container.expandConsole}
name={name}
author={getFullName()}
notebookContent={content}
parentDomElement={parentDomElement}
/>
);
} }
public openCopyNotebookPane(name: string, content: string): void { public openCopyNotebookPane(name: string, content: string): void {

View File

@@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "./Explorer"; import Explorer from "./Explorer";
import { handleOpenAction } from "./OpenActions"; import { handleOpenAction } from "./OpenActions";
import AddCollectionPane from "./Panes/AddCollectionPane";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
describe("OpenActions", () => { describe("OpenActions", () => {
@@ -15,8 +14,7 @@ describe("OpenActions", () => {
beforeEach(() => { beforeEach(() => {
explorer = {} as Explorer; explorer = {} as Explorer;
explorer.addCollectionPane = {} as AddCollectionPane; explorer.onNewCollectionClicked = jest.fn();
explorer.addCollectionPane.open = jest.fn();
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane; explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
explorer.cassandraAddCollectionPane.open = jest.fn(); explorer.cassandraAddCollectionPane.open = jest.fn();
explorer.closeAllPanes = () => {}; explorer.closeAllPanes = () => {};
@@ -90,24 +88,24 @@ describe("OpenActions", () => {
}); });
describe("AddCollection pane kind", () => { describe("AddCollection pane kind", () => {
it("string value should call addCollectionPane.open", () => { it("string value should call explorer.onNewCollectionClicked", () => {
const action = { const action = {
actionType: "OpenPane", actionType: "OpenPane",
paneKind: "AddCollection", paneKind: "AddCollection",
}; };
const actionHandled = handleOpenAction(action, [], explorer); const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.addCollectionPane.open).toHaveBeenCalled(); expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
}); });
it("enum value should call addCollectionPane.open", () => { it("enum value should call explorer.onNewCollectionClicked", () => {
const action = { const action = {
actionType: "OpenPane", actionType: "OpenPane",
paneKind: ActionContracts.PaneKind.AddCollection, paneKind: ActionContracts.PaneKind.AddCollection,
}; };
const actionHandled = handleOpenAction(action, [], explorer); const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.addCollectionPane.open).toHaveBeenCalled(); expect(explorer.onNewCollectionClicked).toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -141,7 +141,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection] (<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
) { ) {
explorer.closeAllPanes(); explorer.closeAllPanes();
explorer.addCollectionPane.open(); explorer.onNewCollectionClicked();
} else if ( } else if (
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection || action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection] (<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]

View File

@@ -143,7 +143,6 @@
size="40" size="40"
class="collid" class="collid"
data-bind="visible: databaseCreateNew, textInput: databaseId, hasFocus: firstFieldHasFocus" data-bind="visible: databaseCreateNew, textInput: databaseId, hasFocus: firstFieldHasFocus"
aria-label="Database id"
autofocus autofocus
/> />
@@ -161,7 +160,6 @@
size="40" size="40"
class="collid" class="collid"
data-bind="visible: !databaseCreateNew(), textInput: databaseId, hasFocus: firstFieldHasFocus" data-bind="visible: !databaseCreateNew(), textInput: databaseId, hasFocus: firstFieldHasFocus"
aria-label="Database id"
/> />
<datalist id="databasesList" data-bind="foreach: databaseIds" data-bind="visible: databaseCreateNew"> <datalist id="databasesList" data-bind="foreach: databaseIds" data-bind="visible: databaseCreateNew">
@@ -246,15 +244,12 @@
placeholder="e.g., Container1" placeholder="e.g., Container1"
size="40" size="40"
class="textfontclr collid" class="textfontclr collid"
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }" data-bind="value: collectionId"
/> />
</div> </div>
<!-- Indexing For Shared Throughput - start --> <!-- Indexing For Shared Throughput - start -->
<div <div class="seconddivpadding" data-bind="visible: showIndexingOptionsForSharedThroughput() && !isMongo()">
class="seconddivpadding"
data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()"
>
<div <div
class="useIndexingForSharedThroughput createNewDatabaseOrUseExisting" class="useIndexingForSharedThroughput createNewDatabaseOrUseExisting"
aria-label="Indexing For Shared Throughput" aria-label="Indexing For Shared Throughput"
@@ -297,7 +292,7 @@
<p <p
class="seconddivpadding" class="seconddivpadding"
data-bind="visible: container.isPreferredApiMongoDB() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported" data-bind="visible: isMongo() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported"
> >
<span class="mandatoryStar">*</span> <span class="mandatoryStar">*</span>
<span class="addCollectionLabel">Storage capacity</span> <span class="addCollectionLabel">Storage capacity</span>
@@ -312,7 +307,7 @@
<div class="tabs"> <div class="tabs">
<div <div
tabindex="0" tabindex="0"
data-bind="event: { keydown: onStorageOptionsKeyDown }, visible: container.isPreferredApiMongoDB() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported" data-bind="event: { keydown: onStorageOptionsKeyDown }, visible: isMongo() && !databaseHasSharedOffer() || container.isFixedCollectionWithSharedThroughputSupported"
aria-label="Storage capacity" aria-label="Storage capacity"
> >
<!-- Fixed option button - Start --> <!-- Fixed option button - Start -->
@@ -355,7 +350,6 @@
attr: { attr: {
placeholder: partitionKeyPlaceholder, placeholder: partitionKeyPlaceholder,
required: partitionKeyVisible(), required: partitionKeyVisible(),
'aria-label': partitionKeyName,
pattern: partitionKeyPattern, pattern: partitionKeyPattern,
title: partitionKeyTitle title: partitionKeyTitle
}" }"

View File

@@ -119,7 +119,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.isPreferredApiTable = options.isPreferredApiTable; this.isPreferredApiTable = options.isPreferredApiTable;
this.partitionKey = ko.observable<string>(); this.partitionKey = ko.observable<string>();
this.partitionKey.subscribe((newPartitionKey: string) => { this.partitionKey.subscribe((newPartitionKey: string) => {
if (this.container.isPreferredApiMongoDB() || !newPartitionKey || newPartitionKey[0] === "/") { if (userContext.apiType === "Mongo" || !newPartitionKey || newPartitionKey[0] === "/") {
return; return;
} }
@@ -354,7 +354,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
// TODO: Create derived classes for Tables and Mongo to replace the If statements below // TODO: Create derived classes for Tables and Mongo to replace the If statements below
this.partitionKeyName = ko.computed<string>(() => { this.partitionKeyName = ko.computed<string>(() => {
if (this.container && !!this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return "Shard key"; return "Shard key";
} }
@@ -364,7 +364,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.lowerCasePartitionKeyName = ko.computed<string>(() => this.partitionKeyName().toLowerCase()); this.lowerCasePartitionKeyName = ko.computed<string>(() => this.partitionKeyName().toLowerCase());
this.partitionKeyPlaceholder = ko.computed<string>(() => { this.partitionKeyPlaceholder = ko.computed<string>(() => {
if (this.container && !!this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return "e.g., address.zipCode"; return "e.g., address.zipCode";
} }
@@ -376,7 +376,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
}); });
this.uniqueKeysPlaceholder = ko.pureComputed<string>(() => { this.uniqueKeysPlaceholder = ko.pureComputed<string>(() => {
if (this.container && !!this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return "Comma separated paths e.g. firstName,address.zipCode"; return "Comma separated paths e.g. firstName,address.zipCode";
} }
@@ -396,11 +396,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return false; return false;
} }
if ( if (userContext.apiType === "Mongo" && !this.isUnlimitedStorageSelected() && this.databaseHasSharedOffer()) {
this.container.isPreferredApiMongoDB() &&
!this.isUnlimitedStorageSelected() &&
this.databaseHasSharedOffer()
) {
return false; return false;
} }
@@ -480,7 +476,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
userContext.portalEnv, userContext.portalEnv,
this.isFreeTierAccount(), this.isFreeTierAccount(),
this.container.isFirstResourceCreated(), this.container.isFirstResourceCreated(),
this.container.defaultExperience(),
true true
); );
}); });
@@ -589,7 +584,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return true; return true;
} }
if (this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return true; return true;
} }
@@ -728,6 +723,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
} }
private isMongo(): boolean {
return userContext.apiType === "Mongo";
}
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) { private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
this.databaseIds(newDatabaseIds?.map((database: ViewModels.Database) => database.id())); this.databaseIds(newDatabaseIds?.map((database: ViewModels.Database) => database.id()));
} }
@@ -810,7 +809,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
let indexingPolicy: DataModels.IndexingPolicy; let indexingPolicy: DataModels.IndexingPolicy;
let createMongoWildcardIndex: boolean; let createMongoWildcardIndex: boolean;
// todo - remove mongo indexing policy ticket # 616274 // todo - remove mongo indexing policy ticket # 616274
if (this.container.isPreferredApiMongoDB() && this.container.isEnableMongoCapabilityPresent()) { if (userContext.apiType === "Mongo" && this.container.isEnableMongoCapabilityPresent()) {
createMongoWildcardIndex = this.shouldCreateMongoWildcardIndex(); createMongoWildcardIndex = this.shouldCreateMongoWildcardIndex();
} else if (this.showIndexingOptionsForSharedThroughput()) { } else if (this.showIndexingOptionsForSharedThroughput()) {
if (this.useIndexingForSharedThroughput()) { if (this.useIndexingForSharedThroughput()) {
@@ -1145,7 +1144,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
let transform = (value: string) => { let transform = (value: string) => {
return value; return value;
}; };
if (this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
transform = (value: string) => { transform = (value: string) => {
return this._convertShardKeyToPartitionKey(value); return this._convertShardKeyToPartitionKey(value);
}; };

View File

@@ -8,6 +8,7 @@ import {
IconButton, IconButton,
IDropdownOption, IDropdownOption,
Link, Link,
Separator,
Stack, Stack,
Text, Text,
TooltipHost, TooltipHost,
@@ -19,11 +20,11 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType"; import { SubscriptionType } from "../../Contracts/SubscriptionType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { CollectionCreation, IndexingPolicies } from "../../Shared/Constants"; import { CollectionCreation, IndexingPolicies } from "../../Shared/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { getUpsellMessage } from "../../Utils/PricingUtils"; import { getUpsellMessage } from "../../Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
@@ -36,6 +37,7 @@ export interface AddCollectionPanelProps {
explorer: Explorer; explorer: Explorer;
closePanel: () => void; closePanel: () => void;
openNotificationConsole: () => void; openNotificationConsole: () => void;
databaseId?: string;
} }
export interface AddCollectionPanelState { export interface AddCollectionPanelState {
@@ -49,7 +51,7 @@ export interface AddCollectionPanelState {
partitionKey: string; partitionKey: string;
enableDedicatedThroughput: boolean; enableDedicatedThroughput: boolean;
createMongoWildCardIndex: boolean; createMongoWildCardIndex: boolean;
useHashV1: boolean; useHashV2: boolean;
enableAnalyticalStore: boolean; enableAnalyticalStore: boolean;
uniqueKeys: string[]; uniqueKeys: string[];
errorMessage: string; errorMessage: string;
@@ -68,20 +70,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
super(props); super(props);
this.state = { this.state = {
createNewDatabase: userContext.defaultExperience !== DefaultAccountExperienceType.Table, createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
newDatabaseId: "", newDatabaseId: "",
isSharedThroughputChecked: this.getSharedThroughputDefault(), isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId: selectedDatabaseId:
userContext.defaultExperience === DefaultAccountExperienceType.Table userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
? CollectionCreation.TablesAPIDefaultDatabase
: undefined,
collectionId: "", collectionId: "",
enableIndexing: true, enableIndexing: true,
isSharded: userContext.defaultExperience !== DefaultAccountExperienceType.Table, isSharded: userContext.apiType !== "Tables",
partitionKey: "", partitionKey: "",
enableDedicatedThroughput: false, enableDedicatedThroughput: false,
createMongoWildCardIndex: true, createMongoWildCardIndex: true,
useHashV1: false, useHashV2: false,
enableAnalyticalStore: false, enableAnalyticalStore: false,
uniqueKeys: [], uniqueKeys: [],
errorMessage: "", errorMessage: "",
@@ -104,13 +104,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.state.errorMessage && this.isFreeTierAccount() && ( {!this.state.errorMessage && this.isFreeTierAccount() && (
<PanelInfoErrorComponent <PanelInfoErrorComponent
message={getUpsellMessage( message={getUpsellMessage(userContext.portalEnv, true, this.props.explorer.isFirstResourceCreated(), true)}
userContext.portalEnv,
true,
this.props.explorer.isFirstResourceCreated(),
userContext.defaultExperience,
true
)}
messageType="info" messageType="info"
showErrorDetails={false} showErrorDetails={false}
openNotificationConsole={this.props.openNotificationConsole} openNotificationConsole={this.props.openNotificationConsole}
@@ -120,17 +114,19 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
<div className="panelMainContent"> <div className="panelMainContent">
<Stack hidden={userContext.defaultExperience === DefaultAccountExperienceType.Table}> <Stack hidden={userContext.apiType === "Tables"}>
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Database id Database {userContext.apiType === "Mongo" ? "name" : "id"}
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="A database is analogous to a namespace. It is the unit of management for a set of containers." content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
true
).toLocaleLowerCase()}.`}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -144,7 +140,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
type="radio" type="radio"
role="radio" role="radio"
id="databaseCreateNew" id="databaseCreateNew"
data-test="addCollection-createNewDatabase"
tabIndex={0} tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)} onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/> />
@@ -158,8 +153,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
name="databaseType" name="databaseType"
type="radio" type="radio"
role="radio" role="radio"
id="databaseUseExisting"
data-test="addCollection-existingDatabase"
tabIndex={0} tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)} onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/> />
@@ -170,8 +163,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<input <input
name="newDatabaseId" name="newDatabaseId"
id="databaseId" id="newDatabaseId"
data-test="addCollection-newDatabaseId"
aria-required aria-required
required required
type="text" type="text"
@@ -181,7 +173,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
placeholder="Type a new database id" placeholder="Type a new database id"
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label="Database id" aria-label="New database id"
autoFocus autoFocus
value={this.state.newDatabaseId} value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -192,7 +184,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.isServerlessAccount() && ( {!this.isServerlessAccount() && (
<Stack horizontal> <Stack horizontal>
<Checkbox <Checkbox
label="Provision database throughput" label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
checked={this.state.isSharedThroughputChecked} checked={this.state.isSharedThroughputChecked}
styles={{ styles={{
text: { fontSize: 12 }, text: { fontSize: 12 },
@@ -205,9 +197,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/> />
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="Provisioned throughput at the database level will be shared across all containers within the database." content={`Throughput configured at the database level will be shared across all ${getCollectionName(
true
).toLocaleLowerCase()} within the database.`}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -218,6 +212,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated() this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
} }
isDatabase={true} isDatabase={true}
isSharded={this.state.isSharded}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)} setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)} setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)} onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
@@ -234,38 +229,40 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) => onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
this.setState({ selectedDatabaseId: database.key as string }) this.setState({ selectedDatabaseId: database.key as string })
} }
defaultSelectedKey={this.props.databaseId}
responsiveMode={999}
/> />
)} )}
<Separator className="panelSeparator" />
</Stack> </Stack>
<Stack> <Stack>
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{`${this.getCollectionName()} id`} {`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="Unique identifier for the container and used for id-based routing through REST and all SDKs." content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<input <input
name="collectionId" name="collectionId"
id="containerId" id="collectionId"
data-test="addCollection-collectionId"
type="text" type="text"
aria-required aria-required
required required
autoComplete="off" autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'" title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder={`e.g., ${this.getCollectionName()}1`} placeholder={`e.g., ${getCollectionName()}1`}
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label={`${this.getCollectionName()} id`} aria-label={`${getCollectionName()} id`}
value={this.state.collectionId} value={this.state.collectionId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ collectionId: event.target.value }) this.setState({ collectionId: event.target.value })
@@ -317,20 +314,22 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
)} )}
{userContext.defaultExperience === DefaultAccountExperienceType.MongoDB && {userContext.apiType === "Mongo" &&
(!this.state.isSharedThroughputChecked || (!this.state.isSharedThroughputChecked ||
this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && ( this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && (
<Stack> <Stack>
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Sharding options Sharding
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="Unique identifier for the container and used for id-based routing through REST and all SDKs." content={
"Sharded collections split your data across many replica sets (shards) to achieve unlimited scalability. Sharded collections require choosing a shard key (field) to evenly distribute your data."
}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -375,34 +374,35 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content={`The ${this.getPartitionKeyName()} is used to automatically partition data among content={this.getPartitionKeyTooltipText()}
multiple servers for scalability. Choose a JSON property name that has a wide range of values and is
likely to have evenly distributed access patterns.`}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<input <input
type="text" type="text"
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
data-test="addCollection-partitionKeyValue"
aria-required aria-required
required required
size={40} size={40}
className="panelTextField" className="panelTextField"
placeholder={this.getPartitionKeyPlaceHolder()} placeholder={this.getPartitionKeyPlaceHolder()}
aria-label={this.getPartitionKeyName()} aria-label={this.getPartitionKeyName()}
pattern={userContext.defaultExperience === DefaultAccountExperienceType.Graph ? "^/[^/]*" : ".*"} pattern={userContext.apiType === "Gremlin" ? "^/[^/]*" : ".*"}
title={ title={userContext.apiType === "Gremlin" ? "May not use composite partition key" : ""}
userContext.defaultExperience === DefaultAccountExperienceType.Graph
? "May not use composite partition key"
: ""
}
value={this.state.partitionKey} value={this.state.partitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ partitionKey: event.target.value }) if (
} userContext.apiType !== "Mongo" &&
this.state.partitionKey === "" &&
!event.target.value.startsWith("/")
) {
this.setState({ partitionKey: "/" + event.target.value });
} else {
this.setState({ partitionKey: event.target.value });
}
}}
/> />
</Stack> </Stack>
)} )}
@@ -410,7 +410,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!this.isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && ( {!this.isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<Checkbox <Checkbox
label={`Provision dedicated throughput for this ${this.getCollectionName()}`} label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
checked={this.state.enableDedicatedThroughput} checked={this.state.enableDedicatedThroughput}
styles={{ styles={{
text: { fontSize: 12 }, text: { fontSize: 12 },
@@ -423,12 +423,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/> />
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="You can optionally provision dedicated throughput for a container within a database that has throughput content={`You can optionally provision dedicated throughput for a ${getCollectionName().toLocaleLowerCase()} within a database that has throughput
provisioned. This dedicated throughput amount will not be shared with other containers in the database and provisioned. This dedicated throughput amount will not be shared with other ${getCollectionName(
true
).toLocaleLowerCase()} in the database and
does not count towards the throughput you provisioned for the database. This throughput amount will be does not count towards the throughput you provisioned for the database. This throughput amount will be
billed in addition to the throughput amount you provisioned at the database level." billed in addition to the throughput amount you provisioned at the database level.`}
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
)} )}
@@ -439,6 +441,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated() this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
} }
isDatabase={false} isDatabase={false}
isSharded={this.state.isSharded}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)} setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)} setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
onCostAcknowledgeChange={(isAcknowledged: boolean) => { onCostAcknowledgeChange={(isAcknowledged: boolean) => {
@@ -447,7 +450,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/> />
)} )}
{userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB && ( {userContext.apiType === "SQL" && (
<Stack> <Stack>
<Stack horizontal> <Stack horizontal>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
@@ -459,7 +462,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values creating a unique key policy when a container is created, you ensure the uniqueness of one or more values
per partition key." per partition key."
> >
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost> </TooltipHost>
</Stack> </Stack>
@@ -471,7 +474,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
type="text" type="text"
autoComplete="off" autoComplete="off"
placeholder={ placeholder={
userContext.defaultExperience === DefaultAccountExperienceType.MongoDB userContext.apiType === "Mongo"
? "Comma separated paths e.g. firstName,address.zipCode" ? "Comma separated paths e.g. firstName,address.zipCode"
: "Comma separated paths e.g. /firstName,/address/zipCode" : "Comma separated paths e.g. /firstName,/address/zipCode"
} }
@@ -512,134 +515,129 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
)} )}
<CollapsibleSectionComponent title="Advanced" isExpandedByDefault={false}> {userContext.apiType !== "Tables" && (
<Stack className="panelGroupSpacing"> <CollapsibleSectionComponent
{this.props.explorer.isEnableMongoCapabilityPresent() && ( title="Advanced"
<Stack> isExpandedByDefault={false}
<Stack horizontal> onExpand={() => {
<span className="mandatoryStar">*&nbsp;</span> TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection);
<Text className="panelTextBold" variant="small"> this.scrollToAdvancedSection();
Indexing }}
</Text> >
<TooltipHost <Stack className="panelGroupSpacing" id="collapsibleSectionContent">
directionalHint={DirectionalHint.bottomLeftEdge} {this.props.explorer.isEnableMongoCapabilityPresent() && (
content="By default, only the field _id is indexed. Creating a wildcard index on all fields will quickly optimize <Stack className="panelGroupSpacing">
query performance and is recommended during development." <Stack horizontal>
> <span className="mandatoryStar">*&nbsp;</span>
<Icon iconName="InfoSolid" className="panelInfoIcon" /> <Text className="panelTextBold" variant="small">
</TooltipHost> Indexing
</Stack> </Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
>
<Icon iconName="Info" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Checkbox
label="Create a Wildcard Index on all fields"
checked={this.state.createMongoWildCardIndex}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked })
}
/>
</Stack>
)}
{userContext.apiType === "SQL" && (
<Checkbox <Checkbox
label="Create a Wildcard Index on all fields" label="My partition key is larger than 100 bytes"
checked={this.state.createMongoWildCardIndex} checked={this.state.useHashV2}
styles={{ styles={{
text: { fontSize: 12 }, text: { fontSize: 12 },
checkbox: { width: 12, height: 12 }, checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" }, label: { padding: 0, alignItems: "center" },
}} }}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked }) this.setState({ useHashV2: isChecked })
} }
/> />
</Stack> )}
)}
{userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB && ( {this.shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Stack horizontal verticalAlign="start"> <Stack horizontal>
<Checkbox <Text className="panelTextBold" variant="small">
checked={this.state.useHashV1} Analytical store
styles={{
checkbox: { width: 12, height: 12 },
label: { padding: 0, margin: "4px 4px 0 0" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ useHashV1: isChecked })
}
/>
<Text variant="small" style={{ lineHeight: "20px" }}>
My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)
</Text>
</Stack>
<Text variant="small">
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme
that supports partition key values of size up to 100 bytes.{" "}
<Link target="_blank" href="https://aka.ms/cosmosdb/pkv2">
Learn more
</Link>
</Text>
</Stack>
)}
{this.shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<Text className="panelTextBold" variant="small">
Analytical store
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads. Learn more"
>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</Stack>
{!this.isSynapseLinkEnabled() && (
<Stack className="panelGroupSpacing">
<Text variant="small">
Azure Synapse Link is required for creating an analytical store container. Enable Synapse Link
for this Cosmos DB account.{" "}
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
Learn more
</Link>
</Text> </Text>
<DefaultButton <TooltipHost
text="Enable" directionalHint={DirectionalHint.bottomLeftEdge}
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()} content={this.getAnalyticalStorageTooltipContent()}
style={{ height: 27, width: 80 }} >
styles={{ label: { fontSize: 12 } }} <Icon iconName="Info" className="panelInfoIcon" />
/> </TooltipHost>
</Stack> </Stack>
)}
</Stack> <Stack horizontal verticalAlign="center">
)} <input
</Stack> className="panelRadioBtn"
</CollapsibleSectionComponent> checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</Stack>
{!this.isSynapseLinkEnabled() && (
<Stack className="panelGroupSpacing">
<Text variant="small">
Azure Synapse Link is required for creating an analytical store{" "}
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "}
<Link href="https://aka.ms/cosmosdb-synapselink" target="_blank">
Learn more
</Link>
</Text>
<DefaultButton
text="Enable"
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
style={{ height: 27, width: 80 }}
styles={{ label: { fontSize: 12 } }}
/>
</Stack>
)}
</Stack>
)}
</Stack>
</CollapsibleSectionComponent>
)}
</div> </div>
<PanelFooterComponent buttonLabel="OK" /> <PanelFooterComponent buttonLabel="OK" />
@@ -656,31 +654,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
})); }));
} }
private getCollectionName(): string { private getPartitionKeyName(isLowerCase?: boolean): string {
switch (userContext.defaultExperience) { const partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
case DefaultAccountExperienceType.DocumentDB:
return "Container";
case DefaultAccountExperienceType.MongoDB:
return "Collection";
case DefaultAccountExperienceType.Cassandra:
case DefaultAccountExperienceType.Table:
return "Table";
case DefaultAccountExperienceType.Graph:
return "Graph";
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
}
private getPartitionKeyName(): string { return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
return userContext.defaultExperience === DefaultAccountExperienceType.MongoDB ? "Shard key" : "Partition key";
} }
private getPartitionKeyPlaceHolder(): string { private getPartitionKeyPlaceHolder(): string {
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return "e.g., address.zipCode"; return "e.g., address.zipCode";
case DefaultAccountExperienceType.Graph: case "Gremlin":
return "e.g., /address"; return "e.g., /address";
default: default:
return "e.g., /address/zipCode"; return "e.g., /address/zipCode";
@@ -782,6 +766,34 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations."; : "Indexing will be turned off. Recommended if you don't need to run queries or only have key value operations.";
} }
private getPartitionKeyTooltipText(): string {
if (userContext.apiType === "Mongo") {
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. Its critical to choose a field that will evenly distribute your data.";
}
let tooltipText = `The ${this.getPartitionKeyName(
true
)} is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.`;
if (userContext.apiType === "SQL") {
tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.";
}
return tooltipText;
}
private getAnalyticalStorageTooltipContent(): JSX.Element {
return (
<Text variant="small">
Enable analytical store capability to perform near real-time analytics on your operational data, without
impacting the performance of transactional workloads.{" "}
<Link target="_blank" href="https://aka.ms/analytical-store-overview">
Learn more
</Link>
</Text>
);
}
private shouldShowCollectionThroughputInput(): boolean { private shouldShowCollectionThroughputInput(): boolean {
if (this.isServerlessAccount()) { if (this.isServerlessAccount()) {
return false; return false;
@@ -817,11 +829,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false; return false;
} }
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return true; return true;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return this.props.explorer.hasStorageAnalyticsAfecFeature(); return this.props.explorer.hasStorageAnalyticsAfecFeature();
default: default:
return false; return false;
@@ -855,7 +867,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
const validPaths: string[] = uniqueKey.split(",")?.filter((path) => path?.length > 0); const validPaths: string[] = uniqueKey.split(",")?.filter((path) => path?.length > 0);
const trimmedPaths: string[] = validPaths?.map((path) => path.trim()); const trimmedPaths: string[] = validPaths?.map((path) => path.trim());
if (trimmedPaths?.length > 0) { if (trimmedPaths?.length > 0) {
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) { if (userContext.apiType === "Mongo") {
trimmedPaths.map((path) => { trimmedPaths.map((path) => {
const transformedPath = path.split(".").join("/"); const transformedPath = path.split(".").join("/");
if (transformedPath[0] !== "/") { if (transformedPath[0] !== "/") {
@@ -887,8 +899,13 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false; return false;
} }
if (throughput > CollectionCreation.MaxRUPerPartition && !this.state.isSharded) {
this.setState({ errorMessage: "Unsharded collections support up to 10,000 RUs" });
return false;
}
if ( if (
userContext.defaultExperience === DefaultAccountExperienceType.Graph && userContext.apiType === "Gremlin" &&
(this.state.partitionKey === "/id" || this.state.partitionKey === "/label") (this.state.partitionKey === "/id" || this.state.partitionKey === "/label")
) { ) {
this.setState({ errorMessage: "/id and /label as partition keys are not allowed for graph." }); this.setState({ errorMessage: "/id and /label as partition keys are not allowed for graph." });
@@ -913,6 +930,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return Constants.AnalyticalStorageTtl.Disabled; return Constants.AnalyticalStorageTtl.Disabled;
} }
private scrollToAdvancedSection(): void {
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
}
private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> { private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault(); event.preventDefault();
@@ -924,14 +945,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId; let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
let partitionKeyString = this.state.partitionKey.trim(); let partitionKeyString = this.state.partitionKey.trim();
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.apiType === "Tables") {
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk' // Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
databaseId = CollectionCreation.TablesAPIDefaultDatabase; databaseId = CollectionCreation.TablesAPIDefaultDatabase;
partitionKeyString = "/'$pk'"; partitionKeyString = "/'$pk'";
} }
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys(); const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys();
const partitionKeyVersion = this.state.useHashV1 ? undefined : 2; const partitionKeyVersion = this.state.useHashV2 ? 2 : undefined;
const partitionKey: DataModels.PartitionKey = partitionKeyString const partitionKey: DataModels.PartitionKey = partitionKeyString
? { ? {
paths: [partitionKeyString], paths: [partitionKeyString],

View File

@@ -238,7 +238,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
userContext.portalEnv, userContext.portalEnv,
this.isFreeTierAccount(), this.isFreeTierAccount(),
this.container.isFirstResourceCreated(), this.container.isFirstResourceCreated(),
this.container.defaultExperience(),
false false
); );
}); });

View File

@@ -2,7 +2,6 @@ import * as ko from "knockout";
import * as _ from "underscore"; import * as _ from "underscore";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -51,7 +50,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
private keyspaceOffers: HashMap<DataModels.Offer>; private keyspaceOffers: Map<string, DataModels.Offer>;
constructor(options: ViewModels.PaneOptions) { constructor(options: ViewModels.PaneOptions) {
super(options); super(options);
@@ -60,7 +59,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.keyspaceCreateNew = ko.observable<boolean>(true); this.keyspaceCreateNew = ko.observable<boolean>(true);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.keyspaceOffers = new HashMap<DataModels.Offer>(); this.keyspaceOffers = new Map();
this.keyspaceIds = ko.observableArray<string>(); this.keyspaceIds = ko.observableArray<string>();
this.keyspaceHasSharedOffer = ko.observable<boolean>(false); this.keyspaceHasSharedOffer = ko.observable<boolean>(false);
this.keyspaceThroughput = ko.observable<number>(); this.keyspaceThroughput = ko.observable<number>();

View File

@@ -131,7 +131,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
}; };
const genericPaneProps: GenericRightPaneProps = { const genericPaneProps: GenericRightPaneProps = {
container,
formError, formError,
formErrorDetail, formErrorDetail,
id: "copynotebookpane", id: "copynotebookpane",
@@ -140,6 +139,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
submitButtonText: "OK", submitButtonText: "OK",
onClose: closePanel, onClose: closePanel,
onSubmit: () => submit(), onSubmit: () => submit(),
expandConsole: () => container.expandConsole(),
}; };
const copyNotebookPaneProps: CopyNotebookPaneProps = { const copyNotebookPaneProps: CopyNotebookPaneProps = {

View File

@@ -7,7 +7,6 @@ import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
import DeleteFeedback from "../../../Common/DeleteFeedback"; import DeleteFeedback from "../../../Common/DeleteFeedback";
import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels"; import { ApiKind, DatabaseAccount } from "../../../Contracts/DataModels";
import { Collection, Database, TreeNode } from "../../../Contracts/ViewModels"; import { Collection, Database, TreeNode } from "../../../Contracts/ViewModels";
import { DefaultAccountExperienceType } from "../../../DefaultAccountExperienceType";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
@@ -107,7 +106,7 @@ describe("Delete Collection Confirmation Pane", () => {
}, },
id: "testDatabaseAccountId", id: "testDatabaseAccountId",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
(deleteCollection as jest.Mock).mockResolvedValue(undefined); (deleteCollection as jest.Mock).mockResolvedValue(undefined);
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined); (TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);

View File

@@ -9,6 +9,7 @@ import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtili
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { import {
@@ -17,14 +18,12 @@ import {
} from "../GenericRightPaneComponent/GenericRightPaneComponent"; } from "../GenericRightPaneComponent/GenericRightPaneComponent";
export interface DeleteCollectionConfirmationPaneProps { export interface DeleteCollectionConfirmationPaneProps {
explorer: Explorer; explorer: Explorer;
collectionName: string;
closePanel: () => void; closePanel: () => void;
} }
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
explorer, explorer,
closePanel, closePanel,
collectionName,
}: DeleteCollectionConfirmationPaneProps) => { }: DeleteCollectionConfirmationPaneProps) => {
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>(""); const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
const [inputCollectionName, setInputCollectionName] = useState<string>(""); const [inputCollectionName, setInputCollectionName] = useState<string>("");
@@ -34,6 +33,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const shouldRecordFeedback = (): boolean => { const shouldRecordFeedback = (): boolean => {
return explorer.isLastCollection() && !explorer.isSelectedDatabaseShared(); return explorer.isLastCollection() && !explorer.isSelectedDatabaseShared();
}; };
const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = "Delete " + collectionName; const paneTitle = "Delete " + collectionName;
const submit = async (): Promise<void> => { const submit = async (): Promise<void> => {
const collection = explorer.findSelectedCollection(); const collection = explorer.findSelectedCollection();
@@ -73,7 +73,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const deleteFeedback = new DeleteFeedback( const deleteFeedback = new DeleteFeedback(
userContext.databaseAccount?.id, userContext.databaseAccount?.id,
userContext.databaseAccount?.name, userContext.databaseAccount?.name,
DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.defaultExperience), DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType),
deleteCollectionFeedback deleteCollectionFeedback
); );
@@ -101,7 +101,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
} }
}; };
const genericPaneProps: GenericRightPaneProps = { const genericPaneProps: GenericRightPaneProps = {
container: explorer,
formError: formError, formError: formError,
formErrorDetail: formError, formErrorDetail: formError,
id: "deleteCollectionpane", id: "deleteCollectionpane",
@@ -110,6 +109,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: "OK", submitButtonText: "OK",
onClose: closePanel, onClose: closePanel,
onSubmit: submit, onSubmit: submit,
expandConsole: () => explorer.expandConsole(),
}; };
return ( return (
<GenericRightPaneComponent {...genericPaneProps}> <GenericRightPaneComponent {...genericPaneProps}>

View File

@@ -16,16 +16,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
} }
> >
<GenericRightPaneComponent <GenericRightPaneComponent
container={ expandConsole={[Function]}
Object {
"findSelectedCollection": [Function],
"isLastCollection": [Function],
"isSelectedDatabaseShared": [Function],
"refreshAllDatabases": [Function],
"selectedCollectionId": [Function],
"selectedNode": [Function],
}
}
formError="" formError=""
formErrorDetail="" formErrorDetail=""
id="deleteCollectionpane" id="deleteCollectionpane"

View File

@@ -7,7 +7,6 @@ import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
import DeleteFeedback from "../../Common/DeleteFeedback"; import DeleteFeedback from "../../Common/DeleteFeedback";
import { ApiKind, DatabaseAccount } from "../../Contracts/DataModels"; import { ApiKind, DatabaseAccount } from "../../Contracts/DataModels";
import { Collection, Database } from "../../Contracts/ViewModels"; import { Collection, Database } from "../../Contracts/ViewModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
@@ -68,7 +67,7 @@ describe("Delete Database Confirmation Pane", () => {
}, },
id: "testDatabaseAccountId", id: "testDatabaseAccountId",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
(deleteDatabase as jest.Mock).mockResolvedValue(undefined); (deleteDatabase as jest.Mock).mockResolvedValue(undefined);
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined); (TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);

View File

@@ -94,7 +94,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
const deleteFeedback = new DeleteFeedback( const deleteFeedback = new DeleteFeedback(
userContext?.databaseAccount.id, userContext?.databaseAccount.id,
userContext?.databaseAccount.name, userContext?.databaseAccount.name,
DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.defaultExperience), DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType),
databaseFeedbackInput databaseFeedbackInput
); );

View File

@@ -1,15 +1,13 @@
import { mount } from "enzyme"; import { mount } from "enzyme";
import React from "react"; import React from "react";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure"; import StoredProcedure from "../../Tree/StoredProcedure";
import { ExecuteSprocParamsPane } from "./ExecuteSprocParamsPane"; import { ExecuteSprocParamsPane } from "./ExecuteSprocParamsPane";
describe("Excute Sproc Param Pane", () => { describe("Excute Sproc Param Pane", () => {
const fakeExplorer = {} as Explorer;
const fakeSproc = {} as StoredProcedure; const fakeSproc = {} as StoredProcedure;
const props = { const props = {
explorer: fakeExplorer,
storedProcedure: fakeSproc, storedProcedure: fakeSproc,
expandConsole: (): void => undefined,
closePanel: (): void => undefined, closePanel: (): void => undefined,
}; };

View File

@@ -2,7 +2,6 @@ import { useBoolean } from "@uifabric/react-hooks";
import { IDropdownOption, IImageProps, Image, Stack, Text } from "office-ui-fabric-react"; import { IDropdownOption, IImageProps, Image, Stack, Text } from "office-ui-fabric-react";
import React, { FunctionComponent, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import AddPropertyIcon from "../../../../images/Add-property.svg"; import AddPropertyIcon from "../../../../images/Add-property.svg";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure"; import StoredProcedure from "../../Tree/StoredProcedure";
import { import {
GenericRightPaneComponent, GenericRightPaneComponent,
@@ -11,7 +10,7 @@ import {
import { InputParameter } from "./InputParameter"; import { InputParameter } from "./InputParameter";
interface ExecuteSprocParamsPaneProps { interface ExecuteSprocParamsPaneProps {
explorer: Explorer; expandConsole: () => void;
storedProcedure: StoredProcedure; storedProcedure: StoredProcedure;
closePanel: () => void; closePanel: () => void;
} }
@@ -27,7 +26,7 @@ interface UnwrappedExecuteSprocParam {
} }
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
explorer, expandConsole,
storedProcedure, storedProcedure,
closePanel, closePanel,
}: ExecuteSprocParamsPaneProps): JSX.Element => { }: ExecuteSprocParamsPaneProps): JSX.Element => {
@@ -43,7 +42,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
}; };
const genericPaneProps: GenericRightPaneProps = { const genericPaneProps: GenericRightPaneProps = {
container: explorer, expandConsole,
formError: formError, formError: formError,
formErrorDetail: formErrorsDetails, formErrorDetail: formErrorsDetails,
id: "executesprocparamspane", id: "executesprocparamspane",

View File

@@ -3,11 +3,11 @@
exports[`Excute Sproc Param Pane should render Default properly 1`] = ` exports[`Excute Sproc Param Pane should render Default properly 1`] = `
<ExecuteSprocParamsPane <ExecuteSprocParamsPane
closePanel={[Function]} closePanel={[Function]}
explorer={Object {}} expandConsole={[Function]}
storedProcedure={Object {}} storedProcedure={Object {}}
> >
<GenericRightPaneComponent <GenericRightPaneComponent
container={Object {}} expandConsole={[Function]}
formError="" formError=""
formErrorDetail="" formErrorDetail=""
id="executesprocparamspane" id="executesprocparamspane"

View File

@@ -3,10 +3,9 @@ import React, { FunctionComponent, ReactNode } from "react";
import ErrorRedIcon from "../../../../images/error_red.svg"; import ErrorRedIcon from "../../../../images/error_red.svg";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { KeyCodes } from "../../../Common/Constants"; import { KeyCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer";
export interface GenericRightPaneProps { export interface GenericRightPaneProps {
container: Explorer; expandConsole: () => void;
formError: string; formError: string;
formErrorDetail: string; formErrorDetail: string;
id: string; id: string;
@@ -24,7 +23,7 @@ export interface GenericRightPaneState {
} }
export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({ export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps> = ({
container, expandConsole,
formError, formError,
formErrorDetail, formErrorDetail,
id, id,
@@ -72,7 +71,7 @@ export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps>
<span className="formErrors" title={formError}> <span className="formErrors" title={formError}>
{formError} {formError}
</span> </span>
<a className="errorLink" role="link" hidden={!formErrorDetail} onClick={showErrorDetail}> <a className="errorLink" role="link" hidden={!formErrorDetail} onClick={expandConsole}>
More details More details
</a> </a>
</span> </span>
@@ -114,10 +113,6 @@ export const GenericRightPaneComponent: FunctionComponent<GenericRightPaneProps>
} }
}; };
const showErrorDetail = (): void => {
container.expandConsole();
};
return ( return (
<div tabIndex={-1} onKeyDown={onKeyDown}> <div tabIndex={-1} onKeyDown={onKeyDown}>
<div className="contextual-pane-out" onClick={onClose}></div> <div className="contextual-pane-out" onClick={onClose}></div>

View File

@@ -37,7 +37,7 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
const title = "Load Query"; const title = "Load Query";
const genericPaneProps: GenericRightPaneProps = { const genericPaneProps: GenericRightPaneProps = {
container: explorer, expandConsole: () => explorer.expandConsole(),
formError: formError, formError: formError,
formErrorDetail: formErrorsDetails, formErrorDetail: formErrorsDetails,
id: "loadQueryPane", id: "loadQueryPane",

View File

@@ -2,7 +2,7 @@
exports[`Load Query Pane should render Default properly 1`] = ` exports[`Load Query Pane should render Default properly 1`] = `
<GenericRightPaneComponent <GenericRightPaneComponent
container={Object {}} expandConsole={[Function]}
formError="" formError=""
formErrorDetail="" formErrorDetail=""
id="loadQueryPane" id="loadQueryPane"

Some files were not shown because too many files have changed in this diff Show More