mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 09:51:11 +00:00
marge master conflict
This commit is contained in:
@@ -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
|
||||||
@@ -124,7 +120,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
|
||||||
|
|||||||
30401
package-lock.json
generated
30401
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -178,7 +178,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"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -28,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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,5 +26,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());
|
||||||
|
|||||||
@@ -63,7 +63,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),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -65,16 +64,15 @@ 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";
|
||||||
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
||||||
import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel";
|
import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import type { GalleryTabOptions } from "./Tabs/GalleryTab";
|
|
||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
||||||
import { TabsManager } from "./Tabs/TabsManager";
|
import { TabsManager } from "./Tabs/TabsManager";
|
||||||
@@ -121,11 +119,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
|
||||||
@@ -174,11 +167,8 @@ export default class Explorer {
|
|||||||
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>;
|
||||||
@@ -415,27 +405,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) {
|
||||||
@@ -501,13 +470,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),
|
||||||
@@ -515,13 +477,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) {
|
||||||
@@ -534,9 +489,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);
|
||||||
@@ -605,7 +558,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":
|
||||||
@@ -619,7 +571,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;
|
||||||
}
|
}
|
||||||
@@ -1432,7 +1383,6 @@ export default class Explorer {
|
|||||||
): Promise<void> {
|
): 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1545,7 +1495,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");
|
||||||
@@ -1561,57 +1511,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> {
|
||||||
@@ -1935,33 +1887,35 @@ export default class Explorer {
|
|||||||
const title = "Gallery";
|
const title = "Gallery";
|
||||||
const hashLocation = "gallery";
|
const hashLocation = "gallery";
|
||||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||||
|
|
||||||
const galleryTabOptions: GalleryTabOptions = {
|
|
||||||
account: userContext.databaseAccount,
|
|
||||||
container: this,
|
|
||||||
junoClient: this.notebookManager?.junoClient,
|
|
||||||
selectedTab: selectedTab || GalleryTabKind.PublicGallery,
|
|
||||||
notebookUrl,
|
|
||||||
galleryItem,
|
|
||||||
isFavorite,
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Gallery,
|
|
||||||
title: title,
|
|
||||||
tabPath: title,
|
|
||||||
hashLocation: hashLocation,
|
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
|
||||||
onLoadStartKey: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const galleryTab = this.tabsManager
|
const galleryTab = this.tabsManager
|
||||||
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
||||||
.find((tab) => tab.hashLocation() == hashLocation);
|
.find((tab) => tab.hashLocation() == hashLocation);
|
||||||
|
|
||||||
if (galleryTab instanceof GalleryTab) {
|
if (galleryTab instanceof GalleryTab) {
|
||||||
this.tabsManager.activateTab(galleryTab);
|
this.tabsManager.activateTab(galleryTab);
|
||||||
galleryTab.reset(galleryTabOptions);
|
|
||||||
} else {
|
} else {
|
||||||
this.tabsManager.activateNewTab(new GalleryTab(galleryTabOptions));
|
this.tabsManager.activateNewTab(
|
||||||
|
new GalleryTab(
|
||||||
|
{
|
||||||
|
tabKind: ViewModels.CollectionTabKind.Gallery,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2163,8 +2117,7 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
public openAddDatabasePane(): void {
|
public openAddDatabasePane(): void {
|
||||||
let isEnable = true;
|
if (userContext.features.enableKOPanel) {
|
||||||
if (isEnable) {
|
|
||||||
this.addDatabasePane.open();
|
this.addDatabasePane.open();
|
||||||
document.getElementById("linkAddDatabase").focus();
|
document.getElementById("linkAddDatabase").focus();
|
||||||
} else {
|
} else {
|
||||||
@@ -2228,6 +2181,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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
5
src/Explorer/LazyMonaco.ts
Normal file
5
src/Explorer/LazyMonaco.ts
Normal 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");
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Contains all notebook related stuff meant to be dynamically loaded by explorer
|
* Contains all notebook related stuff meant to be dynamically loaded by explorer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ImmutableNotebook } from "@nteract/commutable";
|
import { ImmutableNotebook } from "@nteract/commutable";
|
||||||
import type { 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";
|
||||||
@@ -22,7 +22,7 @@ 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";
|
||||||
@@ -53,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;
|
||||||
@@ -91,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);
|
||||||
|
|
||||||
@@ -123,7 +120,20 @@ export default class NotebookManager {
|
|||||||
content: NotebookPaneContent,
|
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 {
|
||||||
|
|||||||
@@ -251,10 +251,7 @@
|
|||||||
</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 +294,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 +309,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 -->
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +585,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 +724,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 +810,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 +1145,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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@ import AddDatabasePaneTemplate from "./AddDatabasePane.html";
|
|||||||
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
||||||
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
|
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
|
||||||
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
|
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
|
||||||
import StringInputPaneTemplate from "./StringInputPane.html";
|
|
||||||
import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
|
import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
|
||||||
import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html";
|
import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html";
|
||||||
|
|
||||||
@@ -65,15 +64,6 @@ export class CassandraAddCollectionPaneComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StringInputPaneComponent {
|
|
||||||
constructor() {
|
|
||||||
return {
|
|
||||||
viewModel: PaneComponent,
|
|
||||||
template: StringInputPaneTemplate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GitHubReposPaneComponent {
|
export class GitHubReposPaneComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ describe("PublishNotebookPaneComponent", () => {
|
|||||||
notebookName: "SampleNotebook.ipynb",
|
notebookName: "SampleNotebook.ipynb",
|
||||||
notebookDescription: "sample description",
|
notebookDescription: "sample description",
|
||||||
notebookTags: "tag1, tag2",
|
notebookTags: "tag1, tag2",
|
||||||
|
imageSrc: "https://i.ytimg.com/vi/E_lByLdKeKY/maxresdefault.jpg",
|
||||||
notebookAuthor: "CosmosDB",
|
notebookAuthor: "CosmosDB",
|
||||||
notebookCreatedDate: "2020-07-17T00:00:00Z",
|
notebookCreatedDate: "2020-07-17T00:00:00Z",
|
||||||
notebookObject: undefined,
|
notebookObject: undefined,
|
||||||
notebookParentDomElement: undefined,
|
notebookParentDomElement: undefined,
|
||||||
onChangeName: undefined,
|
setNotebookName: undefined,
|
||||||
onChangeDescription: undefined,
|
setNotebookDescription: undefined,
|
||||||
onChangeTags: undefined,
|
setNotebookTags: undefined,
|
||||||
onChangeImageSrc: undefined,
|
setImageSrc: undefined,
|
||||||
onError: undefined,
|
onError: undefined,
|
||||||
clearFormError: undefined,
|
clearFormError: undefined,
|
||||||
};
|
};
|
||||||
205
src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx
Normal file
205
src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { ImmutableNotebook, toJS } from "@nteract/commutable";
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
|
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import { CodeOfConductComponent } from "../../Controls/NotebookGallery/CodeOfConductComponent";
|
||||||
|
import { GalleryTab } from "../../Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
||||||
|
|
||||||
|
export interface PublishNotebookPaneAProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
openNotificationConsole: () => void;
|
||||||
|
junoClient: JunoClient;
|
||||||
|
name: string;
|
||||||
|
author: string;
|
||||||
|
notebookContent: string | ImmutableNotebook;
|
||||||
|
parentDomElement: HTMLElement;
|
||||||
|
}
|
||||||
|
export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> = ({
|
||||||
|
explorer: container,
|
||||||
|
junoClient,
|
||||||
|
closePanel,
|
||||||
|
name,
|
||||||
|
author,
|
||||||
|
notebookContent,
|
||||||
|
parentDomElement,
|
||||||
|
}: PublishNotebookPaneAProps): JSX.Element => {
|
||||||
|
const [isCodeOfConductAccepted, setIsCodeOfConductAccepted] = useState<boolean>(false);
|
||||||
|
const [content, setContent] = useState<string>("");
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [formErrorDetail, setFormErrorDetail] = useState<string>("");
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
|
|
||||||
|
const [notebookName, setNotebookName] = useState<string>(name);
|
||||||
|
const [notebookDescription, setNotebookDescription] = useState<string>("");
|
||||||
|
const [notebookTags, setNotebookTags] = useState<string>("");
|
||||||
|
const [imageSrc, setImageSrc] = useState<string>();
|
||||||
|
|
||||||
|
const CodeOfConductAccepted = async () => {
|
||||||
|
try {
|
||||||
|
const response = await junoClient.isCodeOfConductAccepted();
|
||||||
|
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||||
|
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
||||||
|
}
|
||||||
|
setIsCodeOfConductAccepted(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(
|
||||||
|
error,
|
||||||
|
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
||||||
|
"Failed to check if code of conduct was accepted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [notebookObject, setNotebookObject] = useState<ImmutableNotebook>();
|
||||||
|
useEffect(() => {
|
||||||
|
CodeOfConductAccepted();
|
||||||
|
let newContent;
|
||||||
|
if (typeof notebookContent === "string") {
|
||||||
|
newContent = notebookContent as string;
|
||||||
|
} else {
|
||||||
|
newContent = JSON.stringify(toJS(notebookContent));
|
||||||
|
setNotebookObject(notebookContent);
|
||||||
|
}
|
||||||
|
setContent(newContent);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const submit = async (): Promise<void> => {
|
||||||
|
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${name} to gallery`);
|
||||||
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
let startKey: number;
|
||||||
|
|
||||||
|
if (!notebookName || !notebookDescription || !author || !imageSrc) {
|
||||||
|
setFormError(`Failed to publish ${notebookName} to gallery`);
|
||||||
|
setFormErrorDetail("Name, description, author and cover image are required");
|
||||||
|
createFormError(formError, formErrorDetail, "PublishNotebookPaneAdapter/submit");
|
||||||
|
setIsExecuting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startKey = traceStart(Action.NotebooksGalleryPublish, {});
|
||||||
|
|
||||||
|
const response = await junoClient.publishNotebook(
|
||||||
|
notebookName,
|
||||||
|
notebookDescription,
|
||||||
|
notebookTags?.split(","),
|
||||||
|
author,
|
||||||
|
imageSrc,
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = response.data;
|
||||||
|
if (data) {
|
||||||
|
let isPublishPending = false;
|
||||||
|
|
||||||
|
if (data.pendingScanJobIds?.length > 0) {
|
||||||
|
isPublishPending = true;
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(
|
||||||
|
`Content of ${name} is currently being scanned for illegal content. It will not be available in the public gallery until the review is complete (may take a few days).`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(`Published ${notebookName} to gallery`);
|
||||||
|
container.openGallery(GalleryTab.Published);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceSuccess(
|
||||||
|
Action.NotebooksGalleryPublish,
|
||||||
|
{
|
||||||
|
notebookId: data.id,
|
||||||
|
isPublishPending,
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.NotebooksGalleryPublish,
|
||||||
|
{
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormError(`Failed to publish ${FileSystemUtil.stripExtension(notebookName, "ipynb")} to gallery`);
|
||||||
|
setFormErrorDetail(`${errorMessage}`);
|
||||||
|
handleError(errorMessage, "PublishNotebookPaneAdapter/submit", formError);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
clearPublishingMessage();
|
||||||
|
setIsExecuting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFormError = (formError: string, formErrorDetail: string, area: string): void => {
|
||||||
|
setFormError(formError);
|
||||||
|
setFormErrorDetail(formErrorDetail);
|
||||||
|
handleError(formErrorDetail, area, formError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFormError = (): void => {
|
||||||
|
setFormError("");
|
||||||
|
setFormErrorDetail("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const props: GenericRightPaneProps = {
|
||||||
|
container: container,
|
||||||
|
formError: formError,
|
||||||
|
formErrorDetail: formErrorDetail,
|
||||||
|
id: "publishnotebookpane",
|
||||||
|
isExecuting: isExecuting,
|
||||||
|
title: "Publish to gallery",
|
||||||
|
submitButtonText: "Publish",
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
onClose: closePanel,
|
||||||
|
isSubmitButtonHidden: !isCodeOfConductAccepted,
|
||||||
|
};
|
||||||
|
|
||||||
|
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
||||||
|
notebookDescription,
|
||||||
|
notebookTags,
|
||||||
|
imageSrc,
|
||||||
|
notebookName,
|
||||||
|
notebookAuthor: author,
|
||||||
|
notebookCreatedDate: new Date().toISOString(),
|
||||||
|
notebookObject: notebookObject,
|
||||||
|
notebookParentDomElement: parentDomElement,
|
||||||
|
onError: createFormError,
|
||||||
|
clearFormError: clearFormError,
|
||||||
|
setNotebookName,
|
||||||
|
setNotebookDescription,
|
||||||
|
setNotebookTags,
|
||||||
|
setImageSrc,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<GenericRightPaneComponent {...props}>
|
||||||
|
{!isCodeOfConductAccepted ? (
|
||||||
|
<div style={{ padding: "25px", marginTop: "10px" }}>
|
||||||
|
<CodeOfConductComponent
|
||||||
|
junoClient={junoClient}
|
||||||
|
onAcceptCodeOfConduct={(isAccepted) => {
|
||||||
|
setIsCodeOfConductAccepted(isAccepted);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
||||||
|
)}
|
||||||
|
</GenericRightPaneComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
import { ImmutableNotebook } from "@nteract/commutable";
|
||||||
|
import Html2Canvas from "html2canvas";
|
||||||
|
import { Dropdown, IDropdownProps, ITextFieldProps, Stack, Text, TextField } from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent, useState } from "react";
|
||||||
|
import { GalleryCardComponent } from "../../Controls/NotebookGallery/Cards/GalleryCardComponent";
|
||||||
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
|
import "./styled.less";
|
||||||
|
|
||||||
|
export interface PublishNotebookPaneProps {
|
||||||
|
notebookName: string;
|
||||||
|
notebookAuthor: string;
|
||||||
|
notebookTags: string;
|
||||||
|
imageSrc: string;
|
||||||
|
notebookDescription: string;
|
||||||
|
notebookCreatedDate: string;
|
||||||
|
notebookObject: ImmutableNotebook;
|
||||||
|
notebookParentDomElement?: HTMLElement;
|
||||||
|
onError: (formError: string, formErrorDetail: string, area: string) => void;
|
||||||
|
clearFormError: () => void;
|
||||||
|
setNotebookName: (newValue: string) => void;
|
||||||
|
setNotebookDescription: (newValue: string) => void;
|
||||||
|
setNotebookTags: (newValue: string) => void;
|
||||||
|
setImageSrc: (newValue: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ImageTypes {
|
||||||
|
Url = "URL",
|
||||||
|
CustomImage = "Custom Image",
|
||||||
|
TakeScreenshot = "Take Screenshot",
|
||||||
|
UseFirstDisplayOutput = "Use First Display Output",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PublishNotebookPaneComponent: FunctionComponent<PublishNotebookPaneProps> = ({
|
||||||
|
notebookName,
|
||||||
|
notebookTags,
|
||||||
|
imageSrc,
|
||||||
|
notebookDescription,
|
||||||
|
notebookAuthor,
|
||||||
|
notebookCreatedDate,
|
||||||
|
notebookObject,
|
||||||
|
notebookParentDomElement,
|
||||||
|
onError,
|
||||||
|
clearFormError,
|
||||||
|
setNotebookName,
|
||||||
|
setNotebookDescription,
|
||||||
|
setNotebookTags,
|
||||||
|
setImageSrc,
|
||||||
|
}: PublishNotebookPaneProps) => {
|
||||||
|
const [type, setType] = useState<string>(ImageTypes.CustomImage);
|
||||||
|
const CARD_WIDTH = 256;
|
||||||
|
const cardImageHeight = 144;
|
||||||
|
const cardHeightToWidthRatio = cardImageHeight / CARD_WIDTH;
|
||||||
|
|
||||||
|
const maxImageSizeInMib = 1.5;
|
||||||
|
|
||||||
|
const descriptionPara1 =
|
||||||
|
"When published, this notebook will appear in the Azure Cosmos DB notebooks public gallery. Make sure you have removed any sensitive data or output before publishing.";
|
||||||
|
|
||||||
|
const descriptionPara2 = `Would you like to publish and share "${FileSystemUtil.stripExtension(
|
||||||
|
notebookName,
|
||||||
|
"ipynb"
|
||||||
|
)}" to the gallery?`;
|
||||||
|
|
||||||
|
const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url];
|
||||||
|
const thumbnailSelectorProps: IDropdownProps = {
|
||||||
|
label: "Cover image",
|
||||||
|
defaultSelectedKey: ImageTypes.CustomImage,
|
||||||
|
ariaLabel: "Cover image",
|
||||||
|
options: options.map((value: string) => ({ text: value, key: value })),
|
||||||
|
onChange: async (event, options) => {
|
||||||
|
setImageSrc("");
|
||||||
|
clearFormError();
|
||||||
|
if (options.text === ImageTypes.TakeScreenshot) {
|
||||||
|
try {
|
||||||
|
await takeScreenshot(notebookParentDomElement, screenshotErrorHandler);
|
||||||
|
} catch (error) {
|
||||||
|
screenshotErrorHandler(error);
|
||||||
|
}
|
||||||
|
} else if (options.text === ImageTypes.UseFirstDisplayOutput) {
|
||||||
|
try {
|
||||||
|
await takeScreenshot(findFirstOutput(), firstOutputErrorHandler);
|
||||||
|
} catch (error) {
|
||||||
|
firstOutputErrorHandler(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setType(options.text);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const thumbnailUrlProps: ITextFieldProps = {
|
||||||
|
label: "Cover image url",
|
||||||
|
ariaLabel: "Cover image url",
|
||||||
|
required: true,
|
||||||
|
onChange: (event, newValue) => {
|
||||||
|
setImageSrc(newValue);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenshotErrorHandler = (error: Error) => {
|
||||||
|
const formError = "Failed to take screen shot";
|
||||||
|
const formErrorDetail = `${error}`;
|
||||||
|
const area = "PublishNotebookPaneComponent/takeScreenshot";
|
||||||
|
onError(formError, formErrorDetail, area);
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstOutputErrorHandler = (error: Error) => {
|
||||||
|
const formError = "Failed to capture first output";
|
||||||
|
const formErrorDetail = `${error}`;
|
||||||
|
const area = "PublishNotebookPaneComponent/UseFirstOutput";
|
||||||
|
onError(formError, formErrorDetail, area);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notebookParentDomElement) {
|
||||||
|
options.push(ImageTypes.TakeScreenshot);
|
||||||
|
if (notebookObject) {
|
||||||
|
options.push(ImageTypes.UseFirstDisplayOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => {
|
||||||
|
updateImageSrc(reader.result.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = (error) => {
|
||||||
|
const formError = `Failed to convert ${file.name} to base64 format`;
|
||||||
|
const formErrorDetail = `${error}`;
|
||||||
|
const area = "PublishNotebookPaneComponent/selectImageFile";
|
||||||
|
onError(formError, formErrorDetail, area);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const takeScreenshot = (target: HTMLElement, onError: (error: Error) => void): void => {
|
||||||
|
const updateImageSrcWithScreenshot = (canvasUrl: string): void => {
|
||||||
|
setImageSrc(canvasUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
target.scrollIntoView();
|
||||||
|
Html2Canvas(target, {
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: true,
|
||||||
|
scale: 1,
|
||||||
|
logging: true,
|
||||||
|
})
|
||||||
|
.then((canvas) => {
|
||||||
|
//redraw canvas to fit Card Cover Image dimensions
|
||||||
|
const originalImageData = canvas.toDataURL();
|
||||||
|
const requiredHeight = parseInt(canvas.style.width.split("px")[0]) * cardHeightToWidthRatio;
|
||||||
|
canvas.height = requiredHeight;
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
const image = new Image();
|
||||||
|
image.src = originalImageData;
|
||||||
|
image.onload = () => {
|
||||||
|
context.drawImage(image, 0, 0);
|
||||||
|
updateImageSrcWithScreenshot(canvas.toDataURL());
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
onError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderThumbnailSelectors = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case ImageTypes.Url:
|
||||||
|
return <TextField {...thumbnailUrlProps} />;
|
||||||
|
case ImageTypes.CustomImage:
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
id="selectImageFile"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file.size / 1024 ** 2 > maxImageSizeInMib) {
|
||||||
|
event.target.value = "";
|
||||||
|
const formError = `Failed to upload ${file.name}`;
|
||||||
|
const formErrorDetail = `Image is larger than ${maxImageSizeInMib} MiB. Please Choose a different image.`;
|
||||||
|
const area = "PublishNotebookPaneComponent/selectImageFile";
|
||||||
|
|
||||||
|
onError(formError, formErrorDetail, area);
|
||||||
|
setImageSrc("");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
clearFormError();
|
||||||
|
}
|
||||||
|
imageToBase64(file, (result: string) => {
|
||||||
|
setImageSrc(result);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findFirstOutput = (): HTMLElement => {
|
||||||
|
const indexOfFirstCodeCellWithDisplay = NotebookUtil.findFirstCodeCellWithDisplay(notebookObject);
|
||||||
|
const cellOutputDomElements = notebookParentDomElement.querySelectorAll<HTMLElement>(".nteract-cell-outputs");
|
||||||
|
return cellOutputDomElements[indexOfFirstCodeCellWithDisplay];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="publishNotebookPanelContent">
|
||||||
|
<Stack className="panelMainContent" tokens={{ childrenGap: 20 }}>
|
||||||
|
<Stack.Item>
|
||||||
|
<Text>{descriptionPara1}</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<Text>{descriptionPara2}</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
ariaLabel="Name"
|
||||||
|
defaultValue={FileSystemUtil.stripExtension(notebookName, "ipynb")}
|
||||||
|
required
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
const notebookName = newValue + ".ipynb";
|
||||||
|
setNotebookName(notebookName);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
ariaLabel="Description"
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
required
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setNotebookDescription(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<TextField
|
||||||
|
label="Tags"
|
||||||
|
ariaLabel="Tags"
|
||||||
|
placeholder="Optional tag 1, Optional tag 2"
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setNotebookTags(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<Dropdown {...thumbnailSelectorProps} />
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>{renderThumbnailSelectors(type)}</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<Text>Preview</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<GalleryCardComponent
|
||||||
|
data={{
|
||||||
|
id: undefined,
|
||||||
|
name: notebookName,
|
||||||
|
description: notebookDescription,
|
||||||
|
gitSha: undefined,
|
||||||
|
tags: notebookTags.split(","),
|
||||||
|
author: notebookAuthor,
|
||||||
|
thumbnailUrl: imageSrc,
|
||||||
|
created: notebookCreatedDate,
|
||||||
|
isSample: false,
|
||||||
|
downloads: undefined,
|
||||||
|
favorites: undefined,
|
||||||
|
views: undefined,
|
||||||
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined,
|
||||||
|
}}
|
||||||
|
isFavorite={undefined}
|
||||||
|
showDownload={false}
|
||||||
|
showDelete={false}
|
||||||
|
onClick={() => undefined}
|
||||||
|
onTagClick={undefined}
|
||||||
|
onFavoriteClick={undefined}
|
||||||
|
onUnfavoriteClick={undefined}
|
||||||
|
onDownloadClick={undefined}
|
||||||
|
onDeleteClick={undefined}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -88,7 +88,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"author": "CosmosDB",
|
"author": "CosmosDB",
|
||||||
"created": "2020-07-17T00:00:00Z",
|
"created": "2020-07-17T00:00:00Z",
|
||||||
"description": "",
|
"description": "sample description",
|
||||||
"downloads": undefined,
|
"downloads": undefined,
|
||||||
"favorites": undefined,
|
"favorites": undefined,
|
||||||
"gitSha": undefined,
|
"gitSha": undefined,
|
||||||
@@ -99,12 +99,14 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
|||||||
"pendingScanJobIds": undefined,
|
"pendingScanJobIds": undefined,
|
||||||
"policyViolations": undefined,
|
"policyViolations": undefined,
|
||||||
"tags": Array [
|
"tags": Array [
|
||||||
"",
|
"tag1",
|
||||||
|
" tag2",
|
||||||
],
|
],
|
||||||
"thumbnailUrl": undefined,
|
"thumbnailUrl": "https://i.ytimg.com/vi/E_lByLdKeKY/maxresdefault.jpg",
|
||||||
"views": undefined,
|
"views": undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onClick={[Function]}
|
||||||
showDelete={false}
|
showDelete={false}
|
||||||
showDownload={false}
|
showDownload={false}
|
||||||
/>
|
/>
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
import { toJS } from "@nteract/commutable";
|
|
||||||
import { ImmutableNotebook } from "@nteract/commutable/src";
|
|
||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
|
||||||
import { HttpStatusCodes } from "../../Common/Constants";
|
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../../Common/ErrorHandlingUtils";
|
|
||||||
import { JunoClient } from "../../Juno/JunoClient";
|
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { traceFailure, traceStart, traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent";
|
|
||||||
import { GalleryTab } from "../Controls/NotebookGallery/GalleryViewerComponent";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import * as FileSystemUtil from "../Notebook/FileSystemUtil";
|
|
||||||
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
|
||||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
|
||||||
|
|
||||||
export class PublishNotebookPaneAdapter implements ReactAdapter {
|
|
||||||
parameters: ko.Observable<number>;
|
|
||||||
private isOpened: boolean;
|
|
||||||
private isExecuting: boolean;
|
|
||||||
private formError: string;
|
|
||||||
private formErrorDetail: string;
|
|
||||||
|
|
||||||
private name: string;
|
|
||||||
private author: string;
|
|
||||||
private content: string;
|
|
||||||
private description: string;
|
|
||||||
private tags: string;
|
|
||||||
private imageSrc: string;
|
|
||||||
private notebookObject: ImmutableNotebook;
|
|
||||||
private parentDomElement: HTMLElement;
|
|
||||||
private isCodeOfConductAccepted: boolean;
|
|
||||||
|
|
||||||
constructor(private container: Explorer, private junoClient: JunoClient) {
|
|
||||||
this.parameters = ko.observable(Date.now());
|
|
||||||
this.reset();
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
if (!this.isOpened) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
|
||||||
container: this.container,
|
|
||||||
formError: this.formError,
|
|
||||||
formErrorDetail: this.formErrorDetail,
|
|
||||||
id: "publishnotebookpane",
|
|
||||||
isExecuting: this.isExecuting,
|
|
||||||
title: "Publish to gallery",
|
|
||||||
submitButtonText: "Publish",
|
|
||||||
onClose: () => this.close(),
|
|
||||||
onSubmit: () => this.submit(),
|
|
||||||
isSubmitButtonHidden: !this.isCodeOfConductAccepted,
|
|
||||||
};
|
|
||||||
|
|
||||||
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
|
||||||
notebookName: this.name,
|
|
||||||
notebookDescription: "",
|
|
||||||
notebookTags: "",
|
|
||||||
notebookAuthor: this.author,
|
|
||||||
notebookCreatedDate: new Date().toISOString(),
|
|
||||||
notebookObject: this.notebookObject,
|
|
||||||
notebookParentDomElement: this.parentDomElement,
|
|
||||||
onChangeName: (newValue: string) => (this.name = newValue),
|
|
||||||
onChangeDescription: (newValue: string) => (this.description = newValue),
|
|
||||||
onChangeTags: (newValue: string) => (this.tags = newValue),
|
|
||||||
onChangeImageSrc: (newValue: string) => (this.imageSrc = newValue),
|
|
||||||
onError: this.createFormError,
|
|
||||||
clearFormError: this.clearFormError,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RightPaneForm {...props}>
|
|
||||||
{!this.isCodeOfConductAccepted ? (
|
|
||||||
<div style={{ padding: "15px", marginTop: "10px" }}>
|
|
||||||
<CodeOfConductComponent
|
|
||||||
junoClient={this.junoClient}
|
|
||||||
onAcceptCodeOfConduct={() => {
|
|
||||||
this.isCodeOfConductAccepted = true;
|
|
||||||
this.triggerRender();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
|
||||||
)}
|
|
||||||
</RightPaneForm>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async open(
|
|
||||||
name: string,
|
|
||||||
author: string,
|
|
||||||
notebookContent: string | ImmutableNotebook,
|
|
||||||
parentDomElement: HTMLElement
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
const response = await this.junoClient.isCodeOfConductAccepted();
|
|
||||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
|
||||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isCodeOfConductAccepted = response.data;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
|
||||||
"Failed to check if code of conduct was accepted"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
this.author = author;
|
|
||||||
if (typeof notebookContent === "string") {
|
|
||||||
this.content = notebookContent as string;
|
|
||||||
} else {
|
|
||||||
this.content = JSON.stringify(toJS(notebookContent));
|
|
||||||
this.notebookObject = notebookContent;
|
|
||||||
}
|
|
||||||
this.parentDomElement = parentDomElement;
|
|
||||||
|
|
||||||
this.isOpened = true;
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
this.reset();
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async submit(): Promise<void> {
|
|
||||||
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${this.name} to gallery`);
|
|
||||||
this.isExecuting = true;
|
|
||||||
this.triggerRender();
|
|
||||||
|
|
||||||
let startKey: number;
|
|
||||||
|
|
||||||
if (!this.name || !this.description || !this.author || !this.imageSrc) {
|
|
||||||
const formError = `Failed to publish ${this.name} to gallery`;
|
|
||||||
const formErrorDetail = "Name, description, author and cover image are required";
|
|
||||||
this.createFormError(formError, formErrorDetail, "PublishNotebookPaneAdapter/submit");
|
|
||||||
this.isExecuting = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
startKey = traceStart(Action.NotebooksGalleryPublish, {});
|
|
||||||
|
|
||||||
const response = await this.junoClient.publishNotebook(
|
|
||||||
this.name,
|
|
||||||
this.description,
|
|
||||||
this.tags?.split(","),
|
|
||||||
this.author,
|
|
||||||
this.imageSrc,
|
|
||||||
this.content
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
if (data) {
|
|
||||||
let isPublishPending = false;
|
|
||||||
|
|
||||||
if (data.pendingScanJobIds?.length > 0) {
|
|
||||||
isPublishPending = true;
|
|
||||||
NotificationConsoleUtils.logConsoleInfo(
|
|
||||||
`Content of ${this.name} is currently being scanned for illegal content. It will not be available in the public gallery until the review is complete (may take a few days).`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
NotificationConsoleUtils.logConsoleInfo(`Published ${this.name} to gallery`);
|
|
||||||
this.container.openGallery(GalleryTab.Published);
|
|
||||||
}
|
|
||||||
|
|
||||||
traceSuccess(
|
|
||||||
Action.NotebooksGalleryPublish,
|
|
||||||
{
|
|
||||||
notebookId: data.id,
|
|
||||||
isPublishPending,
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.NotebooksGalleryPublish,
|
|
||||||
{
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.formError = `Failed to publish ${FileSystemUtil.stripExtension(this.name, "ipynb")} to gallery`;
|
|
||||||
this.formErrorDetail = `${errorMessage}`;
|
|
||||||
handleError(errorMessage, "PublishNotebookPaneAdapter/submit", this.formError);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
clearPublishingMessage();
|
|
||||||
this.isExecuting = false;
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private createFormError = (formError: string, formErrorDetail: string, area: string): void => {
|
|
||||||
this.formError = formError;
|
|
||||||
this.formErrorDetail = formErrorDetail;
|
|
||||||
handleError(formErrorDetail, area, formError);
|
|
||||||
this.triggerRender();
|
|
||||||
};
|
|
||||||
|
|
||||||
private clearFormError = (): void => {
|
|
||||||
this.formError = undefined;
|
|
||||||
this.formErrorDetail = undefined;
|
|
||||||
this.triggerRender();
|
|
||||||
};
|
|
||||||
|
|
||||||
private reset = (): void => {
|
|
||||||
this.isOpened = false;
|
|
||||||
this.isExecuting = false;
|
|
||||||
this.formError = undefined;
|
|
||||||
this.formErrorDetail = undefined;
|
|
||||||
this.name = undefined;
|
|
||||||
this.author = undefined;
|
|
||||||
this.content = undefined;
|
|
||||||
this.description = undefined;
|
|
||||||
this.tags = undefined;
|
|
||||||
this.imageSrc = undefined;
|
|
||||||
this.notebookObject = undefined;
|
|
||||||
this.parentDomElement = undefined;
|
|
||||||
this.isCodeOfConductAccepted = undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
import { ITextFieldProps, Stack, Text, TextField, Dropdown, IDropdownProps } from "office-ui-fabric-react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { GalleryCardComponent } from "../Controls/NotebookGallery/Cards/GalleryCardComponent";
|
|
||||||
import * as FileSystemUtil from "../Notebook/FileSystemUtil";
|
|
||||||
import "./PublishNotebookPaneComponent.less";
|
|
||||||
import Html2Canvas from "html2canvas";
|
|
||||||
import { ImmutableNotebook } from "@nteract/commutable/src";
|
|
||||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
|
||||||
|
|
||||||
export interface PublishNotebookPaneProps {
|
|
||||||
notebookName: string;
|
|
||||||
notebookDescription: string;
|
|
||||||
notebookTags: string;
|
|
||||||
notebookAuthor: string;
|
|
||||||
notebookCreatedDate: string;
|
|
||||||
notebookObject: ImmutableNotebook;
|
|
||||||
notebookParentDomElement?: HTMLElement;
|
|
||||||
onChangeName: (newValue: string) => void;
|
|
||||||
onChangeDescription: (newValue: string) => void;
|
|
||||||
onChangeTags: (newValue: string) => void;
|
|
||||||
onChangeImageSrc: (newValue: string) => void;
|
|
||||||
onError: (formError: string, formErrorDetail: string, area: string) => void;
|
|
||||||
clearFormError: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PublishNotebookPaneState {
|
|
||||||
type: string;
|
|
||||||
notebookName: string;
|
|
||||||
notebookDescription: string;
|
|
||||||
notebookTags: string;
|
|
||||||
imageSrc: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ImageTypes {
|
|
||||||
Url = "URL",
|
|
||||||
CustomImage = "Custom Image",
|
|
||||||
TakeScreenshot = "Take Screenshot",
|
|
||||||
UseFirstDisplayOutput = "Use First Display Output",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PublishNotebookPaneComponent extends React.Component<PublishNotebookPaneProps, PublishNotebookPaneState> {
|
|
||||||
private static readonly maxImageSizeInMib = 1.5;
|
|
||||||
private descriptionPara1: string;
|
|
||||||
private descriptionPara2: string;
|
|
||||||
private nameProps: ITextFieldProps;
|
|
||||||
private descriptionProps: ITextFieldProps;
|
|
||||||
private tagsProps: ITextFieldProps;
|
|
||||||
private thumbnailUrlProps: ITextFieldProps;
|
|
||||||
private thumbnailSelectorProps: IDropdownProps;
|
|
||||||
private imageToBase64: (file: File, updateImageSrc: (result: string) => void) => void;
|
|
||||||
private takeScreenshot: (target: HTMLElement, onError: (error: Error) => void) => void;
|
|
||||||
|
|
||||||
constructor(props: PublishNotebookPaneProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
type: ImageTypes.CustomImage,
|
|
||||||
notebookName: props.notebookName,
|
|
||||||
notebookDescription: "",
|
|
||||||
notebookTags: "",
|
|
||||||
imageSrc: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => {
|
|
||||||
updateImageSrc(reader.result.toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
const onError = this.props.onError;
|
|
||||||
reader.onerror = (error) => {
|
|
||||||
const formError = `Failed to convert ${file.name} to base64 format`;
|
|
||||||
const formErrorDetail = `${error}`;
|
|
||||||
const area = "PublishNotebookPaneComponent/selectImageFile";
|
|
||||||
onError(formError, formErrorDetail, area);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
this.takeScreenshot = (target: HTMLElement, onError: (error: Error) => void): void => {
|
|
||||||
const updateImageSrcWithScreenshot = (canvasUrl: string): void => {
|
|
||||||
this.props.onChangeImageSrc(canvasUrl);
|
|
||||||
this.setState({ imageSrc: canvasUrl });
|
|
||||||
};
|
|
||||||
|
|
||||||
target.scrollIntoView();
|
|
||||||
Html2Canvas(target, {
|
|
||||||
useCORS: true,
|
|
||||||
allowTaint: true,
|
|
||||||
scale: 1,
|
|
||||||
logging: true,
|
|
||||||
})
|
|
||||||
.then((canvas) => {
|
|
||||||
//redraw canvas to fit Card Cover Image dimensions
|
|
||||||
const originalImageData = canvas.toDataURL();
|
|
||||||
const requiredHeight =
|
|
||||||
parseInt(canvas.style.width.split("px")[0]) * GalleryCardComponent.cardHeightToWidthRatio;
|
|
||||||
canvas.height = requiredHeight;
|
|
||||||
const context = canvas.getContext("2d");
|
|
||||||
const image = new Image();
|
|
||||||
image.src = originalImageData;
|
|
||||||
image.onload = () => {
|
|
||||||
context.drawImage(image, 0, 0);
|
|
||||||
updateImageSrcWithScreenshot(canvas.toDataURL());
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
onError(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.descriptionPara1 =
|
|
||||||
"When published, this notebook will appear in the Azure Cosmos DB notebooks public gallery. Make sure you have removed any sensitive data or output before publishing.";
|
|
||||||
|
|
||||||
this.descriptionPara2 = `Would you like to publish and share "${FileSystemUtil.stripExtension(
|
|
||||||
this.props.notebookName,
|
|
||||||
"ipynb"
|
|
||||||
)}" to the gallery?`;
|
|
||||||
|
|
||||||
this.thumbnailUrlProps = {
|
|
||||||
label: "Cover image url",
|
|
||||||
ariaLabel: "Cover image url",
|
|
||||||
required: true,
|
|
||||||
onChange: (event, newValue) => {
|
|
||||||
this.props.onChangeImageSrc(newValue);
|
|
||||||
this.setState({ imageSrc: newValue });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const screenshotErrorHandler = (error: Error) => {
|
|
||||||
const formError = "Failed to take screen shot";
|
|
||||||
const formErrorDetail = `${error}`;
|
|
||||||
const area = "PublishNotebookPaneComponent/takeScreenshot";
|
|
||||||
this.props.onError(formError, formErrorDetail, area);
|
|
||||||
};
|
|
||||||
|
|
||||||
const firstOutputErrorHandler = (error: Error) => {
|
|
||||||
const formError = "Failed to capture first output";
|
|
||||||
const formErrorDetail = `${error}`;
|
|
||||||
const area = "PublishNotebookPaneComponent/UseFirstOutput";
|
|
||||||
this.props.onError(formError, formErrorDetail, area);
|
|
||||||
};
|
|
||||||
|
|
||||||
const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url];
|
|
||||||
|
|
||||||
if (this.props.notebookParentDomElement) {
|
|
||||||
options.push(ImageTypes.TakeScreenshot);
|
|
||||||
if (this.props.notebookObject) {
|
|
||||||
options.push(ImageTypes.UseFirstDisplayOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.thumbnailSelectorProps = {
|
|
||||||
label: "Cover image",
|
|
||||||
defaultSelectedKey: ImageTypes.CustomImage,
|
|
||||||
ariaLabel: "Cover image",
|
|
||||||
options: options.map((value: string) => ({ text: value, key: value })),
|
|
||||||
onChange: async (event, options) => {
|
|
||||||
this.setState({ imageSrc: undefined });
|
|
||||||
this.props.onChangeImageSrc(undefined);
|
|
||||||
this.props.clearFormError();
|
|
||||||
if (options.text === ImageTypes.TakeScreenshot) {
|
|
||||||
try {
|
|
||||||
await this.takeScreenshot(this.props.notebookParentDomElement, screenshotErrorHandler);
|
|
||||||
} catch (error) {
|
|
||||||
screenshotErrorHandler(error);
|
|
||||||
}
|
|
||||||
} else if (options.text === ImageTypes.UseFirstDisplayOutput) {
|
|
||||||
try {
|
|
||||||
await this.takeScreenshot(this.findFirstOutput(), firstOutputErrorHandler);
|
|
||||||
} catch (error) {
|
|
||||||
firstOutputErrorHandler(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ type: options.text });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.nameProps = {
|
|
||||||
label: "Name",
|
|
||||||
ariaLabel: "Name",
|
|
||||||
defaultValue: FileSystemUtil.stripExtension(this.props.notebookName, "ipynb"),
|
|
||||||
required: true,
|
|
||||||
onChange: (event, newValue) => {
|
|
||||||
const notebookName = newValue + ".ipynb";
|
|
||||||
this.props.onChangeName(notebookName);
|
|
||||||
this.setState({ notebookName });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.descriptionProps = {
|
|
||||||
label: "Description",
|
|
||||||
ariaLabel: "Description",
|
|
||||||
multiline: true,
|
|
||||||
rows: 3,
|
|
||||||
required: true,
|
|
||||||
onChange: (event, newValue) => {
|
|
||||||
this.props.onChangeDescription(newValue);
|
|
||||||
this.setState({ notebookDescription: newValue });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tagsProps = {
|
|
||||||
label: "Tags",
|
|
||||||
ariaLabel: "Tags",
|
|
||||||
placeholder: "Optional tag 1, Optional tag 2",
|
|
||||||
onChange: (event, newValue) => {
|
|
||||||
this.props.onChangeTags(newValue);
|
|
||||||
this.setState({ notebookTags: newValue });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderThumbnailSelectors(type: string) {
|
|
||||||
switch (type) {
|
|
||||||
case ImageTypes.Url:
|
|
||||||
return <TextField {...this.thumbnailUrlProps} />;
|
|
||||||
case ImageTypes.CustomImage:
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
id="selectImageFile"
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
onChange={(event) => {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file.size / 1024 ** 2 > PublishNotebookPaneComponent.maxImageSizeInMib) {
|
|
||||||
event.target.value = "";
|
|
||||||
const formError = `Failed to upload ${file.name}`;
|
|
||||||
const formErrorDetail = `Image is larger than ${PublishNotebookPaneComponent.maxImageSizeInMib} MiB. Please Choose a different image.`;
|
|
||||||
const area = "PublishNotebookPaneComponent/selectImageFile";
|
|
||||||
|
|
||||||
this.props.onError(formError, formErrorDetail, area);
|
|
||||||
this.props.onChangeImageSrc(undefined);
|
|
||||||
this.setState({ imageSrc: undefined });
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.props.clearFormError();
|
|
||||||
}
|
|
||||||
this.imageToBase64(file, (result: string) => {
|
|
||||||
this.props.onChangeImageSrc(result);
|
|
||||||
this.setState({ imageSrc: result });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private findFirstOutput(): HTMLElement {
|
|
||||||
const indexOfFirstCodeCellWithDisplay = NotebookUtil.findFirstCodeCellWithDisplay(this.props.notebookObject);
|
|
||||||
const cellOutputDomElements = this.props.notebookParentDomElement.querySelectorAll<HTMLElement>(
|
|
||||||
".nteract-cell-outputs"
|
|
||||||
);
|
|
||||||
return cellOutputDomElements[indexOfFirstCodeCellWithDisplay];
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="publishNotebookPanelContent">
|
|
||||||
<Stack className="panelMainContent" tokens={{ childrenGap: 20 }}>
|
|
||||||
<Stack.Item>
|
|
||||||
<Text>{this.descriptionPara1}</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Text>{this.descriptionPara2}</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<TextField {...this.nameProps} />
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<TextField {...this.descriptionProps} />
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<TextField {...this.tagsProps} />
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Dropdown {...this.thumbnailSelectorProps} />
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>{this.renderThumbnailSelectors(this.state.type)}</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<Text>Preview</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item>
|
|
||||||
<GalleryCardComponent
|
|
||||||
data={{
|
|
||||||
id: undefined,
|
|
||||||
name: this.state.notebookName,
|
|
||||||
description: this.state.notebookDescription,
|
|
||||||
gitSha: undefined,
|
|
||||||
tags: this.state.notebookTags.split(","),
|
|
||||||
author: this.props.notebookAuthor,
|
|
||||||
thumbnailUrl: this.state.imageSrc,
|
|
||||||
created: this.props.notebookCreatedDate,
|
|
||||||
isSample: false,
|
|
||||||
downloads: undefined,
|
|
||||||
favorites: undefined,
|
|
||||||
views: undefined,
|
|
||||||
newCellId: undefined,
|
|
||||||
policyViolations: undefined,
|
|
||||||
pendingScanJobIds: undefined,
|
|
||||||
}}
|
|
||||||
isFavorite={undefined}
|
|
||||||
showDownload={false}
|
|
||||||
showDelete={false}
|
|
||||||
onClick={undefined}
|
|
||||||
onTagClick={undefined}
|
|
||||||
onFavoriteClick={undefined}
|
|
||||||
onUnfavoriteClick={undefined}
|
|
||||||
onDownloadClick={undefined}
|
|
||||||
onDeleteClick={undefined}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -153,40 +153,6 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -209,9 +175,7 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -234,20 +198,6 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -402,9 +352,7 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -455,40 +403,6 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -520,7 +434,6 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -552,21 +465,9 @@ exports[`Settings Pane should render Default properly 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 {
|
||||||
@@ -592,20 +493,6 @@ exports[`Settings Pane should render Default properly 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],
|
||||||
@@ -904,40 +791,6 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -960,9 +813,7 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -985,20 +836,6 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -1153,9 +990,7 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -1206,40 +1041,6 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -1271,7 +1072,6 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
@@ -1303,21 +1103,9 @@ exports[`Settings Pane should render Gremlin properly 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 {
|
||||||
@@ -1343,20 +1131,6 @@ exports[`Settings Pane should render Gremlin properly 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],
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
|
||||||
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
|
||||||
<div class="contextual-pane" id="stringInputPane">
|
|
||||||
<!-- String Input form -- Start -->
|
|
||||||
<div class="contextual-pane-in">
|
|
||||||
<form class="paneContentContainer" data-bind="submit: submit">
|
|
||||||
<!-- String Input header - Start -->
|
|
||||||
<div class="firstdivbg headerline">
|
|
||||||
<span role="heading" aria-level="2" data-bind="text: title"></span>
|
|
||||||
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel"></div>
|
|
||||||
</div>
|
|
||||||
<!-- String Input header - End -->
|
|
||||||
|
|
||||||
<!-- String Input errors - Start -->
|
|
||||||
<div
|
|
||||||
class="warningErrorContainer"
|
|
||||||
aria-live="assertive"
|
|
||||||
data-bind="visible: formErrors() && formErrors() !== ''"
|
|
||||||
>
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
|
|
||||||
<span class="warningErrorDetailsLinkContainer">
|
|
||||||
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
|
|
||||||
<a
|
|
||||||
class="errorLink"
|
|
||||||
role="link"
|
|
||||||
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
|
|
||||||
>More details</a
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- String Input errors - End -->
|
|
||||||
|
|
||||||
<!-- String Input inputs - Start -->
|
|
||||||
<div class="paneMainContent">
|
|
||||||
<div>
|
|
||||||
<p data-bind="text: inputLabel"></p>
|
|
||||||
<p>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="collectionIdConfirmation"
|
|
||||||
required
|
|
||||||
class="collid"
|
|
||||||
data-bind="textInput: stringInput, hasFocus: firstFieldHasFocus"
|
|
||||||
aria-label="inputLabel"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="paneFooter">
|
|
||||||
<div class="leftpanel-okbut">
|
|
||||||
<input type="submit" data-bind="attr: { value: submitButtonLabel }" class="btncreatecoll1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- String Input inputs - End -->
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- String Input form - Start -->
|
|
||||||
<!-- Loader - Start -->
|
|
||||||
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
|
|
||||||
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
|
|
||||||
</div>
|
|
||||||
<!-- Loader - End -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import Q from "q";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
|
||||||
|
|
||||||
export interface StringInputPaneOpenOptions {
|
|
||||||
paneTitle: string;
|
|
||||||
inputLabel: string;
|
|
||||||
errorMessage: string;
|
|
||||||
inProgressMessage: string;
|
|
||||||
successMessage: string;
|
|
||||||
onSubmit: (input: string) => Promise<any>;
|
|
||||||
submitButtonLabel: string;
|
|
||||||
defaultInput?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic pane to get a single string input from user
|
|
||||||
*/
|
|
||||||
export class StringInputPane extends ContextualPaneBase {
|
|
||||||
private openOptions: StringInputPaneOpenOptions;
|
|
||||||
private submitButtonLabel: ko.Observable<string>;
|
|
||||||
private inputLabel: ko.Observable<string>;
|
|
||||||
private stringInput: ko.Observable<string>;
|
|
||||||
|
|
||||||
private paneDeferred: Q.Deferred<any>;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.PaneOptions) {
|
|
||||||
super(options);
|
|
||||||
this.resetData();
|
|
||||||
this.inputLabel = ko.observable("");
|
|
||||||
this.submitButtonLabel = ko.observable("Load");
|
|
||||||
this.stringInput = ko.observable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public submit() {
|
|
||||||
this.formErrors("");
|
|
||||||
this.formErrorsDetails("");
|
|
||||||
|
|
||||||
const clearInProgressMessage = logConsoleProgress(`${this.openOptions.inProgressMessage} ${this.stringInput()}`);
|
|
||||||
this.isExecuting(true);
|
|
||||||
this.openOptions
|
|
||||||
.onSubmit(this.stringInput())
|
|
||||||
.then(
|
|
||||||
(value: any) => {
|
|
||||||
logConsoleInfo(`${this.openOptions.successMessage}: ${this.stringInput()}`);
|
|
||||||
this.close();
|
|
||||||
this.paneDeferred.resolve(value);
|
|
||||||
},
|
|
||||||
(reason) => {
|
|
||||||
let error = reason;
|
|
||||||
if (reason instanceof Error) {
|
|
||||||
error = reason.message;
|
|
||||||
} else if (typeof reason === "object") {
|
|
||||||
error = JSON.stringify(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's an AjaxError (AjaxObservable), add more error
|
|
||||||
if (reason.response && reason.response.message) {
|
|
||||||
error += `. ${reason.response.message}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formErrors(this.openOptions.errorMessage);
|
|
||||||
this.formErrorsDetails(`${this.openOptions.errorMessage}: ${error}`);
|
|
||||||
logConsoleError(`${this.openOptions.errorMessage} ${this.stringInput()}: ${error}`);
|
|
||||||
this.paneDeferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
this.isExecuting(false);
|
|
||||||
clearInProgressMessage();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public close() {
|
|
||||||
super.close();
|
|
||||||
this.resetData();
|
|
||||||
this.resetFileInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
public openWithOptions<T>(options: StringInputPaneOpenOptions): Q.Promise<T> {
|
|
||||||
this.openOptions = options;
|
|
||||||
this.title(this.openOptions.paneTitle);
|
|
||||||
if (this.openOptions.submitButtonLabel) {
|
|
||||||
this.submitButtonLabel(this.openOptions.submitButtonLabel);
|
|
||||||
}
|
|
||||||
this.inputLabel(this.openOptions.inputLabel);
|
|
||||||
this.stringInput(this.openOptions.defaultInput);
|
|
||||||
|
|
||||||
super.open();
|
|
||||||
this.paneDeferred = Q.defer<T>();
|
|
||||||
return this.paneDeferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetFileInput(): void {
|
|
||||||
this.stringInput("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
src/Explorer/Panes/StringInputPane/StringInputPane.test.tsx
Normal file
28
src/Explorer/Panes/StringInputPane/StringInputPane.test.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { mount } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { StringInputPane } from "./StringInputPane";
|
||||||
|
const props = {
|
||||||
|
explorer: new Explorer(),
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
errorMessage: "Could not create directory ",
|
||||||
|
inProgressMessage: "Creating directory ",
|
||||||
|
successMessage: "Created directory ",
|
||||||
|
inputLabel: "Enter new directory name",
|
||||||
|
paneTitle: "Create new directory",
|
||||||
|
submitButtonLabel: "Create",
|
||||||
|
defaultInput: "",
|
||||||
|
onSubmit: jest.fn(),
|
||||||
|
notebookFile: {
|
||||||
|
name: "Untitled1123.ipynb",
|
||||||
|
path: "notebooks/Untitled1123.ipynb",
|
||||||
|
type: 0,
|
||||||
|
timestamp: 1618452275805,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
describe("StringInput Pane", () => {
|
||||||
|
it("should render Create new directory properly", () => {
|
||||||
|
const wrapper = mount(<StringInputPane {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
122
src/Explorer/Panes/StringInputPane/StringInputPane.tsx
Normal file
122
src/Explorer/Panes/StringInputPane/StringInputPane.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { TextField } from "office-ui-fabric-react";
|
||||||
|
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
|
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||||
|
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||||
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
|
||||||
|
export interface StringInputPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
errorMessage: string;
|
||||||
|
inProgressMessage: string;
|
||||||
|
successMessage: string;
|
||||||
|
inputLabel: string;
|
||||||
|
paneTitle: string;
|
||||||
|
submitButtonLabel: string;
|
||||||
|
defaultInput: string;
|
||||||
|
onSubmit: (notebookFile: NotebookContentItem, input: string) => Promise<NotebookContentItem>;
|
||||||
|
notebookFile: NotebookContentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||||
|
explorer: container,
|
||||||
|
closePanel,
|
||||||
|
errorMessage,
|
||||||
|
inProgressMessage,
|
||||||
|
successMessage,
|
||||||
|
inputLabel,
|
||||||
|
paneTitle,
|
||||||
|
submitButtonLabel,
|
||||||
|
defaultInput,
|
||||||
|
onSubmit,
|
||||||
|
notebookFile,
|
||||||
|
}: StringInputPanelProps): JSX.Element => {
|
||||||
|
const [stringInput, setStringInput] = useState<string>(defaultInput);
|
||||||
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
|
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const submit = async (): Promise<void> => {
|
||||||
|
if (stringInput === "") {
|
||||||
|
const errorMessage = "Please " + inputLabel;
|
||||||
|
setFormErrors(errorMessage);
|
||||||
|
logConsoleError("Error while " + paneTitle + " : " + errorMessage);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
setFormErrors("");
|
||||||
|
setFormErrorsDetails("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
|
||||||
|
try {
|
||||||
|
const newNotebookFile: NotebookContentItem = await onSubmit(notebookFile, stringInput);
|
||||||
|
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
||||||
|
const originalPath = notebookFile.path;
|
||||||
|
|
||||||
|
const notebookTabs = container.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);
|
||||||
|
});
|
||||||
|
closePanel();
|
||||||
|
} catch (reason) {
|
||||||
|
let error = reason;
|
||||||
|
if (reason instanceof Error) {
|
||||||
|
error = reason.message;
|
||||||
|
} else if (typeof reason === "object") {
|
||||||
|
error = JSON.stringify(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's an AjaxError (AjaxObservable), add more error
|
||||||
|
if (reason?.response?.message) {
|
||||||
|
error += `. ${reason.response.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormErrors(errorMessage);
|
||||||
|
setFormErrorsDetails(`${errorMessage}: ${error}`);
|
||||||
|
logConsoleError(`${errorMessage} ${stringInput}: ${error}`);
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false);
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
|
container: container,
|
||||||
|
formError: formErrors,
|
||||||
|
formErrorDetail: formErrorsDetails,
|
||||||
|
id: "stringInputPane",
|
||||||
|
isExecuting: isExecuting,
|
||||||
|
title: paneTitle,
|
||||||
|
submitButtonText: submitButtonLabel,
|
||||||
|
onClose: closePanel,
|
||||||
|
onSubmit: submit,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
|
<div className="paneMainContent">
|
||||||
|
<TextField
|
||||||
|
label={inputLabel}
|
||||||
|
name="collectionIdConfirmation"
|
||||||
|
value={stringInput}
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
|
||||||
|
setStringInput(newValue)
|
||||||
|
}
|
||||||
|
aria-label={inputLabel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</GenericRightPaneComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
51
src/Explorer/Panes/Tables/EditTableEntityPanel.test.tsx
Normal file
51
src/Explorer/Panes/Tables/EditTableEntityPanel.test.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { mount } from "enzyme";
|
||||||
|
import * as ko from "knockout";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
|
import * as Entities from "../../Tables/Entities";
|
||||||
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
|
import { EditTableEntityPanel } from "./EditTableEntityPanel";
|
||||||
|
|
||||||
|
describe("Excute Edit Table Entity Pane", () => {
|
||||||
|
const fakeExplorer = {} as Explorer;
|
||||||
|
const fakeQueryTablesTab = {} as QueryTablesTab;
|
||||||
|
const fakeTableEntityListViewModel = {} as TableListViewModal;
|
||||||
|
fakeTableEntityListViewModel.items = ko.observableArray<Entities.ITableEntity>();
|
||||||
|
const fakeCassandraApiClient = {} as CassandraAPIDataClient;
|
||||||
|
fakeTableEntityListViewModel.headers = [];
|
||||||
|
fakeTableEntityListViewModel.selected = ko.observableArray<Entities.ITableEntity>([{}]);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
explorer: fakeExplorer,
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
queryTablesTab: fakeQueryTablesTab,
|
||||||
|
tableEntityListViewModel: fakeTableEntityListViewModel,
|
||||||
|
cassandraApiClient: fakeCassandraApiClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially display 4 input field, 2 properties and 1 entity values", () => {
|
||||||
|
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||||
|
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("add a new entity row", () => {
|
||||||
|
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||||
|
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||||
|
expect(wrapper.find("input[type='text']")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("remove a entity field", () => {
|
||||||
|
const wrapper = mount(<EditTableEntityPanel {...props} />);
|
||||||
|
// Since default entity row doesn't have delete option, so added row then delete for test cases.
|
||||||
|
wrapper.find(".addButtonEntiy").last().simulate("click");
|
||||||
|
wrapper.find("#deleteEntity").last().simulate("click");
|
||||||
|
expect(wrapper.find("input[type='text']")).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
419
src/Explorer/Panes/Tables/EditTableEntityPanel.tsx
Normal file
419
src/Explorer/Panes/Tables/EditTableEntityPanel.tsx
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
import { useBoolean } from "@uifabric/react-hooks";
|
||||||
|
import {
|
||||||
|
IDropdownOption,
|
||||||
|
Image,
|
||||||
|
IPanelProps,
|
||||||
|
IRenderFunction,
|
||||||
|
Label,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import * as _ from "underscore";
|
||||||
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
|
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
|
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||||
|
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
|
||||||
|
import * as Entities from "../../Tables/Entities";
|
||||||
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||||
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
|
import { PanelContainerComponent } from "../PanelContainerComponent";
|
||||||
|
import {
|
||||||
|
attributeNameLabel,
|
||||||
|
attributeValueLabel,
|
||||||
|
backImageProps,
|
||||||
|
cassandraOptions,
|
||||||
|
columnProps,
|
||||||
|
dataTypeLabel,
|
||||||
|
defaultStringPlaceHolder,
|
||||||
|
detailedHelp,
|
||||||
|
entityFromAttributes,
|
||||||
|
getAddButtonLabel,
|
||||||
|
getEntityValuePlaceholder,
|
||||||
|
getFormattedTime,
|
||||||
|
imageProps,
|
||||||
|
isValidEntities,
|
||||||
|
options,
|
||||||
|
} from "./Validators/EntityTableHelper";
|
||||||
|
|
||||||
|
interface EditTableEntityPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
queryTablesTab: QueryTablesTab;
|
||||||
|
tableEntityListViewModel: TableEntityListViewModel;
|
||||||
|
cassandraApiClient: CassandraAPIDataClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EntityRowType {
|
||||||
|
property: string;
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
isPropertyTypeDisable: boolean;
|
||||||
|
isDeleteOptionVisible: boolean;
|
||||||
|
id: number;
|
||||||
|
entityValuePlaceholder: string;
|
||||||
|
isEntityTypeDate: boolean;
|
||||||
|
entityTimeValue?: string;
|
||||||
|
isEntityValueDisable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps> = ({
|
||||||
|
explorer,
|
||||||
|
closePanel,
|
||||||
|
queryTablesTab,
|
||||||
|
tableEntityListViewModel,
|
||||||
|
cassandraApiClient,
|
||||||
|
}: EditTableEntityPanelProps): JSX.Element => {
|
||||||
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
|
const [originalDocument, setOriginalDocument] = useState<Entities.ITableEntity>({});
|
||||||
|
const [entityAttributeProperty, setEntityAttributeProperty] = useState<string>("");
|
||||||
|
|
||||||
|
const [
|
||||||
|
isEntityValuePanelOpen,
|
||||||
|
{ setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse },
|
||||||
|
] = useBoolean(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let originalDocument: { [key: string]: any } = {};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const entityAttribute: any = tableEntityListViewModel.selected();
|
||||||
|
const entityFormattedAttribute = constructDisplayedAttributes(entityAttribute[0]);
|
||||||
|
setEntities(entityFormattedAttribute);
|
||||||
|
|
||||||
|
if (userContext.apiType === "Tables") {
|
||||||
|
originalDocument = TableEntityProcessor.convertEntitiesToDocuments(entityAttribute, queryTablesTab.collection)[0];
|
||||||
|
originalDocument.id = (): string => originalDocument.$id;
|
||||||
|
} else {
|
||||||
|
originalDocument = entityAttribute;
|
||||||
|
}
|
||||||
|
setOriginalDocument(originalDocument);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const constructDisplayedAttributes = (entity: Entities.ITableEntity): EntityRowType[] => {
|
||||||
|
const displayedAttributes: EntityRowType[] = [];
|
||||||
|
const keys = Object.keys(entity);
|
||||||
|
keys.length &&
|
||||||
|
keys.forEach((key: string) => {
|
||||||
|
if (
|
||||||
|
key !== TableEntityProcessor.keyProperties.attachments &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.etag &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.resourceId &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.self &&
|
||||||
|
(userContext.apiType !== "Cassandra" || key !== TableConstants.EntityKeyNames.RowKey)
|
||||||
|
) {
|
||||||
|
if (userContext.apiType === "Cassandra") {
|
||||||
|
const cassandraKeys = queryTablesTab.collection.cassandraKeys.partitionKeys
|
||||||
|
.concat(queryTablesTab.collection.cassandraKeys.clusteringKeys)
|
||||||
|
.map((key) => key.property);
|
||||||
|
const entityAttribute: Entities.ITableEntityAttribute = entity[key];
|
||||||
|
const entityAttributeType: string = entityAttribute.$;
|
||||||
|
const displayValue: string = getPropertyDisplayValue(entity, key, entityAttributeType);
|
||||||
|
const nonEditableType: boolean =
|
||||||
|
entityAttributeType === TableConstants.CassandraType.Blob ||
|
||||||
|
entityAttributeType === TableConstants.CassandraType.Inet;
|
||||||
|
const isDisable = !_.contains<string>(cassandraKeys, key) && !nonEditableType;
|
||||||
|
const time =
|
||||||
|
entityAttributeType === TableConstants.TableType.DateTime ? getFormattedTime(displayValue) : "";
|
||||||
|
displayedAttributes.push({
|
||||||
|
property: key,
|
||||||
|
type: entityAttributeType,
|
||||||
|
value: displayValue,
|
||||||
|
isPropertyTypeDisable: !nonEditableType,
|
||||||
|
isDeleteOptionVisible: isDisable,
|
||||||
|
id: displayedAttributes.length,
|
||||||
|
entityValuePlaceholder: defaultStringPlaceHolder,
|
||||||
|
isEntityTypeDate: entityAttributeType === "DateTime",
|
||||||
|
isEntityValueDisable: !isDisable,
|
||||||
|
entityTimeValue: time,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const entityAttribute: Entities.ITableEntityAttribute = entity[key];
|
||||||
|
const entityAttributeType: string = entityAttribute.$;
|
||||||
|
const displayValue: string = getPropertyDisplayValue(entity, key, entityAttributeType);
|
||||||
|
const editable: boolean = isAttributeEditable(key, entityAttributeType);
|
||||||
|
// As per VSO:189935, Binary properties are read-only, we still want to be able to remove them.
|
||||||
|
const removable: boolean = editable || entityAttributeType === TableConstants.TableType.Binary;
|
||||||
|
const time =
|
||||||
|
entityAttributeType === TableConstants.TableType.DateTime ? getFormattedTime(displayValue) : "";
|
||||||
|
displayedAttributes.push({
|
||||||
|
property: key,
|
||||||
|
type: entityAttributeType,
|
||||||
|
value: displayValue,
|
||||||
|
isPropertyTypeDisable: !editable,
|
||||||
|
isDeleteOptionVisible: removable,
|
||||||
|
id: displayedAttributes.length,
|
||||||
|
entityValuePlaceholder: defaultStringPlaceHolder,
|
||||||
|
isEntityTypeDate: entityAttributeType === "DateTime",
|
||||||
|
isEntityValueDisable: !editable,
|
||||||
|
entityTimeValue: time,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return displayedAttributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAttributeEditable = (attributeName: string, entityAttributeType: string) => {
|
||||||
|
return !(
|
||||||
|
attributeName === TableConstants.EntityKeyNames.PartitionKey ||
|
||||||
|
attributeName === TableConstants.EntityKeyNames.RowKey ||
|
||||||
|
attributeName === TableConstants.EntityKeyNames.Timestamp ||
|
||||||
|
// As per VSO:189935, Making Binary properties read-only in Edit Entity dialog until we have a full story for it.
|
||||||
|
entityAttributeType === TableConstants.TableType.Binary
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPropertyDisplayValue = (entity: Entities.ITableEntity, name: string, type: string): string => {
|
||||||
|
const attribute: Entities.ITableEntityAttribute = entity[name];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let displayValue: any = attribute._;
|
||||||
|
const isBinary: boolean = type === TableConstants.TableType.Binary;
|
||||||
|
|
||||||
|
// Showing the value in base64 for binary properties since that is what the Azure Storage Client Library expects.
|
||||||
|
// This means that, even if the Azure Storage API returns a byte[] of binary content, it needs that same array
|
||||||
|
// *base64 - encoded * as the value for the updated property or the whole update operation will fail.
|
||||||
|
if (isBinary && displayValue && $.isArray(displayValue.data)) {
|
||||||
|
const bytes: number[] = displayValue.data;
|
||||||
|
displayValue = getBase64DisplayValue(bytes);
|
||||||
|
}
|
||||||
|
return displayValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBase64DisplayValue = (bytes: number[]): string => {
|
||||||
|
let displayValue = "";
|
||||||
|
try {
|
||||||
|
const chars: string[] = bytes.map((byte: number) => String.fromCharCode(byte));
|
||||||
|
const toEncode: string = chars.join("");
|
||||||
|
displayValue = window.btoa(toEncode);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
return displayValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async (event: React.FormEvent<HTMLInputElement>): Promise<void> => {
|
||||||
|
if (!isValidEntities(entities)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
|
const tableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : explorer.tableDataClient;
|
||||||
|
const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument;
|
||||||
|
const newEntity: Entities.ITableEntity = await tableDataClient.updateDocument(
|
||||||
|
queryTablesTab.collection,
|
||||||
|
originalDocumentData,
|
||||||
|
entity
|
||||||
|
);
|
||||||
|
await tableEntityListViewModel.updateCachedEntity(newEntity);
|
||||||
|
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||||
|
tableEntityListViewModel.redrawTableThrottled();
|
||||||
|
}
|
||||||
|
tableEntityListViewModel.selected.removeAll();
|
||||||
|
tableEntityListViewModel.selected.push(newEntity);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||||
|
let newHeaders: string[] = [];
|
||||||
|
const keys = Object.keys(newEntity);
|
||||||
|
keys &&
|
||||||
|
keys.forEach((key: string) => {
|
||||||
|
if (
|
||||||
|
!_.contains(viewModel.headers, key) &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.attachments &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.etag &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.resourceId &&
|
||||||
|
key !== TableEntityProcessor.keyProperties.self &&
|
||||||
|
(!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey)
|
||||||
|
) {
|
||||||
|
newHeaders.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let newHeadersInserted = false;
|
||||||
|
if (newHeaders.length) {
|
||||||
|
if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) {
|
||||||
|
newHeaders = viewModel.headers.concat(newHeaders);
|
||||||
|
}
|
||||||
|
viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false);
|
||||||
|
newHeadersInserted = true;
|
||||||
|
}
|
||||||
|
return newHeadersInserted;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add new entity row
|
||||||
|
const addNewEntity = (): void => {
|
||||||
|
const cloneEntities = [...entities];
|
||||||
|
cloneEntities.splice(cloneEntities.length, 0, {
|
||||||
|
property: "",
|
||||||
|
type: TableConstants.TableType.String,
|
||||||
|
value: "",
|
||||||
|
isPropertyTypeDisable: false,
|
||||||
|
isDeleteOptionVisible: true,
|
||||||
|
id: cloneEntities.length + 1,
|
||||||
|
entityValuePlaceholder: "",
|
||||||
|
isEntityTypeDate: false,
|
||||||
|
});
|
||||||
|
setEntities(cloneEntities);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete entity row
|
||||||
|
const deleteEntityAtIndex = (indexToRemove: number): void => {
|
||||||
|
const cloneEntities = [...entities];
|
||||||
|
cloneEntities.splice(indexToRemove, 1);
|
||||||
|
setEntities(cloneEntities);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle Entity change
|
||||||
|
const entityChange = (value: string | Date, indexOfInput: number, key: string): void => {
|
||||||
|
const cloneEntities = [...entities];
|
||||||
|
if (key === "property") {
|
||||||
|
cloneEntities[indexOfInput].property = value.toString();
|
||||||
|
} else if (key === "time") {
|
||||||
|
cloneEntities[indexOfInput].entityTimeValue = value.toString();
|
||||||
|
} else {
|
||||||
|
cloneEntities[indexOfInput].value = value.toString();
|
||||||
|
}
|
||||||
|
setEntities(cloneEntities);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle Entity type
|
||||||
|
const entityTypeChange = (
|
||||||
|
_event: React.FormEvent<HTMLDivElement>,
|
||||||
|
selectedType: IDropdownOption,
|
||||||
|
indexOfEntity: number
|
||||||
|
): void => {
|
||||||
|
const entityValuePlaceholder = getEntityValuePlaceholder(selectedType.key);
|
||||||
|
const cloneEntities = [...entities];
|
||||||
|
cloneEntities[indexOfEntity].type = selectedType.key.toString();
|
||||||
|
cloneEntities[indexOfEntity].entityValuePlaceholder = entityValuePlaceholder;
|
||||||
|
cloneEntities[indexOfEntity].isEntityTypeDate = selectedType.key === TableConstants.TableType.DateTime;
|
||||||
|
setEntities(cloneEntities);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open edit entity value modal
|
||||||
|
const editEntity = (rowEndex: number): void => {
|
||||||
|
const entityAttribute: EntityRowType = entities[rowEndex] && entities[rowEndex];
|
||||||
|
setEntityAttributeValue(entityAttribute.value);
|
||||||
|
setEntityAttributeProperty(entityAttribute.property);
|
||||||
|
setSelectedRow(rowEndex);
|
||||||
|
setIsEntityValuePanelTrue();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPanelContent = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<form className="panelFormWrapper">
|
||||||
|
<div className="panelFormWrapper">
|
||||||
|
<div className="panelMainContent">
|
||||||
|
{entities.map((entity, index) => {
|
||||||
|
return (
|
||||||
|
<TableEntity
|
||||||
|
key={"" + entity.id + index}
|
||||||
|
isDeleteOptionVisible={entity.isDeleteOptionVisible}
|
||||||
|
entityTypeLabel={index === 0 && dataTypeLabel}
|
||||||
|
entityPropertyLabel={index === 0 && attributeNameLabel}
|
||||||
|
entityValueLabel={index === 0 && attributeValueLabel}
|
||||||
|
options={userContext.apiType === "Cassandra" ? cassandraOptions : options}
|
||||||
|
isPropertyTypeDisable={entity.isPropertyTypeDisable}
|
||||||
|
entityProperty={entity.property}
|
||||||
|
selectedKey={entity.type}
|
||||||
|
entityPropertyPlaceHolder={detailedHelp}
|
||||||
|
entityValuePlaceholder={entity.entityValuePlaceholder}
|
||||||
|
entityValue={entity.value}
|
||||||
|
isEntityTypeDate={entity.isEntityTypeDate}
|
||||||
|
entityTimeValue={entity.entityTimeValue}
|
||||||
|
isEntityValueDisable={entity.isEntityValueDisable}
|
||||||
|
onEditEntity={() => editEntity(index)}
|
||||||
|
onSelectDate={(date: Date) => {
|
||||||
|
entityChange(date, index, "value");
|
||||||
|
}}
|
||||||
|
onDeleteEntity={() => deleteEntityAtIndex(index)}
|
||||||
|
onEntityPropertyChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "property");
|
||||||
|
}}
|
||||||
|
onEntityTypeChange={(event: React.FormEvent<HTMLDivElement>, selectedParam: IDropdownOption) => {
|
||||||
|
entityTypeChange(event, selectedParam, index);
|
||||||
|
}}
|
||||||
|
onEntityValueChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "value");
|
||||||
|
}}
|
||||||
|
onEntityTimeValueChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "time");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{userContext.apiType !== "Cassandra" && (
|
||||||
|
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
||||||
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
||||||
|
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{renderPanelFooter()}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPanelFooter = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="paneFooter">
|
||||||
|
<div className="leftpanel-okbut">
|
||||||
|
<input type="submit" onClick={submit} className="genericPaneSubmitBtn" value="Update Entity" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRenderNavigationContent: IRenderFunction<IPanelProps> = () => (
|
||||||
|
<Stack horizontal {...columnProps}>
|
||||||
|
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
||||||
|
<Label>{entityAttributeProperty}</Label>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
if (isEntityValuePanelOpen) {
|
||||||
|
return (
|
||||||
|
<PanelContainerComponent
|
||||||
|
headerText=""
|
||||||
|
onRenderNavigationContent={onRenderNavigationContent}
|
||||||
|
panelWidth="700px"
|
||||||
|
isOpen={true}
|
||||||
|
panelContent={
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
rows={5}
|
||||||
|
className="entityValueTextField"
|
||||||
|
value={entityAttributeValue}
|
||||||
|
onChange={(event, newInput?: string) => {
|
||||||
|
setEntityAttributeValue(newInput);
|
||||||
|
entityChange(newInput, selectedRow, "value");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
closePanel={() => closePanel()}
|
||||||
|
isConsoleExpanded={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelContainerComponent
|
||||||
|
headerText="Edit Table Entity"
|
||||||
|
panelWidth="700px"
|
||||||
|
isOpen={true}
|
||||||
|
panelContent={renderPanelContent()}
|
||||||
|
closePanel={() => closePanel()}
|
||||||
|
isConsoleExpanded={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,28 +5,6 @@ import * as Entities from "../../../Tables/Entities";
|
|||||||
import * as Utilities from "../../../Tables/Utilities";
|
import * as Utilities from "../../../Tables/Utilities";
|
||||||
|
|
||||||
export const defaultStringPlaceHolder = "Enter identifier value.";
|
export const defaultStringPlaceHolder = "Enter identifier value.";
|
||||||
export const defaultEntities = [
|
|
||||||
{
|
|
||||||
property: "PartitionKey",
|
|
||||||
type: "String",
|
|
||||||
value: "",
|
|
||||||
isPropertyTypeDisable: true,
|
|
||||||
isDeleteOptionVisible: false,
|
|
||||||
id: 1,
|
|
||||||
entityValuePlaceholder: defaultStringPlaceHolder,
|
|
||||||
isEntityTypeDate: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: "RowKey",
|
|
||||||
type: "String",
|
|
||||||
value: "",
|
|
||||||
isPropertyTypeDisable: true,
|
|
||||||
isDeleteOptionVisible: false,
|
|
||||||
id: 2,
|
|
||||||
entityValuePlaceholder: defaultStringPlaceHolder,
|
|
||||||
isEntityTypeDate: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Dropdown options
|
// Dropdown options
|
||||||
const { String, Boolean, Binary, DateTime, Double, Guid, Int32, Int64 } = TableConstants.TableType;
|
const { String, Boolean, Binary, DateTime, Double, Guid, Int32, Int64 } = TableConstants.TableType;
|
||||||
@@ -133,20 +111,21 @@ export const entityFromAttributes = (entities: EntityRowType[]): Entities.ITable
|
|||||||
|
|
||||||
// GetPlaceholder according to entity type
|
// GetPlaceholder according to entity type
|
||||||
export const getEntityValuePlaceholder = (entityType: string | number): string => {
|
export const getEntityValuePlaceholder = (entityType: string | number): string => {
|
||||||
|
const { String, Boolean, DateTime, Double, Guid, Int32, Int64 } = TableConstants.TableType;
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case "String":
|
case String:
|
||||||
return stringPlaceholder;
|
return stringPlaceholder;
|
||||||
case "Boolean":
|
case Boolean:
|
||||||
return booleanPlaceHolder;
|
return booleanPlaceHolder;
|
||||||
case "DateTime":
|
case DateTime:
|
||||||
return datePlaceholder;
|
return datePlaceholder;
|
||||||
case "Double":
|
case Double:
|
||||||
return doublePlaceholder;
|
return doublePlaceholder;
|
||||||
case "Guid":
|
case Guid:
|
||||||
return guidPlaceholder;
|
return guidPlaceholder;
|
||||||
case "Int32":
|
case Int32:
|
||||||
return intPlaceholder;
|
return intPlaceholder;
|
||||||
case "Int64":
|
case Int64:
|
||||||
return int64Placeholder;
|
return int64Placeholder;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
@@ -164,13 +143,13 @@ export const isValidEntities = (entities: EntityRowType[]): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isEntityPropertyTypeDisable = (header: string): boolean => {
|
const isEntityPropertyTypeDisable = (header: string): boolean => {
|
||||||
if (header === "PartitionKey" || header === "RowKey") {
|
if (header === TableConstants.EntityKeyNames.PartitionKey || header === TableConstants.EntityKeyNames.RowKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultEntities = (headers: string[], entityTypes: { [key: string]: string }): EntityRowType[] => {
|
export const getDefaultEntities = (headers: string[], entityTypes: EntityType): EntityRowType[] => {
|
||||||
const defaultEntities: EntityRowType[] = [];
|
const defaultEntities: EntityRowType[] = [];
|
||||||
headers.forEach((header: string) => {
|
headers.forEach((header: string) => {
|
||||||
if (header !== "Timestamp") {
|
if (header !== "Timestamp") {
|
||||||
@@ -183,7 +162,7 @@ export const getDefaultEntities = (headers: string[], entityTypes: { [key: strin
|
|||||||
isDeleteOptionVisible: !isEntityPropertyTypeDisable(header),
|
isDeleteOptionVisible: !isEntityPropertyTypeDisable(header),
|
||||||
id: 1,
|
id: 1,
|
||||||
entityValuePlaceholder: defaultStringPlaceHolder,
|
entityValuePlaceholder: defaultStringPlaceHolder,
|
||||||
isEntityTypeDate: entityType === "DateTime",
|
isEntityTypeDate: entityType === TableConstants.TableType.DateTime,
|
||||||
};
|
};
|
||||||
defaultEntities.push(entityRow);
|
defaultEntities.push(entityRow);
|
||||||
}
|
}
|
||||||
@@ -212,6 +191,13 @@ export const getButtonLabel = (apiType: string): string => {
|
|||||||
return "Add Entity";
|
return "Add Entity";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFormattedTime = (displayValue: string): string => {
|
||||||
|
const date = new Date(displayValue);
|
||||||
|
const minutes = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes();
|
||||||
|
const hours = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours();
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const getCassandraDefaultEntities = (
|
export const getCassandraDefaultEntities = (
|
||||||
headers: string[],
|
headers: string[],
|
||||||
entityTypes: { [key: string]: string }
|
entityTypes: { [key: string]: string }
|
||||||
@@ -244,4 +230,9 @@ export interface EntityRowType {
|
|||||||
entityValuePlaceholder: string;
|
entityValuePlaceholder: string;
|
||||||
isEntityTypeDate: boolean;
|
isEntityTypeDate: boolean;
|
||||||
entityTimeValue?: string;
|
entityTimeValue?: string;
|
||||||
|
isEntityValueDisable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityType {
|
||||||
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -153,40 +153,6 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -209,9 +175,7 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -234,20 +198,6 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -402,9 +352,7 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -455,40 +403,6 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -520,7 +434,6 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
@@ -552,21 +465,9 @@ exports[`Upload Items Pane should render Default properly 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 {
|
||||||
@@ -592,20 +493,6 @@ exports[`Upload Items Pane should render Default properly 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],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -237,7 +237,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
title: "New SQL Query",
|
title: "New SQL Query",
|
||||||
description: null,
|
description: null,
|
||||||
});
|
});
|
||||||
} else if (this.container.isPreferredApiMongoDB()) {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: NewQueryIcon,
|
iconSrc: NewQueryIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ export default class TableCommands {
|
|||||||
var entityToUpdate: Entities.ITableEntity = viewModel.selected()[0];
|
var entityToUpdate: Entities.ITableEntity = viewModel.selected()[0];
|
||||||
var originalNumberOfProperties = entityToUpdate ? 0 : Object.keys(entityToUpdate).length - 1; // .metadata is always a property for etag
|
var originalNumberOfProperties = entityToUpdate ? 0 : Object.keys(entityToUpdate).length - 1; // .metadata is always a property for etag
|
||||||
|
|
||||||
this._container.editTableEntityPane.originEntity = entityToUpdate;
|
|
||||||
this._container.editTableEntityPane.tableViewModel = viewModel;
|
|
||||||
this._container.editTableEntityPane.originalNumberOfProperties = originalNumberOfProperties;
|
|
||||||
this._container.editTableEntityPane.open();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import DocumentsTab from "./DocumentsTab";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import DocumentsTab from "./DocumentsTab";
|
||||||
|
|
||||||
describe("Documents tab", () => {
|
describe("Documents tab", () => {
|
||||||
describe("buildQuery", () => {
|
describe("buildQuery", () => {
|
||||||
@@ -25,7 +26,6 @@ describe("Documents tab", () => {
|
|||||||
|
|
||||||
describe("showPartitionKey", () => {
|
describe("showPartitionKey", () => {
|
||||||
const explorer = new Explorer();
|
const explorer = new Explorer();
|
||||||
|
|
||||||
const mongoExplorer = new Explorer();
|
const mongoExplorer = new Explorer();
|
||||||
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
||||||
|
|
||||||
@@ -124,6 +124,9 @@ describe("Documents tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be false for Mongo accounts with system partitionKey", () => {
|
it("should be false for Mongo accounts with system partitionKey", () => {
|
||||||
|
updateUserContext({
|
||||||
|
apiType: "Mongo",
|
||||||
|
});
|
||||||
const documentsTab = new DocumentsTab({
|
const documentsTab = new DocumentsTab({
|
||||||
collection: mongoCollectionWithSystemPartitionKey,
|
collection: mongoCollectionWithSystemPartitionKey,
|
||||||
partitionKey: null,
|
partitionKey: null,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
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 { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import * as QueryUtils from "../../Utils/QueryUtils";
|
import * as QueryUtils from "../../Utils/QueryUtils";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
@@ -71,9 +72,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
|
|
||||||
constructor(options: ViewModels.DocumentsTabOptions) {
|
constructor(options: ViewModels.DocumentsTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.isPreferredApiMongoDB = !!this.collection
|
this.isPreferredApiMongoDB = userContext.apiType === "Mongo" || options.isPreferredApiMongoDB;
|
||||||
? this.collection.container.isPreferredApiMongoDB()
|
|
||||||
: options.isPreferredApiMongoDB;
|
|
||||||
|
|
||||||
this.idHeader = this.isPreferredApiMongoDB ? "_id" : "id";
|
this.idHeader = this.isPreferredApiMongoDB ? "_id" : "id";
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import React from "react";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import type { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { IGalleryItem, JunoClient } from "../../Juno/JunoClient";
|
import type { TabOptions } from "../../Contracts/ViewModels";
|
||||||
import { GalleryAndNotebookViewerComponentProps } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
|
import type { IGalleryItem, JunoClient } from "../../Juno/JunoClient";
|
||||||
import { GalleryAndNotebookViewerComponentAdapter } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponentAdapter";
|
import { GalleryAndNotebookViewerComponent as GalleryViewer } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
|
||||||
import { GalleryTab as GalleryViewerTab, SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent";
|
import type { GalleryTab as GalleryViewerTab } from "../Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../Explorer";
|
import { SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
|
import type Explorer from "../Explorer";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export interface GalleryTabOptions extends ViewModels.TabOptions {
|
interface Props {
|
||||||
account: DatabaseAccount;
|
account: DatabaseAccount;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
@@ -17,51 +18,16 @@ export interface GalleryTabOptions extends ViewModels.TabOptions {
|
|||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Notebook gallery tab
|
|
||||||
*/
|
|
||||||
export default class GalleryTab extends TabsBase {
|
export default class GalleryTab extends TabsBase {
|
||||||
public readonly html = '<div style="height: 100%" data-bind="react:galleryAndNotebookViewerComponentAdapter"></div>';
|
constructor(options: TabOptions, private props: Props) {
|
||||||
private container: Explorer;
|
|
||||||
private galleryAndNotebookViewerComponentProps: GalleryAndNotebookViewerComponentProps;
|
|
||||||
public galleryAndNotebookViewerComponentAdapter: GalleryAndNotebookViewerComponentAdapter;
|
|
||||||
|
|
||||||
constructor(options: GalleryTabOptions) {
|
|
||||||
super(options);
|
super(options);
|
||||||
this.container = options.container;
|
|
||||||
|
|
||||||
this.galleryAndNotebookViewerComponentProps = {
|
|
||||||
container: options.container,
|
|
||||||
junoClient: options.junoClient,
|
|
||||||
notebookUrl: options.notebookUrl,
|
|
||||||
galleryItem: options.galleryItem,
|
|
||||||
isFavorite: options.isFavorite,
|
|
||||||
selectedTab: options.selectedTab,
|
|
||||||
sortBy: SortBy.MostRecent,
|
|
||||||
searchText: undefined,
|
|
||||||
};
|
|
||||||
this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(
|
|
||||||
this.galleryAndNotebookViewerComponentProps
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(options: GalleryTabOptions) {
|
public render() {
|
||||||
this.container = options.container;
|
return <GalleryViewer {...this.props} sortBy={SortBy.MostRecent} searchText={undefined} />;
|
||||||
|
|
||||||
this.galleryAndNotebookViewerComponentProps.container = options.container;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.junoClient = options.junoClient;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.notebookUrl = options.notebookUrl;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.galleryItem = options.galleryItem;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.isFavorite = options.isFavorite;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.selectedTab = options.selectedTab;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.sortBy = SortBy.MostViewed;
|
|
||||||
this.galleryAndNotebookViewerComponentProps.searchText = undefined;
|
|
||||||
|
|
||||||
this.galleryAndNotebookViewerComponentAdapter.reset();
|
|
||||||
this.galleryAndNotebookViewerComponentAdapter.triggerRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.container;
|
return this.props.container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
|
||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -17,11 +16,11 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
public url: ko.Computed<string>;
|
public url: ko.Computed<string>;
|
||||||
private _container: Explorer;
|
private _container: Explorer;
|
||||||
private _runtimeEndpoint: string;
|
private _runtimeEndpoint: string;
|
||||||
private _logTraces: HashMap<number>;
|
private _logTraces: Map<string, number>;
|
||||||
|
|
||||||
constructor(options: ViewModels.TabOptions) {
|
constructor(options: ViewModels.TabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this._logTraces = new HashMap<number>();
|
this._logTraces = new Map();
|
||||||
this._container = options.collection.container;
|
this._container = options.collection.container;
|
||||||
this.url = ko.computed<string>(() => {
|
this.url = ko.computed<string>(() => {
|
||||||
const account = userContext.databaseAccount;
|
const account = userContext.databaseAccount;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
|
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||||
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
|
||||||
@@ -47,7 +46,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
public isPreferredApiMongoDB: boolean;
|
public isPreferredApiMongoDB: boolean;
|
||||||
|
|
||||||
public queryMetrics: ko.Observable<HashMap<DataModels.QueryMetrics>>;
|
public queryMetrics: ko.Observable<Map<string, DataModels.QueryMetrics>>;
|
||||||
public aggregatedQueryMetrics: ko.Observable<DataModels.QueryMetrics>;
|
public aggregatedQueryMetrics: ko.Observable<DataModels.QueryMetrics>;
|
||||||
public activityId: ko.Observable<string>;
|
public activityId: ko.Observable<string>;
|
||||||
public roundTrips: ko.Observable<number>;
|
public roundTrips: ko.Observable<number>;
|
||||||
@@ -91,10 +90,8 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this.isPreferredApiMongoDB = false;
|
this.isPreferredApiMongoDB = false;
|
||||||
this.aggregatedQueryMetrics = ko.observable<DataModels.QueryMetrics>();
|
this.aggregatedQueryMetrics = ko.observable<DataModels.QueryMetrics>();
|
||||||
this._resetAggregateQueryMetrics();
|
this._resetAggregateQueryMetrics();
|
||||||
this.queryMetrics = ko.observable<HashMap<DataModels.QueryMetrics>>(new HashMap<DataModels.QueryMetrics>());
|
this.queryMetrics = ko.observable<Map<string, DataModels.QueryMetrics>>(new Map());
|
||||||
this.queryMetrics.subscribe((metrics: HashMap<DataModels.QueryMetrics>) =>
|
this.queryMetrics.subscribe((metrics) => this.aggregatedQueryMetrics(this._aggregateQueryMetrics(metrics)));
|
||||||
this.aggregatedQueryMetrics(this._aggregateQueryMetrics(metrics))
|
|
||||||
);
|
|
||||||
this.isQueryMetricsEnabled = ko.computed<boolean>(() => {
|
this.isQueryMetricsEnabled = ko.computed<boolean>(() => {
|
||||||
return userContext.apiType === "SQL" || false;
|
return userContext.apiType === "SQL" || false;
|
||||||
});
|
});
|
||||||
@@ -364,13 +361,13 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this.queryMetrics.valueHasMutated();
|
this.queryMetrics.valueHasMutated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _aggregateQueryMetrics(metricsMap: HashMap<DataModels.QueryMetrics>): DataModels.QueryMetrics {
|
private _aggregateQueryMetrics(metricsMap: Map<string, DataModels.QueryMetrics>): DataModels.QueryMetrics {
|
||||||
if (!metricsMap) {
|
if (!metricsMap) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const aggregatedMetrics: DataModels.QueryMetrics = this.aggregatedQueryMetrics();
|
const aggregatedMetrics: DataModels.QueryMetrics = this.aggregatedQueryMetrics();
|
||||||
metricsMap.forEach((partitionKeyRangeId: string, queryMetrics: DataModels.QueryMetrics) => {
|
metricsMap.forEach((queryMetrics) => {
|
||||||
if (queryMetrics) {
|
if (queryMetrics) {
|
||||||
aggregatedMetrics.documentLoadTime =
|
aggregatedMetrics.documentLoadTime =
|
||||||
queryMetrics.documentLoadTime &&
|
queryMetrics.documentLoadTime &&
|
||||||
@@ -510,7 +507,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryMetrics: HashMap<DataModels.QueryMetrics> = this.queryMetrics();
|
const queryMetrics = this.queryMetrics();
|
||||||
let csvData: string = "";
|
let csvData: string = "";
|
||||||
const columnHeaders: string =
|
const columnHeaders: string =
|
||||||
[
|
[
|
||||||
@@ -528,7 +525,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
"Document write time (ms)",
|
"Document write time (ms)",
|
||||||
].join(",") + "\n";
|
].join(",") + "\n";
|
||||||
csvData = csvData + columnHeaders;
|
csvData = csvData + columnHeaders;
|
||||||
queryMetrics.forEach((partitionKeyRangeId: string, queryMetric: DataModels.QueryMetrics) => {
|
queryMetrics.forEach((queryMetric, partitionKeyRangeId) => {
|
||||||
const partitionKeyRangeData: string =
|
const partitionKeyRangeData: string =
|
||||||
[
|
[
|
||||||
partitionKeyRangeId,
|
partitionKeyRangeId,
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onEditEntityClick = (): Q.Promise<any> => {
|
public onEditEntityClick = (): Q.Promise<any> => {
|
||||||
this.tableCommands.editEntityCommand(this.tableEntityListViewModel());
|
this.container.openEditTableEntityPanel(this, this.tableEntityListViewModel());
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as monaco from "monaco-editor";
|
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
@@ -8,6 +7,7 @@ import editable from "../../Common/EditableUtility";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import { loadMonaco, monaco } from "../LazyMonaco";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export default abstract class ScriptTabBase extends TabsBase implements ViewModels.WaitsForTemplate {
|
export default abstract class ScriptTabBase extends TabsBase implements ViewModels.WaitsForTemplate {
|
||||||
@@ -299,38 +299,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
return !!value;
|
return !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _toSeverity(severity: string): monaco.MarkerSeverity {
|
protected async _createBodyEditor() {
|
||||||
switch (severity.toLowerCase()) {
|
|
||||||
case "error":
|
|
||||||
return monaco.MarkerSeverity.Error;
|
|
||||||
case "warning":
|
|
||||||
return monaco.MarkerSeverity.Warning;
|
|
||||||
case "info":
|
|
||||||
return monaco.MarkerSeverity.Info;
|
|
||||||
case "ignore":
|
|
||||||
default:
|
|
||||||
return monaco.MarkerSeverity.Hint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _toEditorPosition(target: number, lines: string[]): ViewModels.EditorPosition {
|
|
||||||
let cursor: number = 0;
|
|
||||||
let previousCursor: number = 0;
|
|
||||||
let i: number = 0;
|
|
||||||
while (target > cursor + lines[i].length) {
|
|
||||||
cursor += lines[i].length + 2;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const editorPosition: ViewModels.EditorPosition = {
|
|
||||||
line: i + 1,
|
|
||||||
column: target - cursor + 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
return editorPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _createBodyEditor() {
|
|
||||||
const id = this.editorId;
|
const id = this.editorId;
|
||||||
const container = document.getElementById(id);
|
const container = document.getElementById(id);
|
||||||
const options = {
|
const options = {
|
||||||
@@ -341,7 +310,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
};
|
};
|
||||||
|
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
|
const monaco = await loadMonaco();
|
||||||
const editor = monaco.editor.create(container, options);
|
const editor = monaco.editor.create(container, options);
|
||||||
this.editor(editor);
|
this.editor(editor);
|
||||||
|
|
||||||
@@ -353,32 +322,4 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
const editorModel = this.editor().getModel();
|
const editorModel = this.editor().getModel();
|
||||||
this.editorContent(editorModel.getValue());
|
this.editorContent(editorModel.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setModelMarkers(errors: ViewModels.QueryError[]) {
|
|
||||||
const markers: monaco.editor.IMarkerData[] = errors.map((e) => this._toMarker(e));
|
|
||||||
const editorModel = this.editor().getModel();
|
|
||||||
monaco.editor.setModelMarkers(editorModel, this.tabId, markers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resetModelMarkers() {
|
|
||||||
const queryEditorModel = this.editor().getModel();
|
|
||||||
monaco.editor.setModelMarkers(queryEditorModel, this.tabId, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _toMarker(error: ViewModels.QueryError): monaco.editor.IMarkerData {
|
|
||||||
const editorModel = this.editor().getModel();
|
|
||||||
const lines: string[] = editorModel.getLinesContent();
|
|
||||||
const start: ViewModels.EditorPosition = ScriptTabBase._toEditorPosition(Number(error.start), lines);
|
|
||||||
const end: ViewModels.EditorPosition = ScriptTabBase._toEditorPosition(Number(error.end), lines);
|
|
||||||
|
|
||||||
return {
|
|
||||||
severity: ScriptTabBase._toSeverity(error.severity),
|
|
||||||
message: error.message,
|
|
||||||
startLineNumber: start.line,
|
|
||||||
startColumn: start.column,
|
|
||||||
endLineNumber: end.line,
|
|
||||||
endColumn: end.column,
|
|
||||||
code: error.code,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab
|
|||||||
<div id="content" className="flexContainer hideOverflows">
|
<div id="content" className="flexContainer hideOverflows">
|
||||||
<div className="nav-tabs-margin">
|
<div className="nav-tabs-margin">
|
||||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
{...tabs.map((tab) => <TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />)}
|
{tabs.map((tab) => (
|
||||||
|
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="tabPanesContainer">
|
<div className="tabPanesContainer">
|
||||||
{...tabs.map((tab) => <TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />)}
|
{tabs.map((tab) => (
|
||||||
|
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class AccessibleVerticalList {
|
|||||||
this.onSelect = onSelect;
|
this.onSelect = onSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onKeyDown = (_src: unknown, event: KeyboardEvent): boolean => {
|
||||||
const targetContainer: Element = <Element>event.target;
|
const targetContainer: Element = <Element>event.target;
|
||||||
if (this.items == null || this.items.length === 0) {
|
if (this.items == null || this.items.length === 0) {
|
||||||
// no items so this should be a noop
|
// no items so this should be a noop
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ describe("Collection", () => {
|
|||||||
|
|
||||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
||||||
const mockContainer = {} as Explorer;
|
const mockContainer = {} as Explorer;
|
||||||
mockContainer.isPreferredApiMongoDB = ko.computed(() => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockContainer.isDatabaseNodeOrNoneSelected = () => {
|
mockContainer.isDatabaseNodeOrNoneSelected = () => {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -127,16 +127,12 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.partitionKey.paths[0]) ||
|
this.partitionKey.paths[0]) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
if (!!container.isPreferredApiMongoDB() && this.partitionKeyProperty && ~this.partitionKeyProperty.indexOf(`"`)) {
|
if (userContext.apiType === "Mongo" && this.partitionKeyProperty && ~this.partitionKeyProperty.indexOf(`"`)) {
|
||||||
this.partitionKeyProperty = this.partitionKeyProperty.replace(/["]+/g, "");
|
this.partitionKeyProperty = this.partitionKeyProperty.replace(/["]+/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO #10738269 : Add this logic in a derived class for Mongo
|
// TODO #10738269 : Add this logic in a derived class for Mongo
|
||||||
if (
|
if (userContext.apiType === "Mongo" && this.partitionKeyProperty && this.partitionKeyProperty.indexOf("$v") > -1) {
|
||||||
!!container.isPreferredApiMongoDB() &&
|
|
||||||
this.partitionKeyProperty &&
|
|
||||||
this.partitionKeyProperty.indexOf("$v") > -1
|
|
||||||
) {
|
|
||||||
// From $v.shard.$v.key.$v > shard.key
|
// From $v.shard.$v.key.$v > shard.key
|
||||||
this.partitionKeyProperty = this.partitionKeyProperty.replace(/.\$v/g, "").replace(/\$v./g, "");
|
this.partitionKeyProperty = this.partitionKeyProperty.replace(/.\$v/g, "").replace(/\$v./g, "");
|
||||||
this.partitionKeyPropertyHeader = "/" + this.partitionKeyProperty;
|
this.partitionKeyPropertyHeader = "/" + this.partitionKeyProperty;
|
||||||
@@ -1123,7 +1119,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else if (userContext.apiType === "Gremlin") {
|
} else if (userContext.apiType === "Gremlin") {
|
||||||
this.onGraphDocumentsClick();
|
this.onGraphDocumentsClick();
|
||||||
return;
|
return;
|
||||||
} else if (this.container.isPreferredApiMongoDB()) {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
this.onMongoDBDocumentsClick();
|
this.onMongoDBDocumentsClick();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1141,7 +1137,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
return "Rows";
|
return "Rows";
|
||||||
} else if (userContext.apiType === "Gremlin") {
|
} else if (userContext.apiType === "Gremlin") {
|
||||||
return "Graph";
|
return "Graph";
|
||||||
} else if (this.container.isPreferredApiMongoDB()) {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
return "Documents";
|
return "Documents";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -761,7 +761,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
{
|
{
|
||||||
label: "Rename",
|
label: "Rename",
|
||||||
iconSrc: NotebookIcon,
|
iconSrc: NotebookIcon,
|
||||||
onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender()),
|
onClick: () => this.container.renameNotebook(item),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "New Directory",
|
label: "New Directory",
|
||||||
@@ -930,7 +930,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private cleanupDatabasesKoSubs(): void {
|
private cleanupDatabasesKoSubs(): void {
|
||||||
this.koSubsDatabaseIdMap.keys().forEach((databaseId: string) => {
|
for (const databaseId of this.koSubsDatabaseIdMap.keys()) {
|
||||||
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||||
this.koSubsDatabaseIdMap.delete(databaseId);
|
this.koSubsDatabaseIdMap.delete(databaseId);
|
||||||
|
|
||||||
@@ -939,7 +939,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
.get(databaseId)
|
.get(databaseId)
|
||||||
.forEach((collectionId: string) => this.cleanupKoSubsForCollection(databaseId, collectionId));
|
.forEach((collectionId: string) => this.cleanupKoSubsForCollection(databaseId, collectionId));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupCollectionsKoSubs(databaseId: string, existingCollectionIds: string[]): void {
|
private cleanupCollectionsKoSubs(databaseId: string, existingCollectionIds: string[]): void {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import postRobot from "post-robot";
|
||||||
|
|
||||||
export interface IGitHubConnectorParams {
|
export interface IGitHubConnectorParams {
|
||||||
state: string;
|
state: string;
|
||||||
code: string;
|
code: string;
|
||||||
@@ -6,25 +8,26 @@ export interface IGitHubConnectorParams {
|
|||||||
export const GitHubConnectorMsgType = "GitHubConnectorMsgType";
|
export const GitHubConnectorMsgType = "GitHubConnectorMsgType";
|
||||||
|
|
||||||
export class GitHubConnector {
|
export class GitHubConnector {
|
||||||
public start(params: URLSearchParams, window: Window & typeof globalThis) {
|
public async start(params: URLSearchParams, window: Window & typeof globalThis): Promise<void> {
|
||||||
window.postMessage(
|
await postRobot.send(
|
||||||
|
window,
|
||||||
|
GitHubConnectorMsgType,
|
||||||
{
|
{
|
||||||
type: GitHubConnectorMsgType,
|
state: params.get("state"),
|
||||||
data: {
|
code: params.get("code"),
|
||||||
state: params.get("state"),
|
} as IGitHubConnectorParams,
|
||||||
code: params.get("code"),
|
{
|
||||||
} as IGitHubConnectorParams,
|
domain: window.location.origin,
|
||||||
},
|
}
|
||||||
window.location.origin
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var connector = new GitHubConnector();
|
var connector = new GitHubConnector();
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", async () => {
|
||||||
const openerWindow = window.opener;
|
const openerWindow = window.opener;
|
||||||
if (openerWindow) {
|
if (openerWindow) {
|
||||||
connector.start(new URLSearchParams(document.location.search), openerWindow);
|
await connector.start(new URLSearchParams(document.location.search), openerWindow);
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { JunoClient } from "../Juno/JunoClient";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { GitHubConnector, IGitHubConnectorParams } from "./GitHubConnector";
|
|
||||||
import { GitHubOAuthService } from "./GitHubOAuthService";
|
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import NotebookManager from "../Explorer/Notebook/NotebookManager";
|
import NotebookManager from "../Explorer/Notebook/NotebookManager";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import { JunoClient } from "../Juno/JunoClient";
|
||||||
|
import { IGitHubConnectorParams } from "./GitHubConnector";
|
||||||
|
import { GitHubOAuthService } from "./GitHubOAuthService";
|
||||||
|
|
||||||
const sampleDatabaseAccount: DataModels.DatabaseAccount = {
|
const sampleDatabaseAccount: DataModels.DatabaseAccount = {
|
||||||
id: "id",
|
id: "id",
|
||||||
@@ -85,25 +85,6 @@ describe("GitHubOAuthService", () => {
|
|||||||
expect(newParams.get("state")).not.toEqual(initialParams.get("state"));
|
expect(newParams.get("state")).not.toEqual(initialParams.get("state"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("finishOAuth is called whenever GitHubConnector is started", async () => {
|
|
||||||
const finishOAuthCallback = jest.fn().mockImplementation();
|
|
||||||
gitHubOAuthService.finishOAuth = finishOAuthCallback;
|
|
||||||
|
|
||||||
const params: IGitHubConnectorParams = {
|
|
||||||
state: "state",
|
|
||||||
code: "code",
|
|
||||||
};
|
|
||||||
const searchParams = new URLSearchParams({ ...params });
|
|
||||||
|
|
||||||
const gitHubConnector = new GitHubConnector();
|
|
||||||
gitHubConnector.start(searchParams, window);
|
|
||||||
|
|
||||||
// GitHubConnector uses Window.postMessage and there's no good way to know when the message has received
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
||||||
|
|
||||||
expect(finishOAuthCallback).toBeCalledWith(params);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("finishOAuth updates token", async () => {
|
it("finishOAuth updates token", async () => {
|
||||||
const data = { key: "value" };
|
const data = { key: "value" };
|
||||||
const getGitHubTokenCallback = jest.fn().mockReturnValue({ status: HttpStatusCodes.OK, data });
|
const getGitHubTokenCallback = jest.fn().mockReturnValue({ status: HttpStatusCodes.OK, data });
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
import postRobot from "post-robot";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import { handleError } from "../Common/ErrorHandlingUtils";
|
import { handleError } from "../Common/ErrorHandlingUtils";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
||||||
import { JunoClient } from "../Juno/JunoClient";
|
import { JunoClient } from "../Juno/JunoClient";
|
||||||
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
|
||||||
import { logConsoleInfo } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo } from "../Utils/NotificationConsoleUtils";
|
||||||
import { GitHubConnectorMsgType, IGitHubConnectorParams } from "./GitHubConnector";
|
import { GitHubConnectorMsgType, IGitHubConnectorParams } from "./GitHubConnector";
|
||||||
|
|
||||||
window.addEventListener("message", (event: MessageEvent) => {
|
postRobot.on(
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
GitHubConnectorMsgType,
|
||||||
return;
|
{
|
||||||
}
|
domain: window.location.origin,
|
||||||
|
},
|
||||||
const msg = event.data;
|
(event) => {
|
||||||
if (msg.type === GitHubConnectorMsgType) {
|
// Typescript definition for event is wrong. So read params by casting to <any>
|
||||||
const params = msg.data as IGitHubConnectorParams;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const params = (event as any).data as IGitHubConnectorParams;
|
||||||
window.dataExplorer.notebookManager?.gitHubOAuthService.finishOAuth(params);
|
window.dataExplorer.notebookManager?.gitHubOAuthService.finishOAuth(params);
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export interface IGitHubOAuthToken {
|
export interface IGitHubOAuthToken {
|
||||||
// API properties
|
// API properties
|
||||||
|
|||||||
@@ -230,15 +230,10 @@ const App: React.FunctionComponent = () => {
|
|||||||
<div data-bind='component: { name: "add-database-pane", params: {data: addDatabasePane} }' />
|
<div data-bind='component: { name: "add-database-pane", params: {data: addDatabasePane} }' />
|
||||||
<div data-bind='component: { name: "add-collection-pane", params: { data: addCollectionPane} }' />
|
<div data-bind='component: { name: "add-collection-pane", params: { data: addCollectionPane} }' />
|
||||||
<div data-bind='component: { name: "graph-styling-pane", params: { data: graphStylingPane} }' />
|
<div data-bind='component: { name: "graph-styling-pane", params: { data: graphStylingPane} }' />
|
||||||
<div data-bind='component: { name: "table-edit-entity-pane", params: { data: editTableEntityPane} }' />
|
|
||||||
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
||||||
<div data-bind='component: { name: "string-input-pane", params: { data: stringInputPane} }' />
|
|
||||||
<KOCommentIfStart if="isGitHubPaneEnabled" />
|
<KOCommentIfStart if="isGitHubPaneEnabled" />
|
||||||
<div data-bind='component: { name: "github-repos-pane", params: { data: gitHubReposPane } }' />
|
<div data-bind='component: { name: "github-repos-pane", params: { data: gitHubReposPane } }' />
|
||||||
<KOCommentEnd />
|
<KOCommentEnd />
|
||||||
<KOCommentIfStart if="isPublishNotebookPaneEnabled" />
|
|
||||||
<div data-bind="react: publishNotebookPaneAdapter" />
|
|
||||||
<KOCommentEnd />
|
|
||||||
{showDialog && <Dialog {...dialogProps} />}
|
{showDialog && <Dialog {...dialogProps} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { IResourceProviderClient } from "../ResourceProvider/IResourceProviderCl
|
|||||||
import { NotebookWorkspace } from "../Contracts/DataModels";
|
import { NotebookWorkspace } from "../Contracts/DataModels";
|
||||||
|
|
||||||
export class NotebookWorkspaceSettingsProviderClient implements IResourceProviderClient<string> {
|
export class NotebookWorkspaceSettingsProviderClient implements IResourceProviderClient<string> {
|
||||||
public async deleteAsync(url: string, apiVersion: string): Promise<void> {
|
public async deleteAsync(_url: string, _apiVersion: string): Promise<void> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postAsync(url: string, body: any, apiVersion: string): Promise<any> {
|
public async postAsync(_url: string, _body: any, _apiVersion: string): Promise<any> {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
notebookServerEndpoint: "http://localhost:8888",
|
notebookServerEndpoint: "http://localhost:8888",
|
||||||
username: "",
|
username: "",
|
||||||
@@ -14,37 +14,37 @@ export class NotebookWorkspaceSettingsProviderClient implements IResourceProvide
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAsync(url: string, apiVersion: string): Promise<string> {
|
public async getAsync(): Promise<string> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async putAsync(url: string, body: any, apiVersion: string): Promise<string> {
|
public async putAsync(): Promise<string> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async patchAsync(url: string, apiVersion: string): Promise<string> {
|
public async patchAsync(): Promise<string> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookWorkspaceResourceProviderClient implements IResourceProviderClient<NotebookWorkspace> {
|
export class NotebookWorkspaceResourceProviderClient implements IResourceProviderClient<NotebookWorkspace> {
|
||||||
public async deleteAsync(url: string, apiVersion: string): Promise<void> {
|
public async deleteAsync(): Promise<void> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postAsync(url: string, body: any, apiVersion: string): Promise<NotebookWorkspace> {
|
public async postAsync(): Promise<NotebookWorkspace> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAsync(url: string, apiVersion: string): Promise<NotebookWorkspace | NotebookWorkspace[]> {
|
public async getAsync(): Promise<NotebookWorkspace | NotebookWorkspace[]> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async putAsync(url: string, body: any, apiVersion: string): Promise<NotebookWorkspace> {
|
public async putAsync(): Promise<NotebookWorkspace> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async patchAsync(url: string, body: any, apiVersion: string): Promise<NotebookWorkspace> {
|
public async patchAsync(): Promise<NotebookWorkspace> {
|
||||||
throw new Error("Not yet implemented");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,10 +175,7 @@ export class TabRouteHandler {
|
|||||||
databaseId,
|
databaseId,
|
||||||
collectionId
|
collectionId
|
||||||
);
|
);
|
||||||
collection &&
|
userContext.apiType === "Mongo" && collection.onMongoDBDocumentsClick();
|
||||||
collection.container &&
|
|
||||||
collection.container.isPreferredApiMongoDB() &&
|
|
||||||
collection.onMongoDBDocumentsClick();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,10 +185,7 @@ export class TabRouteHandler {
|
|||||||
databaseId,
|
databaseId,
|
||||||
collectionId
|
collectionId
|
||||||
);
|
);
|
||||||
collection &&
|
collection && userContext.apiType === "Mongo" && collection.onSchemaAnalyzerClick();
|
||||||
collection.container &&
|
|
||||||
collection.container.isPreferredApiMongoDB() &&
|
|
||||||
collection.onSchemaAnalyzerClick();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,10 +222,7 @@ export class TabRouteHandler {
|
|||||||
if (!!matchingTab) {
|
if (!!matchingTab) {
|
||||||
matchingTab.onTabClick();
|
matchingTab.onTabClick();
|
||||||
} else {
|
} else {
|
||||||
collection &&
|
userContext.apiType === "Mongo" && collection.onNewMongoQueryClick(collection, null);
|
||||||
collection.container &&
|
|
||||||
collection.container.isPreferredApiMongoDB() &&
|
|
||||||
collection.onNewMongoQueryClick(collection, null);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -250,10 +241,7 @@ export class TabRouteHandler {
|
|||||||
if (!!matchingTab) {
|
if (!!matchingTab) {
|
||||||
matchingTab.onTabClick();
|
matchingTab.onTabClick();
|
||||||
} else {
|
} else {
|
||||||
collection &&
|
userContext.apiType === "Mongo" && collection.onNewMongoShellClick();
|
||||||
collection.container &&
|
|
||||||
collection.container.isPreferredApiMongoDB() &&
|
|
||||||
collection.onNewMongoShellClick();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { HttpHeaders, TerminalQueryParams } from "../Common/Constants";
|
|||||||
|
|
||||||
const getUrlVars = (): { [key: string]: string } => {
|
const getUrlVars = (): { [key: string]: string } => {
|
||||||
const vars: { [key: string]: string } = {};
|
const vars: { [key: string]: string } = {};
|
||||||
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (m, key, value): string => {
|
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (_m, key, value): string => {
|
||||||
vars[key] = decodeURIComponent(value);
|
vars[key] = decodeURIComponent(value);
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ interface UserContext {
|
|||||||
readonly quotaId?: string;
|
readonly quotaId?: string;
|
||||||
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
|
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
|
||||||
// This is coming in a future Cosmos ARM API version as a prperty on databaseAccount
|
// This is coming in a future Cosmos ARM API version as a prperty on databaseAccount
|
||||||
readonly apiType?: ApiType;
|
apiType?: ApiType;
|
||||||
readonly isTryCosmosDBSubscription?: boolean;
|
readonly isTryCosmosDBSubscription?: boolean;
|
||||||
readonly portalEnv?: PortalEnv;
|
readonly portalEnv?: PortalEnv;
|
||||||
readonly features: Features;
|
readonly features: Features;
|
||||||
@@ -47,8 +47,10 @@ const userContext: UserContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function updateUserContext(newContext: Partial<UserContext>): void {
|
function updateUserContext(newContext: Partial<UserContext>): void {
|
||||||
|
if (newContext.databaseAccount) {
|
||||||
|
newContext.apiType = apiType(newContext.databaseAccount);
|
||||||
|
}
|
||||||
Object.assign(userContext, newContext);
|
Object.assign(userContext, newContext);
|
||||||
Object.assign(userContext, { apiType: apiType(userContext.databaseAccount) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiType(account: DatabaseAccount | undefined): ApiType {
|
function apiType(account: DatabaseAccount | undefined): ApiType {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { decryptJWTToken } from "./AuthorizationUtils";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
import { decryptJWTToken } from "./AuthorizationUtils";
|
||||||
|
|
||||||
export function getFullName(): string {
|
export const getFullName = (): string => {
|
||||||
const authToken = userContext.authorizationToken;
|
const { authorizationToken } = userContext;
|
||||||
const props = decryptJWTToken(authToken);
|
const { name } = decryptJWTToken(authorizationToken);
|
||||||
return props.name;
|
return name;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./src/AuthType.ts",
|
"./src/AuthType.ts",
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
"./src/Common/DeleteFeedback.ts",
|
"./src/Common/DeleteFeedback.ts",
|
||||||
"./src/Common/DocumentUtility.ts",
|
"./src/Common/DocumentUtility.ts",
|
||||||
"./src/Common/EnvironmentUtility.ts",
|
"./src/Common/EnvironmentUtility.ts",
|
||||||
"./src/Common/HashMap.ts",
|
|
||||||
"./src/Common/HeadersUtility.test.ts",
|
"./src/Common/HeadersUtility.test.ts",
|
||||||
"./src/Common/HeadersUtility.ts",
|
"./src/Common/HeadersUtility.ts",
|
||||||
"./src/Common/Logger.ts",
|
"./src/Common/Logger.ts",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
/* eslint-disable no-undef */
|
||||||
require("dotenv/config");
|
require("dotenv/config");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||||
@@ -83,7 +85,8 @@ const typescriptRule = {
|
|||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function (env = {}, argv = {}) {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
module.exports = function (_env = {}, argv = {}) {
|
||||||
const mode = argv.mode || "development";
|
const mode = argv.mode || "development";
|
||||||
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
|
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
|
||||||
const envVars = {
|
const envVars = {
|
||||||
@@ -211,6 +214,7 @@ module.exports = function (env = {}, argv = {}) {
|
|||||||
util: true,
|
util: true,
|
||||||
tls: "empty",
|
tls: "empty",
|
||||||
net: "empty",
|
net: "empty",
|
||||||
|
fs: "empty",
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
chunkFilename: "[name].[chunkhash:6].js",
|
chunkFilename: "[name].[chunkhash:6].js",
|
||||||
@@ -264,7 +268,7 @@ module.exports = function (env = {}, argv = {}) {
|
|||||||
target: "https://main.documentdb.ext.azure.com",
|
target: "https://main.documentdb.ext.azure.com",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
bypass: function (req, res, proxyOptions) {
|
bypass: (req, res) => {
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.send();
|
res.send();
|
||||||
@@ -304,8 +308,5 @@ module.exports = function (env = {}, argv = {}) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
stats: "minimal",
|
stats: "minimal",
|
||||||
node: {
|
|
||||||
fs: "empty",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user