Compare commits

...

16 Commits

Author SHA1 Message Date
Steve Faulkner
a6e4d1eaf9 Remove default 2020-09-25 18:13:11 -05:00
Steve Faulkner
2d3d96bcc7 xMerge branch 'master' into exclude-vendor-from-sourcemaps 2020-09-25 18:12:17 -05:00
Steve Faulkner
f2d4cfcef9 Fixes 2020-09-25 18:12:04 -05:00
Tanuj Mittal
70c7d84bdb Do not fail when trying to find DE window with cross origin (#231)
* Do not fail when trying to find DE window with cross origin

* Fix lint errors
2020-09-25 14:49:11 -07:00
Tanuj Mittal
dcc2036793 Fix hotfix syntax in workflow (#232) 2020-09-25 14:44:15 -07:00
Tanuj Mittal
987368fe58 Disable endtoendpuppeteer tests (temporarily) (#233) 2020-09-25 14:38:49 -07:00
Steve Faulkner
9ea588261e Don't generate source maps for vendor files 2020-09-25 14:46:51 -05:00
Steve Faulkner
91aa91d860 Cleanup extension endpoint loading (#224) 2020-09-24 18:10:54 -05:00
victor-meng
2e747a1a07 Fix create database for serverless accounts (#228) 2020-09-24 14:03:37 -07:00
Zachary Foster
290ca4aba5 Commits add table entity pane changes without steve's changes (#227) 2020-09-23 18:29:04 -04:00
victor-meng
28ceb18d73 Use SDK for reading database offer for Tables API (#226) 2020-09-23 13:32:47 -07:00
victor-meng
666a378b3b Fix resource tree refresh issue (#222) 2020-09-23 13:18:05 -07:00
Zachary Foster
13dafb9581 Adds cassandra e2e container CRUD test (#195)
* Starts cassandra

* Adds more cassandra

* wip

* Fix a few extra console.logs

* Format

* Adds cassandra connection string secret to ci

* Adds test name to failure screenshot

* Disable no-any on expect type, as it has getState() method

* Constantize some delays

* Accidentally deleted a brace
2020-09-22 16:21:57 -04:00
Tanuj Mittal
7c5c8ddb7a Fix incorrect usage of TelemetryProcessor (#221)
* Fix incorrect usage of TelemetryProcessor

* Address feedback
2020-09-22 12:19:06 -07:00
victor-meng
3f2c67af23 Fix some content becomes hidden after zooming to 200% (#223) 2020-09-22 11:59:44 -07:00
artrejo
3ae1f97ccc Remove AFEC check for Synapse Link and Mongo 2020-09-21 13:44:05 -07:00
32 changed files with 335 additions and 341 deletions

View File

@@ -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/')

View File

@@ -5,6 +5,7 @@ module.exports = {
headless: isCI,
slowMo: 50,
defaultViewport: null,
ignoreHTTPSErrors: true
ignoreHTTPSErrors: true,
args: ["--disable-web-security"]
}
};

View File

@@ -2078,7 +2078,7 @@ a:link {
.resourceTreeAndTabs {
display: flex;
flex: 1 1 auto;
overflow: hidden;
overflow: auto;
height: 100%;
}

View File

@@ -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";
}

View File

@@ -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());
}
}

View File

@@ -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();
});
});
});

View File

@@ -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;
}

View File

@@ -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");
});
});

View File

@@ -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");

View File

@@ -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;
};

View File

@@ -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, [

View File

@@ -292,7 +292,6 @@ export interface CreateCollectionParams {
export interface ReadDatabaseOfferParams {
databaseId: string;
databaseResourceId?: string;
isServerless?: boolean;
offerId?: string;
}

View File

@@ -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))}&`;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
});
});

View File

@@ -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("");
}

View File

@@ -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))
});
});
});
});

View File

@@ -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("");
}

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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";

View File

@@ -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) => {

View File

@@ -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

View File

@@ -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
};
}

View File

@@ -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");

View 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
View 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;
};

View 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;
}
});
});

View File

@@ -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;
}
})
})
}
});
});

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"allowJs": true,
"sourceMap": false,
"sourceMap": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,

View File

@@ -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