Compare commits

..

1 Commits

Author SHA1 Message Date
kcheekuri
87eb38e645 Clean computeV2 code 2022-01-19 11:50:00 -05:00
36 changed files with 697 additions and 38418 deletions

6
.vscode/launch.json vendored
View File

@@ -12,8 +12,7 @@
"--inspect-brk",
"${workspaceRoot}/node_modules/jest/bin/jest.js",
"--runInBand",
"--coverage",
"false"
"--coverage", "false"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
@@ -27,8 +26,7 @@
"--inspect-brk",
"${workspaceRoot}/node_modules/jest/bin/jest.js",
"${fileBasenameNoExtension}",
"--coverage",
"false",
"--coverage", "false",
// "--watch",
// // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints.
// // https://github.com/facebook/jest/issues/6683

View File

@@ -1,5 +1,4 @@
{
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
"isTerminalEnabled" : true,
"isPhoenixEnabled" : true
"isTerminalEnabled" : true
}

View File

@@ -1,5 +1,4 @@
{
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
"isTerminalEnabled" : false,
"isPhoenixEnabled" : false
"isTerminalEnabled" : false
}

31921
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -236,12 +236,13 @@ describe("MongoProxyClient", () => {
});
it("returns a production endpoint", () => {
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
});
it("returns a development endpoint", () => {
const endpoint = getEndpoint("https://localhost:1234");
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
});
@@ -249,7 +250,7 @@ describe("MongoProxyClient", () => {
updateUserContext({
authType: AuthType.EncryptedToken,
});
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
});
});

View File

