mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 03:41:19 +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:
|
||||
branches:
|
||||
- master
|
||||
- hotfix/*
|
||||
- release/*
|
||||
- hotfix/**
|
||||
- release/**
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
@@ -196,26 +196,6 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
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:
|
||||
name: Publish Nuget
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
headless: isCI,
|
||||
slowMo: 50,
|
||||
defaultViewport: null,
|
||||
ignoreHTTPSErrors: true
|
||||
ignoreHTTPSErrors: true,
|
||||
args: ["--disable-web-security"]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2078,7 +2078,7 @@ a:link {
|
||||
.resourceTreeAndTabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,89 +170,8 @@ export enum MongoBackendEndpointType {
|
||||
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
|
||||
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 guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
|
||||
public static readonly queryApi: string = "api/cassandra";
|
||||
@@ -562,3 +481,11 @@ export class AnalyticalStorageTtl {
|
||||
public static readonly Infinite: number = -1;
|
||||
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 {
|
||||
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 {
|
||||
if (uri && uri.slice(-1) !== "/") {
|
||||
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();
|
||||
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 * as _ from "underscore";
|
||||
import * as Constants from "./Constants";
|
||||
import { getDataExplorerWindow } from "../Utils/WindowUtils";
|
||||
|
||||
export interface CachedDataPromise<T> {
|
||||
deferred: Q.Deferred<T>;
|
||||
@@ -48,38 +49,18 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
||||
|
||||
export function sendMessage(data: any): void {
|
||||
if (canSendMessage()) {
|
||||
const dataExplorerWindow = getDataExplorerWindow(window);
|
||||
if (dataExplorerWindow) {
|
||||
dataExplorerWindow.parent.postMessage(
|
||||
{
|
||||
signature: "pcIframe",
|
||||
data: data
|
||||
},
|
||||
dataExplorerWindow.document.referrer
|
||||
);
|
||||
}
|
||||
// We try to find data explorer window first, then fallback to current window
|
||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
||||
portalChildWindow.parent.postMessage(
|
||||
{
|
||||
signature: "pcIframe",
|
||||
data: data
|
||||
},
|
||||
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 {
|
||||
return window.parent !== window;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { AuthType } from "../AuthType";
|
||||
import { configContext, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||
import { resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||
import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
||||
@@ -237,19 +236,19 @@ describe("MongoProxyClient", () => {
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
it("returns a development endpoint", () => {
|
||||
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");
|
||||
});
|
||||
|
||||
it("returns a guest endpoint", () => {
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import EnvironmentUtility from "./EnvironmentUtility";
|
||||
import { MinimalQueryIterator } from "./IteratorUtilities";
|
||||
import { sendMessage } from "./MessageHandler";
|
||||
|
||||
@@ -78,7 +77,7 @@ export function queryDocuments(
|
||||
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
|
||||
};
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount) || "";
|
||||
const endpoint = getEndpoint() || "";
|
||||
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
@@ -139,7 +138,7 @@ export function readDocument(
|
||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||
};
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
const endpoint = getEndpoint();
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
method: "GET",
|
||||
@@ -179,7 +178,7 @@ export function createDocument(
|
||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
|
||||
};
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
||||
@@ -221,7 +220,7 @@ export function updateDocument(
|
||||
pk:
|
||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||
};
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
@@ -260,7 +259,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
||||
pk:
|
||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
|
||||
};
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
@@ -303,7 +302,7 @@ export function createMongoCollectionWithProxy(
|
||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
||||
};
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
const endpoint = getEndpoint();
|
||||
|
||||
return window
|
||||
.fetch(
|
||||
@@ -327,12 +326,9 @@ export function createMongoCollectionWithProxy(
|
||||
});
|
||||
}
|
||||
|
||||
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
|
||||
const serverId = window.dataExplorer.serverId();
|
||||
export function getEndpoint(): string {
|
||||
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
|
||||
let url = configContext.MONGO_BACKEND_ENDPOINT
|
||||
? configContext.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer"
|
||||
: EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint);
|
||||
let url = (configContext.MONGO_BACKEND_ENDPOINT || extensionEndpoint) + "/api/mongo/explorer";
|
||||
|
||||
if (window.authType === AuthType.EncryptedToken) {
|
||||
url = url.replace("api/mongo", "api/guest/mongo");
|
||||
|
||||
@@ -16,7 +16,11 @@ export const readDatabaseOffer = async (
|
||||
): Promise<DataModels.OfferWithHeaders> => {
|
||||
let offerId = params.offerId;
|
||||
if (!offerId) {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
try {
|
||||
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
||||
} catch (error) {
|
||||
@@ -26,7 +30,7 @@ export const readDatabaseOffer = async (
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId, params.isServerless);
|
||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
||||
if (!offerId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -76,8 +80,8 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
|
||||
return rpResponse?.name;
|
||||
};
|
||||
|
||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string, isServerless: boolean): Promise<string> => {
|
||||
const offers = await readOffers(isServerless);
|
||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
||||
const offers = await readOffers();
|
||||
const offer = offers.find(offer => offer.resource === databaseResourceId);
|
||||
return offer?.id;
|
||||
};
|
||||
|
||||
@@ -6,11 +6,7 @@ import { client } from "../CosmosClient";
|
||||
import { sendCachedDataMessage } from "../MessageHandler";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readOffers = async (isServerless?: boolean): Promise<Offer[]> => {
|
||||
if (isServerless) {
|
||||
return []; // Reading offers is not supported for serverless accounts
|
||||
}
|
||||
|
||||
export const readOffers = async (): Promise<Offer[]> => {
|
||||
try {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
||||
|
||||
@@ -292,7 +292,6 @@ export interface CreateCollectionParams {
|
||||
export interface ReadDatabaseOfferParams {
|
||||
databaseId: string;
|
||||
databaseResourceId?: string;
|
||||
isServerless?: boolean;
|
||||
offerId?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as Logger from "../../../Common/Logger";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { StringUtils } from "../../../Utils/StringUtils";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
@@ -32,11 +34,11 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
|
||||
public getTerminalParams(): 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();
|
||||
if (terminalEndpoint) {
|
||||
params.set("terminalEndpoint", terminalEndpoint);
|
||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
||||
}
|
||||
|
||||
return params;
|
||||
@@ -75,11 +77,13 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
return "";
|
||||
}
|
||||
|
||||
params.set("server", serverInfo.notebookServerEndpoint);
|
||||
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
||||
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?";
|
||||
for (let key of params.keys()) {
|
||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
||||
|
||||
@@ -608,7 +608,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.container.isPreferredApiMongoDB() && this.container.hasStorageAnalyticsAfecFeature()) {
|
||||
if (this.container.isPreferredApiMongoDB()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -666,7 +666,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
const subscriptionType: ViewModels.SubscriptionType =
|
||||
this.container.subscriptionType && this.container.subscriptionType();
|
||||
|
||||
if (subscriptionType === ViewModels.SubscriptionType.EA) {
|
||||
if (subscriptionType === ViewModels.SubscriptionType.EA || this.container.isServerlessEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
const subscriptionType: ViewModels.SubscriptionType =
|
||||
this.container.subscriptionType && this.container.subscriptionType();
|
||||
|
||||
if (subscriptionType === ViewModels.SubscriptionType.EA) {
|
||||
if (subscriptionType === ViewModels.SubscriptionType.EA || this.container.isServerlessEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -134,11 +134,9 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
expect(telemetryProcessorSpy.called).toBe(true);
|
||||
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
||||
expect(
|
||||
telemetryProcessorSpy.calledWith(
|
||||
Action.DeleteCollection,
|
||||
ActionModifiers.Mark,
|
||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
)
|
||||
telemetryProcessorSpy.calledWith(Action.DeleteCollection, ActionModifiers.Mark, {
|
||||
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,11 +88,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
|
||||
this.containerDeleteFeedback()
|
||||
);
|
||||
|
||||
TelemetryProcessor.trace(
|
||||
Action.DeleteCollection,
|
||||
ActionModifiers.Mark,
|
||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
);
|
||||
TelemetryProcessor.trace(Action.DeleteCollection, ActionModifiers.Mark, {
|
||||
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
});
|
||||
|
||||
this.containerDeleteFeedback("");
|
||||
}
|
||||
|
||||
@@ -120,11 +120,9 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
|
||||
return pane.submit().then(() => {
|
||||
let deleteFeedback = new DeleteFeedback(SubscriptionId, AccountName, DataModels.ApiKind.SQL, Feedback);
|
||||
expect(TelemetryProcessor.trace).toHaveBeenCalledWith(
|
||||
Action.DeleteDatabase,
|
||||
ActionModifiers.Mark,
|
||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
);
|
||||
expect(TelemetryProcessor.trace).toHaveBeenCalledWith(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,11 +97,9 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
this.databaseDeleteFeedback()
|
||||
);
|
||||
|
||||
TelemetryProcessor.trace(
|
||||
Action.DeleteDatabase,
|
||||
ActionModifiers.Mark,
|
||||
JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
);
|
||||
TelemetryProcessor.trace(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback))
|
||||
});
|
||||
|
||||
this.databaseDeleteFeedback("");
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export default class AddTableEntityPane extends TableEntityPane {
|
||||
);
|
||||
this.updateIsActionEnabled();
|
||||
super.open();
|
||||
this.focusValueElement();
|
||||
});
|
||||
} else {
|
||||
this.displayedAttributes(
|
||||
@@ -79,8 +80,12 @@ export default class AddTableEntityPane extends TableEntityPane {
|
||||
);
|
||||
this.updateIsActionEnabled();
|
||||
super.open();
|
||||
this.focusValueElement();
|
||||
}
|
||||
const focusElement = document.getElementById("closeAddEntityPane");
|
||||
}
|
||||
|
||||
private focusValueElement() {
|
||||
const focusElement = document.getElementById("addTableEntityValue");
|
||||
focusElement && focusElement.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { AuthType } from "../../AuthType";
|
||||
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as Entities from "./Entities";
|
||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
@@ -308,7 +307,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestQueryApi
|
||||
: Constants.CassandraBackend.queryApi;
|
||||
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`, {
|
||||
$.ajax(`${collection.container.extensionEndpoint()}${apiEndpoint}`, {
|
||||
type: "POST",
|
||||
data: {
|
||||
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
||||
@@ -559,7 +558,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestKeysApi
|
||||
: Constants.CassandraBackend.keysApi;
|
||||
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
|
||||
let endpoint = `${collection.container.extensionEndpoint()}${apiEndpoint}`;
|
||||
const deferred = Q.defer<CassandraTableKeys>();
|
||||
$.ajax(endpoint, {
|
||||
type: "POST",
|
||||
@@ -614,7 +613,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestSchemaApi
|
||||
: Constants.CassandraBackend.schemaApi;
|
||||
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
|
||||
let endpoint = `${collection.container.extensionEndpoint()}${apiEndpoint}`;
|
||||
const deferred = Q.defer<CassandraTableKey[]>();
|
||||
$.ajax(endpoint, {
|
||||
type: "POST",
|
||||
@@ -668,7 +667,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
authType === AuthType.EncryptedToken
|
||||
? Constants.CassandraBackend.guestCreateOrDeleteApi
|
||||
: Constants.CassandraBackend.createOrDeleteApi;
|
||||
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(explorer)}${apiEndpoint}`, {
|
||||
$.ajax(`${explorer.extensionEndpoint()}${apiEndpoint}`, {
|
||||
type: "POST",
|
||||
data: {
|
||||
accountName: explorer.databaseAccount() && explorer.databaseAccount().name,
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as Constants from "../../Common/Constants";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
|
||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
||||
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
||||
import Q from "q";
|
||||
import TabsBase from "./TabsBase";
|
||||
@@ -109,11 +108,7 @@ export default class MongoShellTab extends TabsBase {
|
||||
) + Constants.MongoDBAccounts.defaultPort.toString();
|
||||
const databaseId = this.collection.databaseId;
|
||||
const collectionId = this.collection.id();
|
||||
const apiEndpoint = EnvironmentUtility.getMongoBackendEndpoint(
|
||||
this._container.serverId(),
|
||||
userContext.databaseAccount.location,
|
||||
this._container.extensionEndpoint()
|
||||
).replace("/api/mongo/explorer", "");
|
||||
const apiEndpoint = this._container.extensionEndpoint();
|
||||
const encryptedAuthToken: string = userContext.accessToken;
|
||||
|
||||
shellIframe.contentWindow.postMessage(
|
||||
@@ -142,7 +137,7 @@ export default class MongoShellTab extends TabsBase {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataToLog: string = event.data.data.logData;
|
||||
const dataToLog = { message: event.data.data.logData };
|
||||
const logType: string = event.data.data.logType;
|
||||
const shellTraceId: string = event.data.data.traceId || "none";
|
||||
|
||||
|
||||
@@ -200,11 +200,10 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
public async loadOffer(): Promise<void> {
|
||||
if (!this.offer()) {
|
||||
if (!this.container.isServerlessEnabled() && !this.offer()) {
|
||||
const params: DataModels.ReadDatabaseOfferParams = {
|
||||
databaseId: this.id(),
|
||||
databaseResourceId: this.self,
|
||||
isServerless: this.container.isServerlessEnabled()
|
||||
databaseResourceId: this.self
|
||||
};
|
||||
this.offer(await readDatabaseOffer(params));
|
||||
}
|
||||
@@ -290,6 +289,10 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
|
||||
if (collectionsToRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionsToKeep: Collection[] = [];
|
||||
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
|
||||
@@ -64,7 +64,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
this.container.nonSystemDatabases.subscribe((databases: ViewModels.Database[]) => {
|
||||
// Clean up old databases
|
||||
this.cleanupDatabasesKoSubs(databases.map((database: ViewModels.Database) => database.id()));
|
||||
this.cleanupDatabasesKoSubs();
|
||||
|
||||
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||
this.triggerRender();
|
||||
@@ -799,16 +799,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.koSubsCollectionIdMap.push(collectionId, sub);
|
||||
}
|
||||
|
||||
private cleanupDatabasesKoSubs(existingDatabaseIds: string[]): void {
|
||||
const databaseIdsToRemove = this.databaseCollectionIdMap
|
||||
.keys()
|
||||
.filter((id: string) => existingDatabaseIds.indexOf(id) === -1);
|
||||
|
||||
databaseIdsToRemove.forEach((databaseId: string) => {
|
||||
if (this.koSubsDatabaseIdMap.has(databaseId)) {
|
||||
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||
this.koSubsDatabaseIdMap.delete(databaseId);
|
||||
}
|
||||
private cleanupDatabasesKoSubs(): void {
|
||||
this.koSubsDatabaseIdMap.keys().forEach((databaseId: string) => {
|
||||
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||
this.koSubsDatabaseIdMap.delete(databaseId);
|
||||
|
||||
if (this.databaseCollectionIdMap.has(databaseId)) {
|
||||
this.databaseCollectionIdMap
|
||||
|
||||
@@ -4,12 +4,15 @@ import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { appInsights } from "../appInsights";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getDataExplorerWindow } from "../../Utils/WindowUtils";
|
||||
|
||||
/**
|
||||
* 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({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
data: {
|
||||
@@ -22,7 +25,7 @@ export function trace(action: Action, actionModifier: string = ActionModifiers.M
|
||||
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();
|
||||
sendMessage({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
@@ -38,7 +41,7 @@ export function traceStart(action: Action, data?: unknown): number {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
export function traceSuccess(action: Action, data?: unknown, timestamp?: number): void {
|
||||
export function traceSuccess(action: Action, data?: TelemetryData, timestamp?: number): void {
|
||||
sendMessage({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
data: {
|
||||
@@ -52,7 +55,7 @@ export function traceSuccess(action: Action, data?: unknown, timestamp?: number)
|
||||
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({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
data: {
|
||||
@@ -66,7 +69,7 @@ export function traceFailure(action: Action, data?: unknown, timestamp?: number)
|
||||
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({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
data: {
|
||||
@@ -80,7 +83,7 @@ export function traceCancel(action: Action, data?: unknown, timestamp?: number):
|
||||
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();
|
||||
sendMessage({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
@@ -96,7 +99,7 @@ export function traceOpen(action: Action, data?: unknown, timestamp?: number): n
|
||||
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();
|
||||
sendMessage({
|
||||
type: MessageTypes.TelemetryInfo,
|
||||
@@ -112,21 +115,16 @@ export function traceMark(action: Action, data?: unknown, timestamp?: number): n
|
||||
return validTimestamp;
|
||||
}
|
||||
|
||||
function getData(actionModifier: string, data: unknown = {}): { [key: string]: string } | undefined {
|
||||
if (typeof data === "string") {
|
||||
data = { message: data };
|
||||
}
|
||||
if (typeof data === "object") {
|
||||
return {
|
||||
// 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
|
||||
authType: (window as any).authType,
|
||||
subscriptionId: userContext.subscriptionId as string,
|
||||
platform: configContext.platform,
|
||||
env: process.env.NODE_ENV as string,
|
||||
actionModifier,
|
||||
...data
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
function getData(actionModifier: string, data: TelemetryData = {}): { [key: string]: string } {
|
||||
const dataExplorerWindow = getDataExplorerWindow(window);
|
||||
return {
|
||||
// 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
|
||||
authType: dataExplorerWindow && (dataExplorerWindow as any).authType,
|
||||
subscriptionId: userContext.subscriptionId as string,
|
||||
platform: configContext.platform,
|
||||
env: process.env.NODE_ENV as string,
|
||||
actionModifier,
|
||||
...data
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ServerConnection } from "@jupyterlab/services";
|
||||
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
|
||||
import { Action } from "../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { TerminalQueryParams } from "../Common/Constants";
|
||||
|
||||
const getUrlVars = (): { [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 => {
|
||||
let body: BodyInit;
|
||||
if (urlVars.hasOwnProperty("terminalEndpoint")) {
|
||||
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
|
||||
body = JSON.stringify({
|
||||
endpoint: urlVars["terminalEndpoint"]
|
||||
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint]
|
||||
});
|
||||
}
|
||||
|
||||
const server = urlVars["server"];
|
||||
const server = urlVars[TerminalQueryParams.Server];
|
||||
let options: Partial<ServerConnection.ISettings> = {
|
||||
baseUrl: server,
|
||||
init: { body },
|
||||
fetch: window.parent.fetch
|
||||
};
|
||||
if (urlVars.hasOwnProperty("token")) {
|
||||
if (urlVars.hasOwnProperty(TerminalQueryParams.Token)) {
|
||||
options = {
|
||||
baseUrl: server,
|
||||
token: urlVars["token"],
|
||||
token: urlVars[TerminalQueryParams.Token],
|
||||
init: { body },
|
||||
fetch: window.parent.fetch
|
||||
};
|
||||
@@ -44,6 +46,12 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const urlVars = getUrlVars();
|
||||
|
||||
// Initialize userContext. Currently only subscrptionId is required by TelemetryProcessor
|
||||
updateUserContext({
|
||||
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId]
|
||||
});
|
||||
|
||||
const serverSettings = createServerSettings(urlVars);
|
||||
|
||||
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, {
|
||||
@@ -51,7 +59,7 @@ const main = async (): Promise<void> => {
|
||||
});
|
||||
|
||||
try {
|
||||
if (urlVars.hasOwnProperty("terminal")) {
|
||||
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
|
||||
await JupyterLabAppFactory.createTerminalApp(serverSettings);
|
||||
} else {
|
||||
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 crypto from 'crypto'
|
||||
import crypto from "crypto";
|
||||
|
||||
jest.setTimeout(300000);
|
||||
|
||||
describe('Collection Add and Delete SQL spec', () => {
|
||||
it('creates a collection', async () => {
|
||||
describe("Collection Add and Delete SQL spec", () => {
|
||||
it("creates a collection", async () => {
|
||||
try {
|
||||
const dbId = `TestDatabase${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);
|
||||
|
||||
// log in with connection string
|
||||
const handle = await page.waitForSelector('iframe');
|
||||
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');
|
||||
await frame.waitFor("div > p.switchConnectTypeText", { visible: true });
|
||||
await frame.click("div > p.switchConnectTypeText");
|
||||
const connStr = process.env.PORTAL_RUNNER_CONNECTION_STRING;
|
||||
await frame.type("input[class='inputToken']", connStr);
|
||||
await frame.click("input[value='Connect']");
|
||||
@@ -25,30 +25,30 @@ describe('Collection Add and Delete SQL spec', () => {
|
||||
await frame.waitFor('button[data-test="New Container"]', { visible: true });
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
await frame.click('button[data-test="New Container"]');
|
||||
|
||||
|
||||
// check new database
|
||||
await frame.waitFor('input[data-test="addCollection-createNewDatabase"]');
|
||||
await frame.click('input[data-test="addCollection-createNewDatabase"]');
|
||||
|
||||
// check shared throughput
|
||||
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
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
await frame.type('input[data-test="addCollection-newDatabaseId"]', dbId);
|
||||
await frame.type('input[data-test="addCollection-newDatabaseId"]', dbId);
|
||||
|
||||
// type collection id
|
||||
await frame.waitFor('input[data-test="addCollection-collectionId"]');
|
||||
await frame.type('input[data-test="addCollection-collectionId"]', collectionId);
|
||||
await frame.type('input[data-test="addCollection-collectionId"]', collectionId);
|
||||
|
||||
// type partition key value
|
||||
await frame.waitFor('input[data-test="addCollection-partitionKeyValue"]');
|
||||
await frame.type('input[data-test="addCollection-partitionKeyValue"]', sharedKey);
|
||||
|
||||
// click submit
|
||||
await frame.waitFor('#submitBtnAddCollection');
|
||||
await frame.click('#submitBtnAddCollection');
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
|
||||
// validate created
|
||||
// open database menu
|
||||
@@ -56,27 +56,27 @@ describe('Collection Add and Delete SQL spec', () => {
|
||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||
|
||||
await frame.click(`div[data-test="${dbId}"]`);
|
||||
await frame.waitFor(3000);
|
||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
||||
await frame.waitFor(3000)
|
||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
||||
|
||||
|
||||
// delete container
|
||||
|
||||
// click context menu for container
|
||||
await frame.waitFor(`div[data-test="${collectionId}"] > div > button`, { visible: true });
|
||||
await frame.waitFor(`span[title="${collectionId}"]`, { visible: true });
|
||||
await frame.click(`div[data-test="${collectionId}"] > div > button`);
|
||||
await frame.waitFor(2000)
|
||||
await frame.waitFor(2000);
|
||||
|
||||
// click delete container
|
||||
await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]', { visible: true });
|
||||
await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
|
||||
|
||||
// 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());
|
||||
|
||||
// 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.waitFor(5000);
|
||||
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 expect(page).not.toMatchElement(`div[data-test="${dbId}"]`);
|
||||
} 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;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"sourceMap": false,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
@@ -11,6 +11,7 @@ const childProcess = require("child_process");
|
||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const isCI = require("is-ci");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
|
||||
|
||||
@@ -104,6 +105,15 @@ module.exports = function(env = {}, argv = {}) {
|
||||
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 = [
|
||||
new CleanWebpackPlugin(["dist"]),
|
||||
new CreateFileWebpack({
|
||||
@@ -164,7 +174,9 @@ module.exports = function(env = {}, argv = {}) {
|
||||
new CopyWebpackPlugin({
|
||||
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) {
|
||||
@@ -194,7 +206,6 @@ module.exports = function(env = {}, argv = {}) {
|
||||
filename: "[name].[chunkhash:6].js",
|
||||
path: path.resolve(__dirname, "dist")
|
||||
},
|
||||
devtool: mode === "development" ? "cheap-eval-source-map" : "source-map",
|
||||
plugins,
|
||||
module: {
|
||||
rules
|
||||
@@ -206,6 +217,7 @@ module.exports = function(env = {}, argv = {}) {
|
||||
minimize: mode === "production" ? true : false,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true,
|
||||
cache: ".cache/terser",
|
||||
terserOptions: {
|
||||
// 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