mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 20:01:45 +00:00
Compare commits
16 Commits
test
...
exclude-ve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e4d1eaf9 | ||
|
|
2d3d96bcc7 | ||
|
|
f2d4cfcef9 | ||
|
|
70c7d84bdb | ||
|
|
dcc2036793 | ||
|
|
987368fe58 | ||
|
|
9ea588261e | ||
|
|
91aa91d860 | ||
|
|
2e747a1a07 | ||
|
|
290ca4aba5 | ||
|
|
28ceb18d73 | ||
|
|
666a378b3b | ||
|
|
13dafb9581 | ||
|
|
7c5c8ddb7a | ||
|
|
3f2c67af23 | ||
|
|
3ae1f97ccc |
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@@ -3,8 +3,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- hotfix/*
|
- hotfix/**
|
||||||
- release/*
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -196,26 +196,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
endtoendpuppeteer:
|
|
||||||
name: "End to end puppeteer tests"
|
|
||||||
needs: [lint, format, compile, unittest]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js 12.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 12.x
|
|
||||||
- name: End to End Puppeteer Tests
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm start &
|
|
||||||
npm run wait-for-server
|
|
||||||
npm run test:e2e
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
|
||||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module.exports = {
|
|||||||
headless: isCI,
|
headless: isCI,
|
||||||
slowMo: 50,
|
slowMo: 50,
|
||||||
defaultViewport: null,
|
defaultViewport: null,
|
||||||
ignoreHTTPSErrors: true
|
ignoreHTTPSErrors: true,
|
||||||
|
args: ["--disable-web-security"]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2078,7 +2078,7 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,89 +170,8 @@ export enum MongoBackendEndpointType {
|
|||||||
remote
|
remote
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MongoBackend {
|
|
||||||
public static localhostEndpoint: string = "/api/mongo/explorer";
|
|
||||||
public static centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
|
||||||
public static northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
|
||||||
public static southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
|
|
||||||
|
|
||||||
public static endpointsByRegion: any = {
|
|
||||||
default: MongoBackend.centralUsEndpoint,
|
|
||||||
northeurope: MongoBackend.northEuropeEndpoint,
|
|
||||||
ukwest: MongoBackend.northEuropeEndpoint,
|
|
||||||
uksouth: MongoBackend.northEuropeEndpoint,
|
|
||||||
westeurope: MongoBackend.northEuropeEndpoint,
|
|
||||||
australiaeast: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
australiasoutheast: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
centralindia: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
eastasia: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
japaneast: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
japanwest: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
koreacentral: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
koreasouth: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
southeastasia: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
southindia: MongoBackend.southEastAsiaEndpoint,
|
|
||||||
westindia: MongoBackend.southEastAsiaEndpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
public static endpointsByEnvironment: any = {
|
|
||||||
default: MongoBackendEndpointType.local,
|
|
||||||
localhost: MongoBackendEndpointType.local,
|
|
||||||
prod1: MongoBackendEndpointType.remote,
|
|
||||||
prod2: MongoBackendEndpointType.remote
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 435619 Add default endpoints per cloud and use regional only when available
|
// TODO: 435619 Add default endpoints per cloud and use regional only when available
|
||||||
export class CassandraBackend {
|
export class CassandraBackend {
|
||||||
public static readonly localhostEndpoint: string = "https://localhost:12901/";
|
|
||||||
public static readonly devEndpoint: string = "https://platformproxycassandradev.azurewebsites.net/";
|
|
||||||
|
|
||||||
public static readonly centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
|
||||||
public static readonly northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
|
||||||
public static readonly southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/";
|
|
||||||
|
|
||||||
public static readonly bf_default: string = "https://main.documentdb.ext.microsoftazure.de/";
|
|
||||||
public static readonly mc_default: string = "https://main.documentdb.ext.azure.cn/";
|
|
||||||
public static readonly ff_default: string = "https://main.documentdb.ext.azure.us/";
|
|
||||||
|
|
||||||
public static readonly endpointsByRegion: any = {
|
|
||||||
default: CassandraBackend.centralUsEndpoint,
|
|
||||||
northeurope: CassandraBackend.northEuropeEndpoint,
|
|
||||||
ukwest: CassandraBackend.northEuropeEndpoint,
|
|
||||||
uksouth: CassandraBackend.northEuropeEndpoint,
|
|
||||||
westeurope: CassandraBackend.northEuropeEndpoint,
|
|
||||||
australiaeast: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
australiasoutheast: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
centralindia: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
eastasia: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
japaneast: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
japanwest: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
koreacentral: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
koreasouth: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
southeastasia: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
southindia: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
westindia: CassandraBackend.southEastAsiaEndpoint,
|
|
||||||
|
|
||||||
// Black Forest
|
|
||||||
germanycentral: CassandraBackend.bf_default,
|
|
||||||
germanynortheast: CassandraBackend.bf_default,
|
|
||||||
|
|
||||||
// Fairfax
|
|
||||||
usdodeast: CassandraBackend.ff_default,
|
|
||||||
usdodcentral: CassandraBackend.ff_default,
|
|
||||||
usgovarizona: CassandraBackend.ff_default,
|
|
||||||
usgoviowa: CassandraBackend.ff_default,
|
|
||||||
usgovtexas: CassandraBackend.ff_default,
|
|
||||||
usgovvirginia: CassandraBackend.ff_default,
|
|
||||||
|
|
||||||
// Mooncake
|
|
||||||
chinaeast: CassandraBackend.mc_default,
|
|
||||||
chinaeast2: CassandraBackend.mc_default,
|
|
||||||
chinanorth: CassandraBackend.mc_default,
|
|
||||||
chinanorth2: CassandraBackend.mc_default
|
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||||
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
||||||
public static readonly queryApi: string = "api/cassandra";
|
public static readonly queryApi: string = "api/cassandra";
|
||||||
@@ -562,3 +481,11 @@ export class AnalyticalStorageTtl {
|
|||||||
public static readonly Infinite: number = -1;
|
public static readonly Infinite: number = -1;
|
||||||
public static readonly Disabled: number = 0;
|
public static readonly Disabled: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TerminalQueryParams {
|
||||||
|
public static readonly Terminal = "terminal";
|
||||||
|
public static readonly Server = "server";
|
||||||
|
public static readonly Token = "token";
|
||||||
|
public static readonly SubscriptionId = "subscriptionId";
|
||||||
|
public static readonly TerminalEndpoint = "terminalEndpoint";
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +1,8 @@
|
|||||||
import * as Constants from "../Common/Constants";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { AuthType } from "../AuthType";
|
|
||||||
import { StringUtils } from "../Utils/StringUtils";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
|
||||||
|
|
||||||
export default class EnvironmentUtility {
|
export default class EnvironmentUtility {
|
||||||
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
|
|
||||||
const defaultEnvironment: string = "default";
|
|
||||||
const defaultLocation: string = "default";
|
|
||||||
let environment: string = serverId;
|
|
||||||
const endpointType: Constants.MongoBackendEndpointType =
|
|
||||||
Constants.MongoBackend.endpointsByEnvironment[environment] ||
|
|
||||||
Constants.MongoBackend.endpointsByEnvironment[defaultEnvironment];
|
|
||||||
if (endpointType === Constants.MongoBackendEndpointType.local) {
|
|
||||||
return `${extensionEndpoint}${Constants.MongoBackend.localhostEndpoint}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedLocation = EnvironmentUtility.normalizeRegionName(location);
|
|
||||||
return (
|
|
||||||
Constants.MongoBackend.endpointsByRegion[normalizedLocation] ||
|
|
||||||
Constants.MongoBackend.endpointsByRegion[defaultLocation]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static isAadUser(): boolean {
|
|
||||||
return window.authType === AuthType.AAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getCassandraBackendEndpoint(explorer: Explorer): string {
|
|
||||||
const defaultLocation: string = "default";
|
|
||||||
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
|
|
||||||
return (
|
|
||||||
Constants.CassandraBackend.endpointsByRegion[location] ||
|
|
||||||
Constants.CassandraBackend.endpointsByRegion[defaultLocation]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static normalizeArmEndpointUri(uri: string): string {
|
public static normalizeArmEndpointUri(uri: string): string {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
return `${uri}/`;
|
return `${uri}/`;
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static normalizeRegionName(region: string): string {
|
|
||||||
return region && StringUtils.stripSpacesFromString(region.toLocaleLowerCase());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,34 +25,4 @@ describe("Message Handler", () => {
|
|||||||
MessageHandler.runGarbageCollector();
|
MessageHandler.runGarbageCollector();
|
||||||
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
|
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getDataExplorerWindow", () => {
|
|
||||||
it("should return current window if current window has dataExplorerPlatform property", () => {
|
|
||||||
const currentWindow: Window = { dataExplorerPlatform: 0 } as any;
|
|
||||||
|
|
||||||
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toEqual(currentWindow);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return current window's parent if current window's parent has dataExplorerPlatform property", () => {
|
|
||||||
const parentWindow: Window = { dataExplorerPlatform: 0 } as any;
|
|
||||||
const currentWindow: Window = { parent: parentWindow } as any;
|
|
||||||
|
|
||||||
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toEqual(parentWindow);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is reference to itself", () => {
|
|
||||||
const parentWindow: Window = {} as any;
|
|
||||||
(parentWindow as any).parent = parentWindow; // If a window does not have a parent, its parent property is a reference to itself.
|
|
||||||
const currentWindow: Window = { parent: parentWindow } as any;
|
|
||||||
|
|
||||||
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is not defined", () => {
|
|
||||||
const parentWindow: Window = {} as any;
|
|
||||||
const currentWindow: Window = { parent: parentWindow } as any;
|
|
||||||
|
|
||||||
expect(MessageHandler.getDataExplorerWindow(currentWindow)).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
|
import { getDataExplorerWindow } from "../Utils/WindowUtils";
|
||||||
|
|
||||||
export interface CachedDataPromise<T> {
|
export interface CachedDataPromise<T> {
|
||||||
deferred: Q.Deferred<T>;
|
deferred: Q.Deferred<T>;
|
||||||
@@ -48,38 +49,18 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
|||||||
|
|
||||||
export function sendMessage(data: any): void {
|
export function sendMessage(data: any): void {
|
||||||
if (canSendMessage()) {
|
if (canSendMessage()) {
|
||||||
const dataExplorerWindow = getDataExplorerWindow(window);
|
// We try to find data explorer window first, then fallback to current window
|
||||||
if (dataExplorerWindow) {
|
const portalChildWindow = getDataExplorerWindow(window) || window;
|
||||||
dataExplorerWindow.parent.postMessage(
|
portalChildWindow.parent.postMessage(
|
||||||
{
|
{
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
data: data
|
data: data
|
||||||
},
|
},
|
||||||
dataExplorerWindow.document.referrer
|
portalChildWindow.document.referrer
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only exported for unit tests
|
|
||||||
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
|
||||||
// Start with the current window and traverse up the parent hierarchy to find a window
|
|
||||||
// with `dataExplorerPlatform` property
|
|
||||||
let dataExplorerWindow: Window | undefined = currentWindow;
|
|
||||||
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
while (dataExplorerWindow && (dataExplorerWindow as any).dataExplorerPlatform == undefined) {
|
|
||||||
// If a window does not have a parent, its parent property is a reference to itself.
|
|
||||||
if (dataExplorerWindow.parent == dataExplorerWindow) {
|
|
||||||
dataExplorerWindow = undefined;
|
|
||||||
} else {
|
|
||||||
dataExplorerWindow = dataExplorerWindow.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataExplorerWindow;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function canSendMessage(): boolean {
|
export function canSendMessage(): boolean {
|
||||||
return window.parent !== window;
|
return window.parent !== window;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||||
@@ -237,19 +236,19 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
it("returns a production endpoint", () => {
|
||||||
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a development endpoint", () => {
|
it("returns a development endpoint", () => {
|
||||||
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
|
||||||
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a guest endpoint", () => {
|
it("returns a guest endpoint", () => {
|
||||||
window.authType = AuthType.EncryptedToken;
|
window.authType = AuthType.EncryptedToken;
|
||||||
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import DocumentId from "../Explorer/Tree/DocumentId";
|
|||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import EnvironmentUtility from "./EnvironmentUtility";
|
|
||||||
import { MinimalQueryIterator } from "./IteratorUtilities";
|
import { MinimalQueryIterator } from "./IteratorUtilities";
|
||||||
import { sendMessage } from "./MessageHandler";
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ export function queryDocuments(
|
|||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
|
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount) || "";
|
const endpoint = getEndpoint() || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -139,7 +138,7 @@ export function readDocument(
|
|||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint();
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -179,7 +178,7 @@ export function createDocument(
|
|||||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
|
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||||
@@ -221,7 +220,7 @@ export function updateDocument(
|
|||||||
pk:
|
pk:
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -260,7 +259,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
|||||||
pk:
|
pk:
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||||
@@ -303,7 +302,7 @@ export function createMongoCollectionWithProxy(
|
|||||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(databaseAccount);
|
const endpoint = getEndpoint();
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(
|
.fetch(
|
||||||
@@ -327,12 +326,9 @@ export function createMongoCollectionWithProxy(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
export function getEndpoint(): string {
|
||||||
const serverId = window.dataExplorer.serverId();
|
|
||||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||||
let url = configContext.MONGO_BACKEND_ENDPOINT
|
let url = (configContext.MONGO_BACKEND_ENDPOINT || extensionEndpoint) + "/api/mongo/explorer";
|
||||||
? configContext.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer"
|
|
||||||
: EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint);
|
|
||||||
|
|
||||||
if (window.authType === AuthType.EncryptedToken) {
|
if (window.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export const readDatabaseOffer = async (
|
|||||||
): Promise<DataModels.OfferWithHeaders> => {
|
): Promise<DataModels.OfferWithHeaders> => {
|
||||||
let offerId = params.offerId;
|
let offerId = params.offerId;
|
||||||
if (!offerId) {
|
if (!offerId) {
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -26,7 +30,7 @@ export const readDatabaseOffer = async (
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId, params.isServerless);
|
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
||||||
if (!offerId) {
|
if (!offerId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -76,8 +80,8 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
|
|||||||
return rpResponse?.name;
|
return rpResponse?.name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string, isServerless: boolean): Promise<string> => {
|
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
||||||
const offers = await readOffers(isServerless);
|
const offers = await readOffers();
|
||||||
const offer = offers.find(offer => offer.resource === databaseResourceId);
|
const offer = offers.find(offer => offer.resource === databaseResourceId);
|
||||||
return offer?.id;
|
return offer?.id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import { client } from "../CosmosClient";
|
|||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
import { sendCachedDataMessage } from "../MessageHandler";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readOffers = async (isServerless?: boolean): Promise<Offer[]> => {
|
export const readOffers = async (): Promise<Offer[]> => {
|
||||||
if (isServerless) {
|
|
||||||
return []; // Reading offers is not supported for serverless accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
||||||
|
|||||||
@@ -292,7 +292,6 @@ export interface CreateCollectionParams {
|
|||||||
export interface ReadDatabaseOfferParams {
|
export interface ReadDatabaseOfferParams {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
databaseResourceId?: string;
|
databaseResourceId?: string;
|
||||||
isServerless?: boolean;
|
|
||||||
offerId?: string;
|
offerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import * as Logger from "../../../Common/Logger";
|
|||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -32,11 +34,11 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
|
|
||||||
public getTerminalParams(): Map<string, string> {
|
public getTerminalParams(): Map<string, string> {
|
||||||
let params: Map<string, string> = new Map<string, string>();
|
let params: Map<string, string> = new Map<string, string>();
|
||||||
params.set("terminal", "true");
|
params.set(TerminalQueryParams.Terminal, "true");
|
||||||
|
|
||||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
||||||
if (terminalEndpoint) {
|
if (terminalEndpoint) {
|
||||||
params.set("terminalEndpoint", terminalEndpoint);
|
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
@@ -75,11 +77,13 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
params.set("server", serverInfo.notebookServerEndpoint);
|
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
||||||
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
||||||
params.set("token", serverInfo.authToken);
|
params.set(TerminalQueryParams.Token, serverInfo.authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params.set(TerminalQueryParams.SubscriptionId, userContext.subscriptionId);
|
||||||
|
|
||||||
let result: string = "terminal.html?";
|
let result: string = "terminal.html?";
|
||||||
for (let key of params.keys()) {
|
for (let key of params.keys()) {
|
||||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container.isPreferredApiMongoDB() && this.container.hasStorageAnalyticsAfecFeature()) {
|
if (this.container.isPreferredApiMongoDB()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +666,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
const subscriptionType: ViewModels.SubscriptionType =
|
const subscriptionType: ViewModels.SubscriptionType =
|
||||||
this.container.subscriptionType && this.container.subscriptionType();
|
this.container.subscriptionType && this.container.subscriptionType();
|
||||||
|
|
||||||
if (subscriptionType === ViewModels.SubscriptionType.EA) {
|
if (subscriptionType === ViewModels.SubscriptionType.EA || this.container.isServerlessEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
const subscriptionType: ViewModels.SubscriptionType =
|
const subscriptionType: ViewModels.SubscriptionType =
|
||||||
this.container.subscriptionType && this.container.subscriptionType();
|
this.container.subscriptionType && this.container.subscriptionType();
|
||||||
|
|
||||||
if (subscriptionType === ViewModels.SubscriptionType.EA) {
|
if (subscriptionType === ViewModels.SubscriptionType.EA || this.container.isServerlessEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,11 +134,9 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
expect(telemetryProcessorSpy.called).toBe(true);
|
expect(telemetryProcessorSpy.called).toBe(true);
|
||||||
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
||||||
expect(
|
expect(
|
||||||
telemetryProcessorSpy.calledWith(
|
telemetryProcessorSpy.calledWith(Action.DeleteCollection, ActionModifiers.Mark, {
|
||||||
Action.DeleteCollection,
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||||
ActionModifiers.Mark,
|
})
|
||||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
|
||||||
)
|
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,11 +88,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
|
|||||||
this.containerDeleteFeedback()
|
this.containerDeleteFeedback()
|
||||||
);
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(
|
TelemetryProcessor.trace(Action.DeleteCollection, ActionModifiers.Mark, {
|
||||||
Action.DeleteCollection,
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||||
ActionModifiers.Mark,
|
});
|
||||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.containerDeleteFeedback("");
|
this.containerDeleteFeedback("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,11 +120,9 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
|
|
||||||
return pane.submit().then(() => {
|
return pane.submit().then(() => {
|
||||||
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
||||||
expect(TelemetryProcessor.trace).toHaveBeenCalledWith(
|
expect(TelemetryProcessor.trace).toHaveBeenCalledWith(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||||
Action.DeleteDatabase,
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||||
ActionModifiers.Mark,
|
});
|
||||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,11 +97,9 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
|||||||
this.databaseDeleteFeedback()
|
this.databaseDeleteFeedback()
|
||||||
);
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(
|
TelemetryProcessor.trace(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||||
Action.DeleteDatabase,
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||||
ActionModifiers.Mark,
|
});
|
||||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.databaseDeleteFeedback("");
|
this.databaseDeleteFeedback("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export default class AddTableEntityPane extends TableEntityPane {
|
|||||||
);
|
);
|
||||||
this.updateIsActionEnabled();
|
this.updateIsActionEnabled();
|
||||||
super.open();
|
super.open();
|
||||||
|
this.focusValueElement();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.displayedAttributes(
|
this.displayedAttributes(
|
||||||
@@ -79,8 +80,12 @@ export default class AddTableEntityPane extends TableEntityPane {
|
|||||||
);
|
);
|
||||||
this.updateIsActionEnabled();
|
this.updateIsActionEnabled();
|
||||||
super.open();
|
super.open();
|
||||||
|
this.focusValueElement();
|
||||||
}
|
}
|
||||||
const focusElement = document.getElementById("closeAddEntityPane");
|
}
|
||||||
|
|
||||||
|
private focusValueElement() {
|
||||||
|
const focusElement = document.getElementById("addTableEntityValue");
|
||||||
focusElement && focusElement.focus();
|
focusElement && focusElement.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as Entities from "./Entities";
|
import * as Entities from "./Entities";
|
||||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
@@ -308,7 +307,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
authType === AuthType.EncryptedToken
|
authType === AuthType.EncryptedToken
|
||||||
? Constants.CassandraBackend.guestQueryApi
|
? Constants.CassandraBackend.guestQueryApi
|
||||||
: Constants.CassandraBackend.queryApi;
|
: Constants.CassandraBackend.queryApi;
|
||||||
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`, {
|
$.ajax(`${collection.container.extensionEndpoint()}${apiEndpoint}`, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: {
|
data: {
|
||||||
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
||||||
@@ -559,7 +558,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
authType === AuthType.EncryptedToken
|
authType === AuthType.EncryptedToken
|
||||||
? Constants.CassandraBackend.guestKeysApi
|
? Constants.CassandraBackend.guestKeysApi
|
||||||
: Constants.CassandraBackend.keysApi;
|
: Constants.CassandraBackend.keysApi;
|
||||||
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
|
let endpoint = `${collection.container.extensionEndpoint()}${apiEndpoint}`;
|
||||||
const deferred = Q.defer<CassandraTableKeys>();
|
const deferred = Q.defer<CassandraTableKeys>();
|
||||||
$.ajax(endpoint, {
|
$.ajax(endpoint, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@@ -614,7 +613,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
authType === AuthType.EncryptedToken
|
authType === AuthType.EncryptedToken
|
||||||
? Constants.CassandraBackend.guestSchemaApi
|
? Constants.CassandraBackend.guestSchemaApi
|
||||||
: Constants.CassandraBackend.schemaApi;
|
: Constants.CassandraBackend.schemaApi;
|
||||||
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
|
let endpoint = `${collection.container.extensionEndpoint()}${apiEndpoint}`;
|
||||||
const deferred = Q.defer<CassandraTableKey[]>();
|
const deferred = Q.defer<CassandraTableKey[]>();
|
||||||
$.ajax(endpoint, {
|
$.ajax(endpoint, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@@ -668,7 +667,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
authType === AuthType.EncryptedToken
|
authType === AuthType.EncryptedToken
|
||||||
? Constants.CassandraBackend.guestCreateOrDeleteApi
|
? Constants.CassandraBackend.guestCreateOrDeleteApi
|
||||||
: Constants.CassandraBackend.createOrDeleteApi;
|
: Constants.CassandraBackend.createOrDeleteApi;
|
||||||
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(explorer)}${apiEndpoint}`, {
|
$.ajax(`${explorer.extensionEndpoint()}${apiEndpoint}`, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: {
|
data: {
|
||||||
accountName: explorer.databaseAccount() && explorer.databaseAccount().name,
|
accountName: explorer.databaseAccount() && explorer.databaseAccount().name,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
|
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
|
||||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
|
||||||
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
@@ -109,11 +108,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
) + Constants.MongoDBAccounts.defaultPort.toString();
|
) + Constants.MongoDBAccounts.defaultPort.toString();
|
||||||
const databaseId = this.collection.databaseId;
|
const databaseId = this.collection.databaseId;
|
||||||
const collectionId = this.collection.id();
|
const collectionId = this.collection.id();
|
||||||
const apiEndpoint = EnvironmentUtility.getMongoBackendEndpoint(
|
const apiEndpoint = this._container.extensionEndpoint();
|
||||||
this._container.serverId(),
|
|
||||||
userContext.databaseAccount.location,
|
|
||||||
this._container.extensionEndpoint()
|
|
||||||
).replace("/api/mongo/explorer", "");
|
|
||||||
const encryptedAuthToken: string = userContext.accessToken;
|
const encryptedAuthToken: string = userContext.accessToken;
|
||||||
|
|
||||||
shellIframe.contentWindow.postMessage(
|
shellIframe.contentWindow.postMessage(
|
||||||
@@ -142,7 +137,7 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataToLog: string = event.data.data.logData;
|
const dataToLog = { message: event.data.data.logData };
|
||||||
const logType: string = event.data.data.logType;
|
const logType: string = event.data.data.logType;
|
||||||
const shellTraceId: string = event.data.data.traceId || "none";
|
const shellTraceId: string = event.data.data.traceId || "none";
|
||||||
|
|
||||||
|
|||||||
@@ -200,11 +200,10 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async loadOffer(): Promise<void> {
|
public async loadOffer(): Promise<void> {
|
||||||
if (!this.offer()) {
|
if (!this.container.isServerlessEnabled() && !this.offer()) {
|
||||||
const params: DataModels.ReadDatabaseOfferParams = {
|
const params: DataModels.ReadDatabaseOfferParams = {
|
||||||
databaseId: this.id(),
|
databaseId: this.id(),
|
||||||
databaseResourceId: this.self,
|
databaseResourceId: this.self
|
||||||
isServerless: this.container.isServerlessEnabled()
|
|
||||||
};
|
};
|
||||||
this.offer(await readDatabaseOffer(params));
|
this.offer(await readDatabaseOffer(params));
|
||||||
}
|
}
|
||||||
@@ -290,6 +289,10 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
|
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
|
||||||
|
if (collectionsToRemove.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const collectionsToKeep: Collection[] = [];
|
const collectionsToKeep: Collection[] = [];
|
||||||
|
|
||||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
|
|
||||||
this.container.nonSystemDatabases.subscribe((databases: ViewModels.Database[]) => {
|
this.container.nonSystemDatabases.subscribe((databases: ViewModels.Database[]) => {
|
||||||
// Clean up old databases
|
// Clean up old databases
|
||||||
this.cleanupDatabasesKoSubs(databases.map((database: ViewModels.Database) => database.id()));
|
this.cleanupDatabasesKoSubs();
|
||||||
|
|
||||||
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
@@ -799,16 +799,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
this.koSubsCollectionIdMap.push(collectionId, sub);
|
this.koSubsCollectionIdMap.push(collectionId, sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupDatabasesKoSubs(existingDatabaseIds: string[]): void {
|
private cleanupDatabasesKoSubs(): void {
|
||||||
const databaseIdsToRemove = this.databaseCollectionIdMap
|
this.koSubsDatabaseIdMap.keys().forEach((databaseId: string) => {
|
||||||
.keys()
|
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||||
.filter((id: string) => existingDatabaseIds.indexOf(id) === -1);
|
this.koSubsDatabaseIdMap.delete(databaseId);
|
||||||
|
|
||||||
databaseIdsToRemove.forEach((databaseId: string) => {
|
|
||||||
if (this.koSubsDatabaseIdMap.has(databaseId)) {
|
|
||||||
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
|
||||||
this.koSubsDatabaseIdMap.delete(databaseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.databaseCollectionIdMap.has(databaseId)) {
|
if (this.databaseCollectionIdMap.has(databaseId)) {
|
||||||
this.databaseCollectionIdMap
|
this.databaseCollectionIdMap
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|||||||
import { appInsights } from "../appInsights";
|
import { appInsights } from "../appInsights";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getDataExplorerWindow } from "../../Utils/WindowUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that persists telemetry data to the portal tables.
|
* Class that persists telemetry data to the portal tables.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data?: unknown): void {
|
type TelemetryData = { [key: string]: unknown };
|
||||||
|
|
||||||
|
export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data?: TelemetryData): void {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
data: {
|
data: {
|
||||||
@@ -22,7 +25,7 @@ export function trace(action: Action, actionModifier: string = ActionModifiers.M
|
|||||||
appInsights.trackEvent({ name: Action[action] }, getData(actionModifier, data));
|
appInsights.trackEvent({ name: Action[action] }, getData(actionModifier, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceStart(action: Action, data?: unknown): number {
|
export function traceStart(action: Action, data?: TelemetryData): number {
|
||||||
const timestamp: number = Date.now();
|
const timestamp: number = Date.now();
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
@@ -38,7 +41,7 @@ export function traceStart(action: Action, data?: unknown): number {
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceSuccess(action: Action, data?: unknown, timestamp?: number): void {
|
export function traceSuccess(action: Action, data?: TelemetryData, timestamp?: number): void {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
data: {
|
data: {
|
||||||
@@ -52,7 +55,7 @@ export function traceSuccess(action: Action, data?: unknown, timestamp?: number)
|
|||||||
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Success, data));
|
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Success, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceFailure(action: Action, data?: unknown, timestamp?: number): void {
|
export function traceFailure(action: Action, data?: TelemetryData, timestamp?: number): void {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
data: {
|
data: {
|
||||||
@@ -66,7 +69,7 @@ export function traceFailure(action: Action, data?: unknown, timestamp?: number)
|
|||||||
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Failed, data));
|
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Failed, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceCancel(action: Action, data?: unknown, timestamp?: number): void {
|
export function traceCancel(action: Action, data?: TelemetryData, timestamp?: number): void {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
data: {
|
data: {
|
||||||
@@ -80,7 +83,7 @@ export function traceCancel(action: Action, data?: unknown, timestamp?: number):
|
|||||||
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Cancel, data));
|
appInsights.stopTrackEvent(Action[action], getData(ActionModifiers.Cancel, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceOpen(action: Action, data?: unknown, timestamp?: number): number {
|
export function traceOpen(action: Action, data?: TelemetryData, timestamp?: number): number {
|
||||||
const validTimestamp = timestamp || Date.now();
|
const validTimestamp = timestamp || Date.now();
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
@@ -96,7 +99,7 @@ export function traceOpen(action: Action, data?: unknown, timestamp?: number): n
|
|||||||
return validTimestamp;
|
return validTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceMark(action: Action, data?: unknown, timestamp?: number): number {
|
export function traceMark(action: Action, data?: TelemetryData, timestamp?: number): number {
|
||||||
const validTimestamp = timestamp || Date.now();
|
const validTimestamp = timestamp || Date.now();
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: MessageTypes.TelemetryInfo,
|
type: MessageTypes.TelemetryInfo,
|
||||||
@@ -112,21 +115,16 @@ export function traceMark(action: Action, data?: unknown, timestamp?: number): n
|
|||||||
return validTimestamp;
|
return validTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getData(actionModifier: string, data: unknown = {}): { [key: string]: string } | undefined {
|
function getData(actionModifier: string, data: TelemetryData = {}): { [key: string]: string } {
|
||||||
if (typeof data === "string") {
|
const dataExplorerWindow = getDataExplorerWindow(window);
|
||||||
data = { message: data };
|
return {
|
||||||
}
|
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
||||||
if (typeof data === "object") {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return {
|
authType: dataExplorerWindow && (dataExplorerWindow as any).authType,
|
||||||
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
subscriptionId: userContext.subscriptionId as string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
platform: configContext.platform,
|
||||||
authType: (window as any).authType,
|
env: process.env.NODE_ENV as string,
|
||||||
subscriptionId: userContext.subscriptionId as string,
|
actionModifier,
|
||||||
platform: configContext.platform,
|
...data
|
||||||
env: process.env.NODE_ENV as string,
|
};
|
||||||
actionModifier,
|
|
||||||
...data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { ServerConnection } from "@jupyterlab/services";
|
|||||||
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
||||||
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 { updateUserContext } from "../UserContext";
|
||||||
|
import { TerminalQueryParams } from "../Common/Constants";
|
||||||
|
|
||||||
const getUrlVars = (): { [key: string]: string } => {
|
const getUrlVars = (): { [key: string]: string } => {
|
||||||
const vars: { [key: string]: string } = {};
|
const vars: { [key: string]: string } = {};
|
||||||
@@ -18,22 +20,22 @@ const getUrlVars = (): { [key: string]: string } => {
|
|||||||
|
|
||||||
const createServerSettings = (urlVars: { [key: string]: string }): ServerConnection.ISettings => {
|
const createServerSettings = (urlVars: { [key: string]: string }): ServerConnection.ISettings => {
|
||||||
let body: BodyInit;
|
let body: BodyInit;
|
||||||
if (urlVars.hasOwnProperty("terminalEndpoint")) {
|
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
|
||||||
body = JSON.stringify({
|
body = JSON.stringify({
|
||||||
endpoint: urlVars["terminalEndpoint"]
|
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = urlVars["server"];
|
const server = urlVars[TerminalQueryParams.Server];
|
||||||
let options: Partial<ServerConnection.ISettings> = {
|
let options: Partial<ServerConnection.ISettings> = {
|
||||||
baseUrl: server,
|
baseUrl: server,
|
||||||
init: { body },
|
init: { body },
|
||||||
fetch: window.parent.fetch
|
fetch: window.parent.fetch
|
||||||
};
|
};
|
||||||
if (urlVars.hasOwnProperty("token")) {
|
if (urlVars.hasOwnProperty(TerminalQueryParams.Token)) {
|
||||||
options = {
|
options = {
|
||||||
baseUrl: server,
|
baseUrl: server,
|
||||||
token: urlVars["token"],
|
token: urlVars[TerminalQueryParams.Token],
|
||||||
init: { body },
|
init: { body },
|
||||||
fetch: window.parent.fetch
|
fetch: window.parent.fetch
|
||||||
};
|
};
|
||||||
@@ -44,6 +46,12 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
|
|||||||
|
|
||||||
const main = async (): Promise<void> => {
|
const main = async (): Promise<void> => {
|
||||||
const urlVars = getUrlVars();
|
const urlVars = getUrlVars();
|
||||||
|
|
||||||
|
// Initialize userContext. Currently only subscrptionId is required by TelemetryProcessor
|
||||||
|
updateUserContext({
|
||||||
|
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId]
|
||||||
|
});
|
||||||
|
|
||||||
const serverSettings = createServerSettings(urlVars);
|
const serverSettings = createServerSettings(urlVars);
|
||||||
|
|
||||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, {
|
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, {
|
||||||
@@ -51,7 +59,7 @@ const main = async (): Promise<void> => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (urlVars.hasOwnProperty("terminal")) {
|
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
|
||||||
await JupyterLabAppFactory.createTerminalApp(serverSettings);
|
await JupyterLabAppFactory.createTerminalApp(serverSettings);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Only terminal is supported");
|
throw new Error("Only terminal is supported");
|
||||||
|
|||||||
49
src/Utils/WindowUtils.test.ts
Normal file
49
src/Utils/WindowUtils.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { getDataExplorerWindow } from "./WindowUtils";
|
||||||
|
|
||||||
|
const createWindow = (dataExplorerPlatform: unknown, parent: Window): Window => {
|
||||||
|
// TODO: Need to `any` here since we're creating a mock window object
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const mockWindow: any = {};
|
||||||
|
if (dataExplorerPlatform !== undefined) {
|
||||||
|
mockWindow.dataExplorerPlatform = dataExplorerPlatform;
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
mockWindow.parent = parent;
|
||||||
|
}
|
||||||
|
return mockWindow;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("WindowUtils", () => {
|
||||||
|
describe("getDataExplorerWindow", () => {
|
||||||
|
it("should return current window if current window has dataExplorerPlatform property", () => {
|
||||||
|
const currentWindow = createWindow(0, undefined);
|
||||||
|
|
||||||
|
expect(getDataExplorerWindow(currentWindow)).toEqual(currentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return current window's parent if current window's parent has dataExplorerPlatform property", () => {
|
||||||
|
const parentWindow = createWindow(0, undefined);
|
||||||
|
const currentWindow = createWindow(undefined, parentWindow);
|
||||||
|
|
||||||
|
expect(getDataExplorerWindow(currentWindow)).toEqual(parentWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is reference to itself", () => {
|
||||||
|
const parentWindow = createWindow(undefined, undefined);
|
||||||
|
|
||||||
|
// TODO: Need to `any` here since parent is a readonly property
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(parentWindow as any).parent = parentWindow; // If a window does not have a parent, its parent property is a reference to itself.
|
||||||
|
const currentWindow = createWindow(undefined, parentWindow);
|
||||||
|
|
||||||
|
expect(getDataExplorerWindow(currentWindow)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is not defined", () => {
|
||||||
|
const parentWindow = createWindow(undefined, undefined);
|
||||||
|
const currentWindow = createWindow(undefined, parentWindow);
|
||||||
|
|
||||||
|
expect(getDataExplorerWindow(currentWindow)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
23
src/Utils/WindowUtils.ts
Normal file
23
src/Utils/WindowUtils.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
||||||
|
// Start with the current window and traverse up the parent hierarchy to find a window
|
||||||
|
// with `dataExplorerPlatform` property
|
||||||
|
let dataExplorerWindow: Window | undefined = currentWindow;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
while (dataExplorerWindow && (dataExplorerWindow as any).dataExplorerPlatform === undefined) {
|
||||||
|
// If a window does not have a parent, its parent property is a reference to itself.
|
||||||
|
if (dataExplorerWindow.parent === dataExplorerWindow) {
|
||||||
|
dataExplorerWindow = undefined;
|
||||||
|
} else {
|
||||||
|
dataExplorerWindow = dataExplorerWindow.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// This can happen if we come across parent from a different origin
|
||||||
|
dataExplorerWindow = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataExplorerWindow;
|
||||||
|
};
|
||||||
98
test/cassandra/container.spec.ts
Normal file
98
test/cassandra/container.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import "expect-puppeteer";
|
||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
jest.setTimeout(300000);
|
||||||
|
const RENDER_DELAY = 400
|
||||||
|
const LOADING_STATE_DELAY = 1800
|
||||||
|
|
||||||
|
describe('Collection Add and Delete Cassandra spec', () => {
|
||||||
|
it('creates a collection', async () => {
|
||||||
|
try {
|
||||||
|
const keyspaceId = `keyspaceid${crypto.randomBytes(8).toString("hex")}`;
|
||||||
|
const tableId = `tableid${crypto.randomBytes(3).toString('hex')}`;
|
||||||
|
const prodUrl = "https://localhost:1234/hostedExplorer.html";
|
||||||
|
page.goto(prodUrl);
|
||||||
|
|
||||||
|
// log in with connection string
|
||||||
|
const handle = await page.waitForSelector('iframe');
|
||||||
|
const frame = await handle.contentFrame();
|
||||||
|
await frame.waitFor('div > p.switchConnectTypeText', { visible: true });
|
||||||
|
await frame.click('div > p.switchConnectTypeText');
|
||||||
|
const connStr = process.env.CASSANDRA_CONNECTION_STRING;
|
||||||
|
await frame.type("input[class='inputToken']", connStr);
|
||||||
|
await frame.click("input[value='Connect']");
|
||||||
|
|
||||||
|
// create new table
|
||||||
|
await frame.waitFor('button[data-test="New Table"]', { visible: true });
|
||||||
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
await frame.click('button[data-test="New Table"]');
|
||||||
|
|
||||||
|
// type keyspace id
|
||||||
|
await frame.waitFor('input[id="keyspace-id"]', { visible: true });
|
||||||
|
await frame.type('input[id="keyspace-id"]', keyspaceId);
|
||||||
|
|
||||||
|
// type table id
|
||||||
|
await frame.waitFor('input[class="textfontclr"]');
|
||||||
|
await frame.type('input[class="textfontclr"]', tableId);
|
||||||
|
|
||||||
|
// click submit
|
||||||
|
await frame.waitFor('#cassandraaddcollectionpane > div > form > div.paneFooter > div > input');
|
||||||
|
await frame.click('#cassandraaddcollectionpane > div > form > div.paneFooter > div > input');
|
||||||
|
|
||||||
|
// open database menu
|
||||||
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
||||||
|
await frame.waitFor(`div[data-test="${keyspaceId}"]`, { visible: true });
|
||||||
|
await frame.waitFor(LOADING_STATE_DELAY)
|
||||||
|
await frame.waitFor(`div[data-test="${keyspaceId}"]`, { visible: true });
|
||||||
|
await frame.click(`div[data-test="${keyspaceId}"]`);
|
||||||
|
await frame.waitFor(`span[title="${tableId}"]`, { visible: true });
|
||||||
|
|
||||||
|
// delete container
|
||||||
|
|
||||||
|
// click context menu for container
|
||||||
|
await frame.waitFor(`div[data-test="${tableId}"] > div > button`, { visible: true });
|
||||||
|
await frame.click(`div[data-test="${tableId}"] > div > button`);
|
||||||
|
|
||||||
|
// click delete container
|
||||||
|
await frame.waitForSelector('body > div.ms-Layer.ms-Layer--fixed');
|
||||||
|
await frame.waitFor(RENDER_DELAY)
|
||||||
|
const elements = await frame.$$('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]')
|
||||||
|
await elements[0].click()
|
||||||
|
|
||||||
|
// confirm delete container
|
||||||
|
await frame.type('input[data-test="confirmCollectionId"]', tableId.trim());
|
||||||
|
|
||||||
|
// click delete
|
||||||
|
await frame.click('input[data-test="deleteCollection"]');
|
||||||
|
await frame.waitFor(LOADING_STATE_DELAY);
|
||||||
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
||||||
|
await expect(page).not.toMatchElement(`div[data-test="${tableId}"]`);
|
||||||
|
|
||||||
|
// click context menu for database
|
||||||
|
await frame.waitFor(`div[data-test="${keyspaceId}"] > div > button`);
|
||||||
|
const button = await frame.$(`div[data-test="${keyspaceId}"] > div > button`);
|
||||||
|
await button.focus();
|
||||||
|
await button.asElement().click();
|
||||||
|
|
||||||
|
// click delete database
|
||||||
|
await frame.waitFor(RENDER_DELAY);
|
||||||
|
const dbElements = await frame.$$('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]')
|
||||||
|
await dbElements[0].click();
|
||||||
|
|
||||||
|
// confirm delete database
|
||||||
|
await frame.type('input[data-test="confirmDatabaseId"]', keyspaceId.trim());
|
||||||
|
|
||||||
|
// click delete
|
||||||
|
await frame.click('input[data-test="deleteDatabase"]');
|
||||||
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
await expect(page).not.toMatchElement(`div[data-test="${keyspaceId}"]`);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const testName = (expect as any).getState().currentTestName
|
||||||
|
await page.screenshot({path: `Test Failed ${testName}.png`});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import "expect-puppeteer";
|
import "expect-puppeteer";
|
||||||
import crypto from 'crypto'
|
import crypto from "crypto";
|
||||||
|
|
||||||
jest.setTimeout(300000);
|
jest.setTimeout(300000);
|
||||||
|
|
||||||
describe('Collection Add and Delete SQL spec', () => {
|
describe("Collection Add and Delete SQL spec", () => {
|
||||||
it('creates a collection', async () => {
|
it("creates a collection", async () => {
|
||||||
try {
|
try {
|
||||||
const dbId = `TestDatabase${crypto.randomBytes(8).toString("hex")}`;
|
const dbId = `TestDatabase${crypto.randomBytes(8).toString("hex")}`;
|
||||||
const collectionId = `TestCollection${crypto.randomBytes(8).toString("hex")}`;
|
const collectionId = `TestCollection${crypto.randomBytes(8).toString("hex")}`;
|
||||||
@@ -13,10 +13,10 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
page.goto(prodUrl);
|
page.goto(prodUrl);
|
||||||
|
|
||||||
// log in with connection string
|
// log in with connection string
|
||||||
const handle = await page.waitForSelector('iframe');
|
const handle = await page.waitForSelector("iframe");
|
||||||
const frame = await handle.contentFrame();
|
const frame = await handle.contentFrame();
|
||||||
await frame.waitFor('div > p.switchConnectTypeText', { visible: true });
|
await frame.waitFor("div > p.switchConnectTypeText", { visible: true });
|
||||||
await frame.click('div > p.switchConnectTypeText');
|
await frame.click("div > p.switchConnectTypeText");
|
||||||
const connStr = process.env.PORTAL_RUNNER_CONNECTION_STRING;
|
const connStr = process.env.PORTAL_RUNNER_CONNECTION_STRING;
|
||||||
await frame.type("input[class='inputToken']", connStr);
|
await frame.type("input[class='inputToken']", connStr);
|
||||||
await frame.click("input[value='Connect']");
|
await frame.click("input[value='Connect']");
|
||||||
@@ -32,7 +32,7 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
|
|
||||||
// check shared throughput
|
// check shared throughput
|
||||||
await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
await frame.waitFor('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||||
await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]') ;
|
await frame.click('input[data-test="addCollectionPane-databaseSharedThroughput"]');
|
||||||
|
|
||||||
// type database id
|
// type database id
|
||||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||||
@@ -47,8 +47,8 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
await frame.type('input[data-test="addCollection-partitionKeyValue"]', sharedKey);
|
await frame.type('input[data-test="addCollection-partitionKeyValue"]', sharedKey);
|
||||||
|
|
||||||
// click submit
|
// click submit
|
||||||
await frame.waitFor('#submitBtnAddCollection');
|
await frame.waitFor("#submitBtnAddCollection");
|
||||||
await frame.click('#submitBtnAddCollection');
|
await frame.click("#submitBtnAddCollection");
|
||||||
|
|
||||||
// validate created
|
// validate created
|
||||||
// open database menu
|
// open database menu
|
||||||
@@ -56,8 +56,7 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
||||||
await frame.click(`div[data-test="${dbId}"]`);
|
await frame.click(`div[data-test="${dbId}"]`);
|
||||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
await frame.waitFor(3000);
|
||||||
await frame.waitFor(3000)
|
|
||||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
||||||
|
|
||||||
// delete container
|
// delete container
|
||||||
@@ -66,17 +65,18 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
await frame.waitFor(`div[data-test="${collectionId}"] > div > button`, { visible: true });
|
await frame.waitFor(`div[data-test="${collectionId}"] > div > button`, { visible: true });
|
||||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
||||||
await frame.click(`div[data-test="${collectionId}"] > div > button`);
|
await frame.click(`div[data-test="${collectionId}"] > div > button`);
|
||||||
await frame.waitFor(2000)
|
await frame.waitFor(2000);
|
||||||
|
|
||||||
// click delete container
|
// click delete container
|
||||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]', { visible: true });
|
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]', { visible: true });
|
||||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||||
|
|
||||||
// confirm delete container
|
// confirm delete container
|
||||||
await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true })
|
await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
|
||||||
await frame.type('input[data-test="confirmCollectionId"]', collectionId.trim());
|
await frame.type('input[data-test="confirmCollectionId"]', collectionId.trim());
|
||||||
|
|
||||||
// click delete
|
// click delete
|
||||||
await frame.waitFor('input[data-test="deleteCollection"]', { visible: true })
|
await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
|
||||||
await frame.click('input[data-test="deleteCollection"]');
|
await frame.click('input[data-test="deleteCollection"]');
|
||||||
await frame.waitFor(5000);
|
await frame.waitFor(5000);
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
@@ -101,8 +101,10 @@ describe('Collection Add and Delete SQL spec', () => {
|
|||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
await expect(page).not.toMatchElement(`div[data-test="${dbId}"]`);
|
await expect(page).not.toMatchElement(`div[data-test="${dbId}"]`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await page.screenshot({path: 'failure.png'});
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const testName = (expect as any).getState().currentTestName;
|
||||||
|
await page.screenshot({ path: `Test Failed ${testName}.jpg` });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"sourceMap": false,
|
"sourceMap": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const childProcess = require("child_process");
|
|||||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const isCI = require("is-ci");
|
const isCI = require("is-ci");
|
||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
|
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
|
||||||
|
|
||||||
@@ -104,6 +105,15 @@ module.exports = function(env = {}, argv = {}) {
|
|||||||
envVars.NODE_ENV = "development";
|
envVars.NODE_ENV = "development";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceMapPlugin =
|
||||||
|
mode === "development"
|
||||||
|
? new webpack.EvalSourceMapDevToolPlugin({})
|
||||||
|
: new webpack.SourceMapDevToolPlugin({
|
||||||
|
// test: [".js", ".mjs", ".css", ".ts", ".tsx"],
|
||||||
|
filename: "[name].js.map",
|
||||||
|
exclude: [/vendor/]
|
||||||
|
});
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
new CleanWebpackPlugin(["dist"]),
|
new CleanWebpackPlugin(["dist"]),
|
||||||
new CreateFileWebpack({
|
new CreateFileWebpack({
|
||||||
@@ -164,7 +174,9 @@ module.exports = function(env = {}, argv = {}) {
|
|||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [{ from: "DataExplorer.nuspec" }, { from: "web.config" }, { from: "quickstart/*.zip" }]
|
patterns: [{ from: "DataExplorer.nuspec" }, { from: "web.config" }, { from: "quickstart/*.zip" }]
|
||||||
}),
|
}),
|
||||||
new EnvironmentPlugin(envVars)
|
new EnvironmentPlugin(envVars),
|
||||||
|
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
||||||
|
sourceMapPlugin
|
||||||
];
|
];
|
||||||
|
|
||||||
if (argv.analyze) {
|
if (argv.analyze) {
|
||||||
@@ -194,7 +206,6 @@ module.exports = function(env = {}, argv = {}) {
|
|||||||
filename: "[name].[chunkhash:6].js",
|
filename: "[name].[chunkhash:6].js",
|
||||||
path: path.resolve(__dirname, "dist")
|
path: path.resolve(__dirname, "dist")
|
||||||
},
|
},
|
||||||
devtool: mode === "development" ? "cheap-eval-source-map" : "source-map",
|
|
||||||
plugins,
|
plugins,
|
||||||
module: {
|
module: {
|
||||||
rules
|
rules
|
||||||
@@ -206,6 +217,7 @@ module.exports = function(env = {}, argv = {}) {
|
|||||||
minimize: mode === "production" ? true : false,
|
minimize: mode === "production" ? true : false,
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({
|
new TerserPlugin({
|
||||||
|
sourceMap: true,
|
||||||
cache: ".cache/terser",
|
cache: ".cache/terser",
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
// These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory
|
// These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory
|
||||||
|
|||||||
Reference in New Issue
Block a user