@@ -1,6 +1,5 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import queryString from "querystring";
import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation";
import { AuthType } from "../AuthType";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
@@ -337,17 +336,14 @@ export function createMongoCollectionWithProxy(
}
export function getFeatureEndpointOrDefault(feature: string): string {
const endpoint =
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
? userContext.features.mongoProxyEndpoint
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
return getEndpoint(endpoint);
return hasFlag(userContext.features.mongoProxyAPIs, feature)
? getEndpoint(userContext.features.mongoProxyEndpoint)
: getEndpoint();
}
export function getEndpoint(endpoint: string): string {
let url = endpoint + "/api/mongo/explorer";
export function getEndpoint(customEndpoint?: string): string {
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
url += "/api/mongo/explorer";
if (userContext.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo");

View File

@@ -1,16 +1,4 @@
import {
allowedAadEndpoints,
allowedArcadiaEndpoints,
allowedArmEndpoints,
allowedBackendEndpoints,
allowedEmulatorEndpoints,
allowedGraphEndpoints,
allowedHostedExplorerEndpoints,
allowedJunoOrigins,
allowedMongoBackendEndpoints,
allowedMsalRedirectEndpoints,
validateEndpoint,
} from "Utils/EndpointValidation";
import { JunoEndpoints } from "Common/Constants";
export enum Platform {
Portal = "Portal",
@@ -20,7 +8,7 @@ export enum Platform {
export interface ConfigContext {
platform: Platform;
allowedParentFrameOrigins: ReadonlyArray<string>;
allowedParentFrameOrigins: string[];
gitSha?: string;
proxyPath?: string;
AAD_ENDPOINT: string;
@@ -40,9 +28,9 @@ export interface ConfigContext {
GITHUB_TEST_ENV_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
isTerminalEnabled: boolean;
isPhoenixEnabled: boolean;
hostedExplorerURL: string;
armAPIVersion?: string;
allowedJunoOrigins: string[];
msalRedirectURI?: string;
}
@@ -52,11 +40,12 @@ let configContext: Readonly<ConfigContext> = {
allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure\\.de$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
], // Webpack injects this at build time
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
],
// Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/",
@@ -72,7 +61,14 @@ let configContext: Readonly<ConfigContext> = {
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
isTerminalEnabled: false,
isPhoenixEnabled: false,
allowedJunoOrigins: [
JunoEndpoints.Test,
JunoEndpoints.Test2,
JunoEndpoints.Test3,
JunoEndpoints.Prod,
JunoEndpoints.Stage,
"https://localhost",
],
};
export function resetConfigContext(): void {
@@ -83,50 +79,6 @@ export function resetConfigContext(): void {
}
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
if (!newContext) {
return;
}
if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) {
delete newContext.ARM_ENDPOINT;
}
if (!validateEndpoint(newContext.AAD_ENDPOINT, allowedAadEndpoints)) {
delete newContext.AAD_ENDPOINT;
}
if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) {
delete newContext.EMULATOR_ENDPOINT;
}
if (!validateEndpoint(newContext.GRAPH_ENDPOINT, allowedGraphEndpoints)) {
delete newContext.GRAPH_ENDPOINT;
}
if (!validateEndpoint(newContext.ARCADIA_ENDPOINT, allowedArcadiaEndpoints)) {
delete newContext.ARCADIA_ENDPOINT;
}
if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) {
delete newContext.BACKEND_ENDPOINT;
}
if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) {
delete newContext.MONGO_BACKEND_ENDPOINT;
}
if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) {
delete newContext.JUNO_ENDPOINT;
}
if (!validateEndpoint(newContext.hostedExplorerURL, allowedHostedExplorerEndpoints)) {
delete newContext.hostedExplorerURL;
}
if (!validateEndpoint(newContext.msalRedirectURI, allowedMsalRedirectEndpoints)) {
delete newContext.msalRedirectURI;
}
Object.assign(configContext, newContext);
}
@@ -150,8 +102,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
});
if (response.status === 200) {
try {
const { ...externalConfig } = await response.json();
updateConfigContext(externalConfig);
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
Object.assign(configContext, externalConfig);
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
updateConfigContext({
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
});
}
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
updateConfigContext({
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
});
}
} catch (error) {
console.error("Unable to parse json in config file");
console.error(error);

View File

@@ -450,24 +450,6 @@ export interface IResponse<T> {
data: T;
}
export interface IValidationError {
message: string;
type: string;
}
export interface IMaxAllocationTimeExceeded extends IValidationError {
earliestAllocationTimestamp: string;
maxAllocationTimePerDayPerUserInMinutes: string;
}
export interface IMaxDbAccountsPerUserExceeded extends IValidationError {
maxSimultaneousConnectionsPerUser: string;
}
export interface IMaxUsersPerDbAccountExceeded extends IValidationError {
maxSimultaneousUsersPerDbAccount: string;
}
export interface IPhoenixConnectionInfoResult {
readonly notebookAuthToken?: string;
readonly notebookServerUrl?: string;
@@ -549,12 +531,3 @@ export interface ContainerConnectionInfo {
status: ConnectionStatusType;
//need to add ram and rom info
}
export enum PhoenixErrorType {
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
MaxUsersPerDbAccountExceeded = "MaxUsersPerDbAccountExceeded",
AllocationValidationResult = "AllocationValidationResult",
RegionNotServicable = "RegionNotServicable",
SubscriptionNotAllowed = "SubscriptionNotAllowed",
}

View File

@@ -149,7 +149,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.offer = this.database?.offer();
}
const initialState: SettingsComponentState = {
this.state = {
throughput: undefined,
throughputBaseline: undefined,
autoPilotThroughput: undefined,
@@ -199,12 +199,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
selectedTab: SettingsV2TabTypes.ScaleTab,
};
this.state = {
...initialState,
...this.getBaselineValues(),
...this.getAutoscaleBaselineValues(),
};
this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => {
@@ -231,6 +225,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.loadMongoIndexes();
}
this.setAutoPilotStates();
this.setBaseline();
if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
@@ -291,24 +286,17 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
);
};
private getAutoscaleBaselineValues = (): Partial<SettingsComponentState> => {
private setAutoPilotStates = (): void => {
const autoscaleMaxThroughput = this.offer?.autoscaleMaxThroughput;
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
return {
this.setState({
isAutoPilotSelected: true,
wasAutopilotOriginallySet: true,
autoPilotThroughput: autoscaleMaxThroughput,
autoPilotThroughputBaseline: autoscaleMaxThroughput,
};
});
}
return {
isAutoPilotSelected: false,
wasAutopilotOriginallySet: false,
autoPilotThroughput: undefined,
autoPilotThroughputBaseline: undefined,
};
};
public hasProvisioningTypeChanged = (): boolean =>
@@ -573,25 +561,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
};
public setBaseline = (): void => {
const baselineValues = this.getBaselineValues();
const autoscaleBaselineValues = this.getAutoscaleBaselineValues();
this.setState({ ...baselineValues, ...autoscaleBaselineValues } as SettingsComponentState);
};
private getBaselineValues = (): Partial<SettingsComponentState> => {
const offerThroughput = this.offer?.manualThroughput;
if (!this.isCollectionSettingsTab) {
return {
this.setState({
throughput: offerThroughput,
throughputBaseline: offerThroughput,
};
});
return;
}
const defaultTtl = this.collection.defaultTtl();
let timeToLive: TtlType;
let timeToLiveSeconds: number;
let timeToLive: TtlType = this.state.timeToLive;
let timeToLiveSeconds = this.state.timeToLiveSeconds;
switch (defaultTtl) {
case undefined:
case 0:
@@ -636,7 +620,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
return {
this.setState({
throughput: offerThroughput,
throughputBaseline: offerThroughput,
changeFeedPolicy: changeFeedPolicy,
@@ -659,7 +643,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
geospatialConfigType: geoSpatialConfigType,
geospatialConfigTypeBaseline: geoSpatialConfigType,
};
});
};
private getTabsButtons = (): CommandButtonComponentProps[] => {

View File

@@ -19,7 +19,7 @@ import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/Telemet
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../../../UserContext";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
@@ -540,7 +540,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
step={AutoPilotUtils.autoPilotIncrementStep}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
onChange={this.onAutoPilotThroughputChange}
min={autoPilotThroughput1K}
min={minAutoPilotThroughput}
errorMessage={this.props.throughputError}
/>
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}

View File

@@ -145,7 +145,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
id="autopilotInput"
key="auto pilot throughput input"
label="Max RU/s"
min={1000}
min={4000}
onChange={[Function]}
required={true}
step={1000}

View File

@@ -5,7 +5,6 @@ const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: true,
isFreeTier: false,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
setIsThroughputCapExceeded: () => jest.fn(),

View File

@@ -14,7 +14,6 @@ import "./ThroughputInput.less";
export interface ThroughputInputProps {
isDatabase: boolean;
isSharded: boolean;
isFreeTier: boolean;
showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
@@ -24,18 +23,15 @@ export interface ThroughputInputProps {
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
isDatabase,
isSharded,
isFreeTier,
showFreeTierExceedThroughputTooltip,
setThroughputValue,
setIsAutoscale,
setIsThroughputCapExceeded,
isSharded,
onCostAcknowledgeChange,
}: ThroughputInputProps) => {
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
const [throughput, setThroughput] = useState<number>(
isFreeTier ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K
);
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
const [throughputError, setThroughputError] = useState<string>("");
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
@@ -155,14 +151,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
if (mode === "Autoscale") {
const defaultThroughput = isFreeTier
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K;
setThroughput(defaultThroughput);
setThroughput(AutoPilotUtils.minAutoPilotThroughput);
setIsAutoScaleSelected(true);
setThroughputValue(defaultThroughput);
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
setIsAutoscale(true);
checkThroughputCap(defaultThroughput);
checkThroughputCap(AutoPilotUtils.minAutoPilotThroughput);
} else {
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
setIsAutoScaleSelected(false);
@@ -233,7 +226,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
}}
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.autoPilotThroughput1K}
min={AutoPilotUtils.minAutoPilotThroughput}
value={throughput.toString()}
aria-label="Max request units per second"
required={true}

