mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-31 07:34:17 +00:00
resolve master merge
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
|
||||||
|
|||||||
64
package-lock.json
generated
64
package-lock.json
generated
@@ -114,12 +114,15 @@
|
|||||||
"@types/d3": "5.9.2",
|
"@types/d3": "5.9.2",
|
||||||
"@types/enzyme": "3.10.7",
|
"@types/enzyme": "3.10.7",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||||
|
"@types/expect-puppeteer": "4.4.5",
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
|
"@types/jest-environment-puppeteer": "4.4.1",
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/promise.prototype.finally": "2.0.3",
|
"@types/promise.prototype.finally": "2.0.3",
|
||||||
|
"@types/puppeteer": "5.4.3",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "17.0.3",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
@@ -168,6 +171,7 @@
|
|||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.1",
|
||||||
"playwright": "1.10.0",
|
"playwright": "1.10.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
|
"process": "0.11.10",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "11.0.4",
|
"react-dev-utils": "11.0.4",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
@@ -5539,6 +5543,16 @@
|
|||||||
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/expect-puppeteer": {
|
||||||
|
"version": "4.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz",
|
||||||
|
"integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jest": "*",
|
||||||
|
"@types/puppeteer": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/geojson": {
|
"node_modules/@types/geojson": {
|
||||||
"version": "7946.0.7",
|
"version": "7946.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
||||||
@@ -5620,6 +5634,17 @@
|
|||||||
"pretty-format": "^26.0.0"
|
"pretty-format": "^26.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jest-environment-puppeteer": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-LiZTD6i63le6QMnxi7pJB0SFv/fWtss6VVEEDm/UaeowBgjduf8txyE//j3WEeDPxngTvioUjbzA7Rc6Wc3cBA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jest/types": ">=24 <=26",
|
||||||
|
"@types/puppeteer": "*",
|
||||||
|
"jest-environment-node": ">=24 <=26"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/jest/node_modules/@jest/types": {
|
"node_modules/@types/jest/node_modules/@jest/types": {
|
||||||
"version": "26.6.2",
|
"version": "26.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||||
@@ -5863,6 +5888,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
|
||||||
"integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw=="
|
"integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/puppeteer": {
|
||||||
|
"version": "5.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz",
|
||||||
|
"integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/q": {
|
"node_modules/@types/q": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz",
|
||||||
@@ -35009,6 +35043,16 @@
|
|||||||
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/expect-puppeteer": {
|
||||||
|
"version": "4.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz",
|
||||||
|
"integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/jest": "*",
|
||||||
|
"@types/puppeteer": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/geojson": {
|
"@types/geojson": {
|
||||||
"version": "7946.0.7",
|
"version": "7946.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
||||||
@@ -35210,6 +35254,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/jest-environment-puppeteer": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-LiZTD6i63le6QMnxi7pJB0SFv/fWtss6VVEEDm/UaeowBgjduf8txyE//j3WEeDPxngTvioUjbzA7Rc6Wc3cBA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jest/types": ">=24 <=26",
|
||||||
|
"@types/puppeteer": "*",
|
||||||
|
"jest-environment-node": ">=24 <=26"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.7",
|
"version": "7.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
||||||
@@ -35303,6 +35358,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
|
||||||
"integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw=="
|
"integrity": "sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw=="
|
||||||
},
|
},
|
||||||
|
"@types/puppeteer": {
|
||||||
|
"version": "5.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz",
|
||||||
|
"integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/q": {
|
"@types/q": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz",
|
||||||
|
|||||||
@@ -111,12 +111,15 @@
|
|||||||
"@types/d3": "5.9.2",
|
"@types/d3": "5.9.2",
|
||||||
"@types/enzyme": "3.10.7",
|
"@types/enzyme": "3.10.7",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||||
|
"@types/expect-puppeteer": "4.4.5",
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
|
"@types/jest-environment-puppeteer": "4.4.1",
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/promise.prototype.finally": "2.0.3",
|
"@types/promise.prototype.finally": "2.0.3",
|
||||||
|
"@types/puppeteer": "5.4.3",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "17.0.3",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
@@ -165,6 +168,7 @@
|
|||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.1",
|
||||||
"playwright": "1.10.0",
|
"playwright": "1.10.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
|
"process": "0.11.10",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "11.0.4",
|
"react-dev-utils": "11.0.4",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
return this.touch(key, value), this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private touch(key: string, value = super.get(key)) {
|
||||||
|
// Map keeps (re) insertion order according to ES6 spec
|
||||||
|
if (value) {
|
||||||
|
this.delete(key);
|
||||||
super.set(key, value);
|
super.set(key, value);
|
||||||
|
|
||||||
this.markKeyAsTouched(key);
|
|
||||||
|
|
||||||
if (super.size() > this.maxNbElements && key !== this.keyQueue[0]) {
|
|
||||||
this.reduceCacheSize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return value;
|
||||||
* Invalidate elements to keep the total number below the limit
|
|
||||||
*/
|
|
||||||
private reduceCacheSize(): void {
|
|
||||||
// remove a key
|
|
||||||
const oldKey = this.keyQueue.shift();
|
|
||||||
if (oldKey) {
|
|
||||||
super.delete(oldKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TooltipHost,
|
TooltipHost,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
||||||
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
this.state = {
|
|
||||||
isDeletingPublishedNotebook: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
|
|
||||||
const options: Intl.DateTimeFormatOptions = {
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
};
|
};
|
||||||
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
|
const dateString = new Date(data.created).toLocaleString("default", options);
|
||||||
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
|
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
|
||||||
|
|
||||||
return (
|
const renderTruncatedDescription = (): string => {
|
||||||
<Card
|
let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
|
||||||
style={{ background: "white" }}
|
if (data.description.length > cardDescriptionMaxChars) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,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],
|
||||||
@@ -378,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],
|
||||||
@@ -462,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],
|
||||||
@@ -494,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 {
|
||||||
@@ -731,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],
|
||||||
@@ -910,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],
|
||||||
@@ -994,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],
|
||||||
@@ -1026,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 {
|
||||||
@@ -1276,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],
|
||||||
@@ -1455,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],
|
||||||
@@ -1539,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],
|
||||||
@@ -1571,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 {
|
||||||
@@ -1808,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],
|
||||||
@@ -1987,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],
|
||||||
@@ -2071,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],
|
||||||
@@ -2103,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 {
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -73,7 +72,6 @@ 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";
|
||||||
@@ -120,11 +118,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
|
||||||
@@ -175,7 +168,6 @@ export default class Explorer {
|
|||||||
public graphStylingPane: GraphStylingPane;
|
public graphStylingPane: GraphStylingPane;
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
||||||
public gitHubReposPane: ContextualPaneBase;
|
public gitHubReposPane: ContextualPaneBase;
|
||||||
public publishNotebookPaneAdapter: ReactAdapter;
|
|
||||||
|
|
||||||
// features
|
// features
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
@@ -412,27 +404,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) {
|
||||||
@@ -1411,7 +1382,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1916,8 +1886,25 @@ 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 galleryTab = this.tabsManager
|
||||||
|
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
||||||
|
.find((tab) => tab.hashLocation() == hashLocation);
|
||||||
|
|
||||||
const galleryTabOptions: GalleryTabOptions = {
|
if (galleryTab instanceof GalleryTab) {
|
||||||
|
this.tabsManager.activateTab(galleryTab);
|
||||||
|
} else {
|
||||||
|
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,
|
account: userContext.databaseAccount,
|
||||||
container: this,
|
container: this,
|
||||||
junoClient: this.notebookManager?.junoClient,
|
junoClient: this.notebookManager?.junoClient,
|
||||||
@@ -1925,24 +1912,9 @@ export default class Explorer {
|
|||||||
notebookUrl,
|
notebookUrl,
|
||||||
galleryItem,
|
galleryItem,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
tabKind: ViewModels.CollectionTabKind.Gallery,
|
}
|
||||||
title: title,
|
)
|
||||||
tabPath: title,
|
);
|
||||||
hashLocation: hashLocation,
|
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
|
||||||
onLoadStartKey: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const galleryTab = this.tabsManager
|
|
||||||
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
|
||||||
.find((tab) => tab.hashLocation() == hashLocation);
|
|
||||||
|
|
||||||
if (galleryTab instanceof GalleryTab) {
|
|
||||||
this.tabsManager.activateTab(galleryTab);
|
|
||||||
galleryTab.reset(galleryTabOptions);
|
|
||||||
} else {
|
|
||||||
this.tabsManager.activateNewTab(new GalleryTab(galleryTabOptions));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,9 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import CancelIcon from "images/cancel.svg";
|
import CancelIcon from "images/cancel.svg";
|
||||||
import CheckIcon from "images/check.svg";
|
import CheckIcon from "images/check-disabled.svg"; // need to revert hardikn
|
||||||
import DeleteIcon from "images/delete.svg";
|
import DeleteIcon from "images/delete.svg";
|
||||||
import EditIcon from "images/edit.svg";
|
import EditIcon from "images/Edit_entity.svg";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||||
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import LoaderIcon from "images/circular_loader_black_16x16.gif";
|
import LoaderIcon from "images/circular_loader_black_16x16.gif";
|
||||||
import ClearIcon from "images/Clear.svg";
|
import ClearIcon from "images/Clear-disabled.svg"; // need to revert hardikn
|
||||||
import ErrorBlackIcon from "images/error_black.svg";
|
import ErrorBlackIcon from "images/error_black.svg";
|
||||||
import ErrorRedIcon from "images/error_red.svg";
|
import ErrorRedIcon from "images/error_red.svg";
|
||||||
import infoBubbleIcon from "images/info-bubble-9x9.svg";
|
import infoBubbleIcon from "images/info-bubble-9x9.svg";
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
DropTargetConnector,
|
DropTargetConnector,
|
||||||
DropTargetMonitor,
|
DropTargetMonitor,
|
||||||
} from "react-dnd";
|
} from "react-dnd";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
import styled, { StyledComponent } from "styled-components";
|
import styled, { StyledComponent } from "styled-components";
|
||||||
|
|||||||
@@ -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
@@ -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,244 +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 {
|
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "./GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
|
||||||
|
|
||||||
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: GenericRightPaneProps = {
|
|
||||||
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 (
|
|
||||||
<GenericRightPaneComponent {...props}>
|
|
||||||
{!this.isCodeOfConductAccepted ? (
|
|
||||||
<div style={{ padding: "15px", marginTop: "10px" }}>
|
|
||||||
<CodeOfConductComponent
|
|
||||||
junoClient={this.junoClient}
|
|
||||||
onAcceptCodeOfConduct={() => {
|
|
||||||
this.isCodeOfConductAccepted = true;
|
|
||||||
this.triggerRender();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<PublishNotebookPaneComponent {...publishNotebookPaneProps} />
|
|
||||||
)}
|
|
||||||
</GenericRightPaneComponent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -175,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],
|
||||||
@@ -354,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],
|
||||||
@@ -438,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],
|
||||||
@@ -470,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 {
|
||||||
@@ -830,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],
|
||||||
@@ -1009,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],
|
||||||
@@ -1093,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],
|
||||||
@@ -1125,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 {
|
||||||
|
|||||||
@@ -178,9 +178,7 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -357,9 +355,7 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -441,7 +437,6 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -473,21 +468,9 @@ exports[`StringInput Pane should render Create new directory 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 {
|
||||||
@@ -709,9 +692,7 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -888,9 +869,7 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -972,7 +951,6 @@ exports[`StringInput Pane should render Create new directory 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],
|
||||||
@@ -1004,21 +982,9 @@ exports[`StringInput Pane should render Create new directory 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 {
|
||||||
|
|||||||
@@ -175,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],
|
||||||
@@ -354,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],
|
||||||
@@ -438,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],
|
||||||
@@ -470,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 {
|
||||||
|
|||||||
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: () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
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";
|
||||||
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 * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import editable from "../../Common/EditableUtility";
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
data: {
|
|
||||||
state: params.get("state"),
|
state: params.get("state"),
|
||||||
code: params.get("code"),
|
code: params.get("code"),
|
||||||
} as IGitHubConnectorParams,
|
} as IGitHubConnectorParams,
|
||||||
},
|
{
|
||||||
window.location.origin
|
domain: 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
|
||||||
|
|||||||
6
src/Libs/jquery.js
vendored
6
src/Libs/jquery.js
vendored
@@ -1,5 +1,5 @@
|
|||||||
import $ from 'jquery';
|
import * as $ from "jquery";
|
||||||
global.$ = $;
|
global.$ = $;
|
||||||
global.jQuery = $;
|
global.jQuery = $;
|
||||||
require('datatables.net-dt');
|
require("datatables.net-dt");
|
||||||
require('datatables.net-colreorder-dt');
|
require("datatables.net-colreorder-dt");
|
||||||
|
|||||||
@@ -235,9 +235,6 @@ const App: React.FunctionComponent = () => {
|
|||||||
<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;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["jest"],
|
"types": ["jest", "puppeteer", "jest-environment-puppeteer", "expect-puppeteer"],
|
||||||
"baseUrl": "src"
|
"baseUrl": "src"
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*", "./test/testExplorer/TestExplorer.ts", "./**/*.ts", "./**/*.tsx"],
|
"include": ["./src/**/*", "./test/testExplorer/TestExplorer.ts", "./**/*.ts", "./**/*.tsx"],
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -84,7 +86,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 = {
|
||||||
@@ -208,7 +211,6 @@ module.exports = function (env = {}, argv = {}) {
|
|||||||
selfServe: "./src/SelfServe/SelfServe.tsx",
|
selfServe: "./src/SelfServe/SelfServe.tsx",
|
||||||
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
chunkFilename: "[name].[chunkhash:6].js",
|
chunkFilename: "[name].[chunkhash:6].js",
|
||||||
filename: "[name].[chunkhash:6].js",
|
filename: "[name].[chunkhash:6].js",
|
||||||
@@ -267,7 +269,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();
|
||||||
|
|||||||
Reference in New Issue
Block a user