Compare commits

..

4 Commits

Author SHA1 Message Date
victor-meng
2e3e547a46 Fix scale tab not showing the correct throughput mode and value and remove freetierautoscalethroughput feature flag (#1245) 2022-03-31 12:13:08 -07:00
Karthik chakravarthy
06f6df83ad Add UX for error Information for Phoenix workspace connection (#1234)
* Add UX for error Information

* update text messages
2022-03-30 08:59:37 -04:00
victor-meng
8b22027cb6 Fix settings tab shows no selected value (#1237) 2022-03-25 15:13:56 -07:00
Armando Trejo Oliver
496f596f38 Fix Parent Origin Regex (#1239)
Not all regex are escaped properly
2022-03-25 12:59:18 -07:00
19 changed files with 31581 additions and 618 deletions

31921
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -99,7 +99,6 @@ export class Flights {
public static readonly PhoenixNotebooks = "phoenixnotebooks";
public static readonly PhoenixFeatures = "phoenixfeatures";
public static readonly NotebooksDownBanner = "notebooksdownbanner";
public static readonly FreeTierAutoscaleThroughput = "freetierautoscalethroughput";
}
export class AfecFeatures {

View File

@@ -52,10 +52,10 @@ 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$`,
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
], // Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",

View File

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

View File

@@ -213,7 +213,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
);
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off" },
{ key: TtlType.Off, text: "Off", disabled: true },
{ key: TtlType.OnNoDefault, text: "On (no default)" },
{ key: TtlType.On, text: "On" },
];

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, autoPilotThroughput4K } from "../../../../../Utils/AutoPilotUtils";
import { autoPilotThroughput1K } 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={userContext.features.freetierAutoscaleThroughput ? autoPilotThroughput1K : autoPilotThroughput4K}
min={autoPilotThroughput1K}
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={4000}
min={1000}
onChange={[Function]}
required={true}
step={1000}

View File

@@ -363,6 +363,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
options={
Array [
Object {
"disabled": true,
"key": "off",
"text": "Off",
},
@@ -639,6 +640,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
options={
Array [
Object {
"disabled": true,
"key": "off",
"text": "Off",
},
@@ -877,6 +879,7 @@ exports[`SubSettingsComponent renders 1`] = `
options={
Array [
Object {
"disabled": true,
"key": "off",
"text": "Off",
},
@@ -1153,6 +1156,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
options={
Array [
Object {
"disabled": true,
"key": "off",
"text": "Off",
},

View File

@@ -34,9 +34,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
}: ThroughputInputProps) => {
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
const [throughput, setThroughput] = useState<number>(
isFreeTier && userContext.features.freetierAutoscaleThroughput
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K
isFreeTier ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K
);
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
const [throughputError, setThroughputError] = useState<string>("");
@@ -157,10 +155,9 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
if (mode === "Autoscale") {
const defaultThroughput =
isFreeTier && userContext.features.freetierAutoscaleThroughput
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K;
const defaultThroughput = isFreeTier
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K;
setThroughput(defaultThroughput);
setIsAutoScaleSelected(true);
setThroughputValue(defaultThroughput);
@@ -236,11 +233,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
}}
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={
userContext.features.freetierAutoscaleThroughput
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K
}
min={AutoPilotUtils.autoPilotThroughput1K}
value={throughput.toString()}
aria-label="Max request units per second"
required={true}

View File

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

View File

@@ -362,12 +362,13 @@ 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);
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
if (connectionInfo.status !== HttpStatusCodes.OK) {
throw new Error(`Received status code: ${connectionInfo?.status}`);
}
@@ -386,12 +387,16 @@ export default class Explorer {
});
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetContainerConnection(connectionStatus);
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."
);
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);

View File

@@ -1,6 +1,7 @@
/**
* 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";
@@ -159,6 +160,16 @@ 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

@@ -146,10 +146,9 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
// TODO add feature flag that disables validation for customers with custom accounts
if (isAutoscaleSelected) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
const minAutoPilotThroughput = userContext.features.freetierAutoscaleThroughput
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K;
setFormErrors(`Please enter a value greater than ${minAutoPilotThroughput} for autopilot throughput`);
setFormErrors(
`Please enter a value greater than ${AutoPilotUtils.autoPilotThroughput1K} for autopilot throughput`
);
return false;
}
}

View File

@@ -1,3 +1,4 @@
import { useDialog } from "Explorer/Controls/Dialog";
import promiseRetry, { AbortError } from "p-retry";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
@@ -16,9 +17,14 @@ 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";
@@ -45,23 +51,25 @@ export class PhoenixClient {
provisionData: IProvisionData,
operation: string
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
let response;
try {
const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
method: operation === "allocate" ? "POST" : "PATCH",
headers: PhoenixClient.getHeaders(),
body: JSON.stringify(provisionData),
});
let data: IPhoenixConnectionInfoResult;
if (response.status === HttpStatusCodes.OK) {
data = await response.json();
const responseJson = await response?.json();
if (response.status === HttpStatusCodes.Forbidden) {
throw new Error(this.ConvertToForbiddenErrorString(responseJson));
}
return {
status: response.status,
data,
data: responseJson,
};
} catch (error) {
console.error(error);
if (response.status === HttpStatusCodes.Forbidden) {
error.status = HttpStatusCodes.Forbidden;
}
throw error;
}
}
@@ -108,6 +116,18 @@ 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);
@@ -177,4 +197,48 @@ 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

@@ -37,7 +37,6 @@ export type Features = {
phoenixNotebooks?: boolean;
phoenixFeatures?: boolean;
notebooksDownBanner: boolean;
freetierAutoscaleThroughput: boolean;
};
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
@@ -90,7 +89,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
freetierAutoscaleThroughput: "true" === get("freetierautoscalethroughput"),
};
}

View File

@@ -1,5 +1,3 @@
import { userContext } from "UserContext";
export const autoPilotThroughput1K = 1000;
export const autoPilotIncrementStep = 1000;
export const autoPilotThroughput4K = 4000;
@@ -8,10 +6,7 @@ export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
if (!maxThroughput) {
return false;
}
const minAutoPilotThroughput = userContext.features.freetierAutoscaleThroughput
? autoPilotThroughput4K
: autoPilotThroughput1K;
if (maxThroughput < minAutoPilotThroughput) {
if (maxThroughput < autoPilotThroughput1K) {
return false;
}
if (maxThroughput % 1000) {

View File

@@ -2,26 +2,28 @@ 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://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}
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}
`("returns $expected when called with $domain", ({ domain, expected }) => {
expect(isInvalidParentFrameOrigin({ origin: domain } as MessageEvent)).toBe(expected);
});

View File

@@ -378,9 +378,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) {
userContext.features.notebooksDownBanner = true;
}
if (inputs.flights.indexOf(Flights.FreeTierAutoscaleThroughput) !== -1) {
userContext.features.freetierAutoscaleThroughput = true;
}
}
}