View File

@@ -3,7 +3,6 @@
exports[`ThroughputInput Pane should render Default properly 1`] = `
<ThroughputInput
isDatabase={false}
isFreeTier={false}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
@@ -1638,7 +1637,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Max request units per second"
errorMessage=""
key=".0:$.2"
min={1000}
min={4000}
onChange={[Function]}
required={true}
step={1000}
@@ -1660,7 +1659,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Max request units per second"
deferredValidationTime={200}
errorMessage=""
min={1000}
min={4000}
onChange={[Function]}
required={true}
resizable={true}
@@ -1956,7 +1955,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-invalid={false}
className="ms-TextField-field field-64"
id="TextField2"
min={1000}
min={4000}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}

View File

@@ -1,10 +1,8 @@
import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { IGalleryItem } from "Juno/JunoClient";
import * as ko from "knockout";
import React from "react";
import _ from "underscore";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
import shallow from "zustand/shallow";
import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
@@ -26,6 +24,7 @@ import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs";
import { IGalleryItem } from "../Juno/JunoClient";
import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@@ -47,7 +46,7 @@ import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import type NotebookManager from "./Notebook/NotebookManager";
import { NotebookPaneContent } from "./Notebook/NotebookManager";
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
import { NotebookUtil } from "./Notebook/NotebookUtil";
import { useNotebook } from "./Notebook/useNotebook";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
@@ -174,11 +173,7 @@ export default class Explorer {
this.resourceTree = new ResourceTreeAdapter(this);
// Override notebook server parameters from URL parameters
if (
userContext.features.notebookServerUrl &&
validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
userContext.features.notebookServerToken
) {
if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) {
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl,
authToken: userContext.features.notebookServerToken,
@@ -190,6 +185,19 @@ export default class Explorer {
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
}
if (userContext.features.livyEndpoint) {
useNotebook.getState().setSparkClusterConnectionInfo({
userName: undefined,
password: undefined,
endpoints: [
{
endpoint: userContext.features.livyEndpoint,
kind: DataModels.SparkClusterEndpointKind.Livy,
},
],
});
}
this.refreshExplorer();
}
@@ -362,13 +370,12 @@ export default class Explorer {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
let connectionInfo;
try {
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
dataExplorerArea: Areas.Notebook,
});
useNotebook.getState().setIsAllocating(true);
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
if (connectionInfo.status !== HttpStatusCodes.OK) {
throw new Error(`Received status code: ${connectionInfo?.status}`);
}
@@ -387,16 +394,6 @@ export default class Explorer {
});
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetContainerConnection(connectionStatus);
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
);
}
throw error;
} finally {
useNotebook.getState().setIsAllocating(false);
@@ -420,10 +417,7 @@ export default class Explorer {
connectionStatus.status = ConnectionStatusType.Connected;
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint:
(validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
userContext.features.notebookServerUrl) ||
connectionInfo.data.notebookServerUrl,
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
forwardingId: connectionInfo.data.forwardingId,
});
@@ -1249,10 +1243,7 @@ export default class Explorer {
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled =
userContext.features.notebooksDownBanner ||
useNotebook.getState().isPhoenixNotebooks ||
useNotebook.getState().isPhoenixFeatures;
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenixNotebooks;
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook
.getState()

View File

@@ -1,7 +1,6 @@
/**
* Notebook container related stuff
*/
import { useDialog } from "Explorer/Controls/Dialog";
import promiseRetry, { AbortError } from "p-retry";
import { PhoenixClient } from "Phoenix/PhoenixClient";
import * as Constants from "../../Common/Constants";
@@ -20,7 +19,6 @@ export class NotebookContainerClient {
private isResettingWorkspace: boolean;
private phoenixClient: PhoenixClient;
private retryOptions: promiseRetry.Options;
private scheduleTimerId: NodeJS.Timeout;
constructor(private onConnectionLost: () => void) {
this.phoenixClient = new PhoenixClient();
@@ -29,36 +27,36 @@ export class NotebookContainerClient {
maxTimeout: Notebook.retryAttemptDelayMs,
minTimeout: Notebook.retryAttemptDelayMs,
};
this.initHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
private initHeartbeat(delayMs: number): void {
this.scheduleHeartbeat(delayMs);
useNotebook.subscribe(
() => this.scheduleHeartbeat(delayMs),
(state) => state.notebookServerInfo
);
}
private scheduleHeartbeat(delayMs: number) {
if (this.scheduleTimerId) {
clearInterval(this.scheduleTimerId);
}
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
this.scheduleTimerId = setInterval(async () => {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
const memoryUsageInfo = await this.getMemoryUsage();
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
}
}, delayMs);
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
} else {
const unsub = useNotebook.subscribe(
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
if (newServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
unsub();
},
(state) => state.notebookServerInfo
);
}
}
/**
* Heartbeat: each ping schedules another ping
*/
private scheduleHeartbeat(delayMs: number): void {
setTimeout(async () => {
const memoryUsageInfo = await this.getMemoryUsage();
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
}, delayMs);
}
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
@@ -160,16 +158,6 @@ export class NotebookContainerClient {
return null;
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
);
}
throw error;
}
}

View File

@@ -303,8 +303,8 @@ export class NotebookContentClient {
private getServerConfig(): ServerConfig {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
return {
endpoint: notebookServerInfo?.notebookServerEndpoint,
token: notebookServerInfo?.authToken,
endpoint: notebookServerInfo.notebookServerEndpoint,
token: notebookServerInfo.authToken,
crossDomain: true,
};
}

View File

@@ -249,7 +249,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={true}
isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
@@ -484,7 +483,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={false}
isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>

View File

@@ -147,7 +147,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
if (isAutoscaleSelected) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
setFormErrors(
`Please enter a value greater than ${AutoPilotUtils.autoPilotThroughput1K} for autopilot throughput`
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
);
return false;
}
@@ -241,7 +241,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={true}
isSharded={databaseCreateNewShared}
isFreeTier={isFreeTierAccount}
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}

View File

@@ -262,7 +262,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
}
isDatabase
isSharded
isFreeTier={isFreeTierAccount}
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
@@ -336,7 +335,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false}
isSharded
isFreeTier={isFreeTierAccount}
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}

View File

@@ -10,6 +10,7 @@ import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUti
import Explorer from "../../Explorer";
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
import { useNotebook } from "../../Notebook/useNotebook";
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
@@ -103,14 +104,11 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
switch (location.type) {
case "MyNotebooks":
parent = {
name: useNotebook.getState().notebookFolderName,
name: ResourceTreeAdapter.MyNotebooksTitle,
path: useNotebook.getState().notebookBasePath,
type: NotebookContentItemType.Directory,
};
isGithubTree = false;
if (useNotebook.getState().isPhoenixNotebooks) {
await container.allocateContainer();
}
break;
case "GitHub":

View File

@@ -35,60 +35,6 @@ const {
Smallint,
Tinyint,
Timestamp,
// List
List_Ascii,
List_Bigint,
List_Blob,
List_Boolean,
List_Date,
List_Decimal,
List_Double,
List_Float,
List_Int,
List_Text,
List_Timestamp,
List_Uuid,
List_Varchar,
List_Varint,
List_Inet,
List_Smallint,
List_Tinyint,
// Map
Map_Ascii,
Map_Bigint,
Map_Blob,
Map_Boolean,
Map_Date,
Map_Decimal,
Map_Double,
Map_Float,
Map_Int,
Map_Text,
Map_Timestamp,
Map_Uuid,
Map_Varchar,
Map_Varint,
Map_Inet,
Map_Smallint,
Map_Tinyint,
// Set
Set_Ascii,
Set_Bigint,
Set_Blob,
Set_Boolean,
Set_Date,
Set_Decimal,
Set_Double,
Set_Float,
Set_Int,
Set_Text,
Set_Timestamp,
Set_Uuid,
Set_Varchar,
Set_Varint,
Set_Inet,
Set_Smallint,
Set_Tinyint,
} = TableConstants.CassandraType;
export const cassandraOptions = [
{ key: Text, text: Text },
@@ -108,60 +54,6 @@ export const cassandraOptions = [
{ key: Smallint, text: Smallint },
{ key: Tinyint, text: Tinyint },
{ key: Timestamp, text: Timestamp },
// List
{ key: List_Ascii, text: List_Ascii },
{ key: List_Bigint, text: List_Bigint },
{ key: List_Blob, text: List_Blob },
{ key: List_Boolean, text: List_Boolean },
{ key: List_Date, text: List_Date },
{ key: List_Decimal, text: List_Decimal },
{ key: List_Double, text: List_Double },
{ key: List_Float, text: List_Float },
{ key: List_Int, text: List_Int },
{ key: List_Text, text: List_Text },
{ key: List_Timestamp, text: List_Timestamp },
{ key: List_Uuid, text: List_Uuid },
{ key: List_Varchar, text: List_Varchar },
{ key: List_Varint, text: List_Varint },
{ key: List_Inet, text: List_Inet },
{ key: List_Smallint, text: List_Smallint },
{ key: List_Tinyint, text: List_Tinyint },
// Map
{ key: Map_Ascii, text: Map_Ascii },
{ key: Map_Bigint, text: Map_Bigint },
{ key: Map_Blob, text: Map_Blob },
{ key: Map_Boolean, text: Map_Boolean },
{ key: Map_Date, text: Map_Date },
{ key: Map_Decimal, text: Map_Decimal },
{ key: Map_Double, text: Map_Double },
{ key: Map_Float, text: Map_Float },
{ key: Map_Int, text: Map_Int },
{ key: Map_Text, text: Map_Text },
{ key: Map_Timestamp, text: Map_Timestamp },
{ key: Map_Uuid, text: Map_Uuid },
{ key: Map_Varchar, text: Map_Varchar },
{ key: Map_Varint, text: Map_Varint },
{ key: Map_Inet, text: Map_Inet },
{ key: Map_Smallint, text: Map_Smallint },
{ key: Map_Tinyint, text: Map_Tinyint },
// Set
{ key: Set_Ascii, text: Set_Ascii },
{ key: Set_Bigint, text: Set_Bigint },
{ key: Set_Blob, text: Set_Blob },
{ key: Set_Boolean, text: Set_Boolean },
{ key: Set_Date, text: Set_Date },
{ key: Set_Decimal, text: Set_Decimal },
{ key: Set_Double, text: Set_Double },
{ key: Set_Float, text: Set_Float },
{ key: Set_Int, text: Set_Int },
{ key: Set_Text, text: Set_Text },
{ key: Set_Timestamp, text: Set_Timestamp },
{ key: Set_Uuid, text: Set_Uuid },
{ key: Set_Varchar, text: Set_Varchar },
{ key: Set_Varint, text: Set_Varint },
{ key: Set_Inet, text: Set_Inet },
{ key: Set_Smallint, text: Set_Smallint },
{ key: Set_Tinyint, text: Set_Tinyint },
];
export const imageProps: IImageProps = {

View File

@@ -27,60 +27,6 @@ export const CassandraType = {
Inet: "Inet",
Smallint: "Smallint",
Tinyint: "Tinyint",
List_Ascii: "List<Ascii>",
List_Bigint: "List<Bigint>",
List_Blob: "List<Blob>",
List_Boolean: "List<Boolean>",
List_Date: "List<Date>",
List_Decimal: "List<Decimal>",
List_Double: "List<Double>",
List_Float: "List<Float>",
List_Int: "List<Int>",
List_Text: "List<Text>",
List_Timestamp: "List<Timestamp>",
List_Uuid: "List<Uuid>",
List_Varchar: "List<Varchar>",
List_Varint: "List<Varint>",
List_Inet: "List<Inet>",
List_Smallint: "List<Smallint>",
List_Tinyint: "List<Tinyint>",
Map_Ascii: "Map<Ascii>",
Map_Bigint: "Map<Bigint>",
Map_Blob: "Map<Blob>",
Map_Boolean: "Map<Boolean>",
Map_Date: "Map<Date>",
Map_Decimal: "Map<Decimal>",
Map_Double: "Map<Double>",
Map_Float: "Map<Float>",
Map_Int: "Map<Int>",
Map_Text: "Map<Text>",
Map_Timestamp: "Map<Timestamp>",
Map_Uuid: "Map<Uuid>",
Map_Varchar: "Map<Varchar>",
Map_Varint: "Map<Varint>",
Map_Inet: "Map<Inet>",
Map_Smallint: "Map<Smallint>",
Map_Tinyint: "Map<Tinyint>",
Set_Ascii: "Set<Ascii>",
Set_Bigint: "Set<Bigint>",
Set_Blob: "Set<Blob>",
Set_Boolean: "Set<Boolean>",
Set_Date: "Set<Date>",
Set_Decimal: "Set<Decimal>",
Set_Double: "Set<Double>",
Set_Float: "Set<Float>",
Set_Int: "Set<Int>",
Set_Text: "Set<Text>",
Set_Timestamp: "Set<Timestamp>",
Set_Uuid: "Set<Uuid>",
Set_Varchar: "Set<Varchar>",
Set_Varint: "Set<Varint>",
Set_Inet: "Set<Inet>",
Set_Smallint: "Set<Smallint>",
Set_Tinyint: "Set<Tinyint>",
};
export const ClauseRule = {

View File

@@ -215,13 +215,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
{
metric: "Request Charge",
value: this.state.requestChargeDisplayText,
toolTip: "Request Charge",
toolTip: "",
isQueryMetricsEnabled: true,
},
{
metric: "Showing Results",
value: this.state.showingDocumentsDisplayText,
toolTip: "Showing Results",
toolTip: "",
isQueryMetricsEnabled: true,
},
{

View File

@@ -308,7 +308,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.rawDataModel.id + " - Items",
tabTitle: "Items",
});
this.documentIds([]);
@@ -316,7 +316,7 @@ export default class Collection implements ViewModels.Collection {
partitionKey: this.partitionKey,
documentIds: ko.observableArray<DocumentId>([]),
tabKind: ViewModels.CollectionTabKind.Documents,
title: this.rawDataModel.id + " - Items",
title: "Items",
collection: this,
node: this,
tabPath: `${this.databaseId}>${this.id()}>Documents`,

View File

@@ -1,5 +1,4 @@
import ko from "knockout";
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
import { GetGithubClientId } from "Utils/GitHubUtils";
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
import { configContext } from "../ConfigContext";
@@ -485,7 +484,7 @@ export class JunoClient {
// public for tests
public static getJunoEndpoint(): string {
const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
if (!validateEndpoint(junoEndpoint, allowedJunoOrigins)) {
if (configContext.allowedJunoOrigins.indexOf(new URL(junoEndpoint).origin) === -1) {
const error = `${junoEndpoint} not allowed as juno endpoint`;
console.error(error);
throw new Error(error);

View File

@@ -1,7 +1,5 @@
import { useDialog } from "Explorer/Controls/Dialog";
import promiseRetry, { AbortError } from "p-retry";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
import {
Areas,
ConnectionStatusType,
@@ -17,14 +15,9 @@ import {
ContainerConnectionInfo,
ContainerInfo,
IContainerData,
IMaxAllocationTimeExceeded,
IMaxDbAccountsPerUserExceeded,
IMaxUsersPerDbAccountExceeded,
IPhoenixConnectionInfoResult,
IProvisionData,
IResponse,
IValidationError,
PhoenixErrorType,
} from "../Contracts/DataModels";
import { useNotebook } from "../Explorer/Notebook/useNotebook";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
@@ -51,25 +44,23 @@ export class PhoenixClient {
provisionData: IProvisionData,
operation: string
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
let response;
try {
response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
method: operation === "allocate" ? "POST" : "PATCH",
headers: PhoenixClient.getHeaders(),
body: JSON.stringify(provisionData),
});
const responseJson = await response?.json();
if (response.status === HttpStatusCodes.Forbidden) {
throw new Error(this.ConvertToForbiddenErrorString(responseJson));
let data: IPhoenixConnectionInfoResult;
if (response.status === HttpStatusCodes.OK) {
data = await response.json();
}
return {
status: response.status,
data: responseJson,
data,
};
} catch (error) {
if (response.status === HttpStatusCodes.Forbidden) {
error.status = HttpStatusCodes.Forbidden;
}
console.error(error);
throw error;
}
}
@@ -116,18 +107,6 @@ export class PhoenixClient {
});
useNotebook.getState().resetContainerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
useDialog
.getState()
.showOkModalDialog(
"Disconnected",
"Disconnected from temporary workspace. Please click on connect button to connect to temporary workspace."
);
throw new AbortError(response.statusText);
} else if (response?.status === HttpStatusCodes.Forbidden) {
const validationMessage = this.ConvertToForbiddenErrorString(await response.json());
if (validationMessage) {
useDialog.getState().showOkModalDialog("Connection Failed", `${validationMessage}`);
}
throw new AbortError(response.statusText);
}
throw new Error(response.statusText);
@@ -175,7 +154,7 @@ export class PhoenixClient {
public static getPhoenixEndpoint(): string {
const phoenixEndpoint =
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) {
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
console.error(error);
throw new Error(error);
@@ -197,48 +176,4 @@ export class PhoenixClient {
[HttpHeaders.contentType]: "application/json",
};
}
public ConvertToForbiddenErrorString(jsonData: IValidationError): string {
const errInfo = jsonData;
switch (errInfo?.type) {
case PhoenixErrorType.MaxAllocationTimeExceeded: {
const maxAllocationTimeExceeded = errInfo as IMaxAllocationTimeExceeded;
const allocateAfterTimestamp = new Date(maxAllocationTimeExceeded?.earliestAllocationTimestamp);
allocateAfterTimestamp.setDate(allocateAfterTimestamp.getDate() + 1);
return (
`${errInfo.message}` +
" Max allocation time for a day to a user is " +
`${maxAllocationTimeExceeded.maxAllocationTimePerDayPerUserInMinutes}` +
". Please try again after " +
`${allocateAfterTimestamp.toLocaleString()}`
);
}
case PhoenixErrorType.MaxDbAccountsPerUserExceeded: {
const maxDbAccountsPerUserExceeded = errInfo as IMaxDbAccountsPerUserExceeded;
return (
`${errInfo.message}` +
" Max simultaneous connections allowed per user is " +
`${maxDbAccountsPerUserExceeded.maxSimultaneousConnectionsPerUser}` +
"."
);
}
case PhoenixErrorType.MaxUsersPerDbAccountExceeded: {
const maxUsersPerDbAccountExceeded = errInfo as IMaxUsersPerDbAccountExceeded;
return (
`${errInfo.message}` +
" Max simultaneous users allowed per DbAccount is " +
`${maxUsersPerDbAccountExceeded.maxSimultaneousUsersPerDbAccount}` +
"."
);
}
case PhoenixErrorType.AllocationValidationResult:
case PhoenixErrorType.RegionNotServicable:
case PhoenixErrorType.SubscriptionNotAllowed: {
return `${errInfo.message}`;
}
default: {
return undefined;
}
}
}
}

View File

@@ -1,5 +1,4 @@
export type Features = {
// set only via feature flags
readonly canExceedMaximumValue: boolean;
readonly cosmosdb: boolean;
readonly enableChangeFeedPolicy: boolean;
@@ -9,6 +8,12 @@ export type Features = {
readonly enableReactPane: boolean;
readonly enableRightPanelV2: boolean;
readonly enableSchema: boolean;
autoscaleDefault: boolean;
partitionKeyDefault: boolean;
partitionKeyDefault2: boolean;
phoenixNotebooks: boolean;
phoenixFeatures: boolean;
notebooksDownBanner: boolean;
readonly enableSDKoperations: boolean;
readonly enableSpark: boolean;
readonly enableTtl: boolean;
@@ -18,6 +23,7 @@ export type Features = {
readonly hostedDataExplorer: boolean;
readonly junoEndpoint?: string;
readonly phoenixEndpoint?: string;
readonly livyEndpoint?: string;
readonly notebookBasePath?: string;
readonly notebookServerToken?: string;
readonly notebookServerUrl?: string;
@@ -29,21 +35,11 @@ export type Features = {
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
partitionKeyDefault: boolean;
partitionKeyDefault2: boolean;
phoenixNotebooks?: boolean;
phoenixFeatures?: boolean;
notebooksDownBanner: boolean;
};
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
const downcased = new URLSearchParams();
const set = (value: string, key: string) => {
downcased.set(key.toLowerCase(), value);
};
const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
const get = (key: string, defaultValue?: string) =>
downcased.get("feature." + key) ?? downcased.get(key) ?? defaultValue;
@@ -76,6 +72,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
mongoProxyAPIs: get("mongoproxyapis"),
junoEndpoint: get("junoendpoint"),
phoenixEndpoint: get("phoenixendpoint"),
livyEndpoint: get("livyendpoint"),
notebookBasePath: get("notebookbasepath"),
notebookServerToken: get("notebookservertoken"),
notebookServerUrl: get("notebookserverurl"),
@@ -87,6 +84,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
autoscaleDefault: "true" === get("autoscaledefault"),
partitionKeyDefault: "true" === get("partitionkeytest"),
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
phoenixNotebooks: "true" === get("phoenixnotebooks"),
phoenixFeatures: "true" === get("phoenixfeatures"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
};

View File

@@ -1,12 +1,12 @@
export const autoPilotThroughput1K = 1000;
export const minAutoPilotThroughput = 4000;
export const autoPilotIncrementStep = 1000;
export const autoPilotThroughput4K = 4000;
export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
if (!maxThroughput) {
return false;
}
if (maxThroughput < autoPilotThroughput1K) {
if (maxThroughput < minAutoPilotThroughput) {
return false;
}
if (maxThroughput % 1000) {

View File

@@ -1,87 +0,0 @@
import { JunoEndpoints } from "Common/Constants";
import * as Logger from "../Common/Logger";
export function validateEndpoint(
endpointToValidate: string | undefined,
allowedEndpoints: ReadonlyArray<string>
): boolean {
try {
return validateEndpointInternal(
endpointToValidate,
allowedEndpoints.map((e) => e)
);
} catch (reason) {
Logger.logError(`${endpointToValidate} not allowed`, "validateEndpoint");
Logger.logError(`${JSON.stringify(reason)}`, "validateEndpoint");
return false;
}
}
function validateEndpointInternal(
endpointToValidate: string | undefined,
allowedEndpoints: ReadonlyArray<string>
): boolean {
if (endpointToValidate === undefined) {
return false;
}
const originToValidate: string = new URL(endpointToValidate).origin;
const allowedOrigins: string[] = allowedEndpoints.map((allowedEndpoint) => new URL(allowedEndpoint).origin) || [];
const valid = allowedOrigins.indexOf(originToValidate) >= 0;
if (!valid) {
throw new Error(
`${endpointToValidate} is not an allowed endpoint. Allowed endpoints are ${allowedEndpoints.toString()}`
);
}
return valid;
}
export const allowedArmEndpoints: ReadonlyArray<string> = [
"https://management.azure.com",
"https://management.usgovcloudapi.net",
"https://management.chinacloudapi.cn",
];
export const allowedAadEndpoints: ReadonlyArray<string> = ["https://login.microsoftonline.com/"];
export const allowedBackendEndpoints: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
"https://localhost:12901",
"https://localhost:1234",
];
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
"https://localhost:12901",
];
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081"];
export const allowedMongoBackendEndpoints: ReadonlyArray<string> = ["https://localhost:1234"];
export const allowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.windows.net"];
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];
export const allowedHostedExplorerEndpoints: ReadonlyArray<string> = ["https://cosmos.azure.com/"];
export const allowedMsalRedirectEndpoints: ReadonlyArray<string> = [
"https://cosmos-explorer-preview.azurewebsites.net/",
];
export const allowedJunoOrigins: ReadonlyArray<string> = [
JunoEndpoints.Test,
JunoEndpoints.Test2,
JunoEndpoints.Test3,
JunoEndpoints.Prod,
JunoEndpoints.Stage,
"https://localhost",
];
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];

View File

@@ -2,28 +2,26 @@ import { isInvalidParentFrameOrigin, isReadyMessage } from "./MessageValidation"
describe("isInvalidParentFrameOrigin", () => {
test.each`
domain | expected
${"https://cosmos.azure.com"} | ${false}
${"https://cosmos.azure.us"} | ${false}
${"https://cosmos.azure.cn"} | ${false}
${"https://portal.azure.com"} | ${false}
${"https://portal.azure.us"} | ${false}
${"https://portal.azure.cn"} | ${false}
${"https://portal.microsoftazure.de"} | ${false}
${"https://subdomain.portal.azure.com"} | ${false}
${"https://subdomain.portal.azure.us"} | ${false}
${"https://subdomain.portal.azure.cn"} | ${false}
${"https://main.documentdb.ext.azure.com"} | ${false}
${"https://main.documentdb.ext.azure.us"} | ${false}
${"https://main.documentdb.ext.azure.cn"} | ${false}
${"https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de"} | ${false}
${"https://main.documentdb.ext.microsoftazure.de"} | ${false}
${"https://random.domain"} | ${true}
${"https://malicious.cloudapp.azure.com"} | ${true}
${"https://malicious.germanycentral.cloudapp.microsoftazure.de"} | ${true}
${"https://maliciousazure.com"} | ${true}
${"https://maliciousportalsazure.com"} | ${true}
${"https://cosmos-db-dataexplorer-germanycentralAazurewebsites.de"} | ${true}
domain | expected
${"https://cosmos.azure.com"} | ${false}
${"https://cosmos.azure.us"} | ${false}
${"https://cosmos.azure.cn"} | ${false}
${"https://portal.azure.com"} | ${false}
${"https://portal.azure.us"} | ${false}
${"https://portal.azure.cn"} | ${false}
${"https://portal.microsoftazure.de"} | ${false}
${"https://subdomain.portal.azure.com"} | ${false}
${"https://subdomain.portal.azure.us"} | ${false}
${"https://subdomain.portal.azure.cn"} | ${false}
${"https://main.documentdb.ext.azure.com"} | ${false}
${"https://main.documentdb.ext.azure.us"} | ${false}
${"https://main.documentdb.ext.azure.cn"} | ${false}
${"https://main.documentdb.ext.microsoftazure.de"} | ${false}
${"https://random.domain"} | ${true}
${"https://malicious.cloudapp.azure.com"} | ${true}
${"https://malicious.germanycentral.cloudapp.microsoftazure.de"} | ${true}
${"https://maliciousazure.com"} | ${true}
${"https://maliciousportalsazure.com"} | ${true}
`("returns $expected when called with $domain", ({ domain, expected }) => {
expect(isInvalidParentFrameOrigin({ origin: domain } as MessageEvent)).toBe(expected);
});

View File

@@ -4,7 +4,7 @@ export function isInvalidParentFrameOrigin(event: MessageEvent): boolean {
return !isValidOrigin(configContext.allowedParentFrameOrigins, event);
}
function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEvent): boolean {
function isValidOrigin(allowedOrigins: string[], event: MessageEvent): boolean {
const eventOrigin = (event && event.origin) || "";
const windowOrigin = (window && window.origin) || "";
if (eventOrigin === windowOrigin) {

View File

@@ -351,11 +351,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.features) {
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
}
//Updating phoenix feature flags for MPAC based of config context
if (configContext.isPhoenixEnabled === true) {
userContext.features.phoenixNotebooks = true;
userContext.features.phoenixFeatures = true;
}
if (inputs.flights) {
if (inputs.flights.indexOf(Flights.AutoscaleTest) !== -1) {
userContext.features.autoscaleDefault;