Remove Explorer.features (#563)
In addition this makes the URL-passed feature flags type safe
This commit is contained in:
parent
b1aeab6b84
commit
f33ec09040
|
@ -98,31 +98,6 @@ export class CapabilityNames {
|
||||||
public static readonly EnableServerless: string = "EnableServerless";
|
public static readonly EnableServerless: string = "EnableServerless";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Features {
|
|
||||||
public static readonly cosmosdb = "cosmosdb";
|
|
||||||
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
|
||||||
public static readonly executeSproc = "dataexplorerexecutesproc";
|
|
||||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
|
||||||
public static readonly enableTtl = "enablettl";
|
|
||||||
public static readonly enableNotebooks = "enablenotebooks";
|
|
||||||
public static readonly enableSpark = "enablespark";
|
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
|
||||||
public static readonly notebookServerUrl = "notebookserverurl";
|
|
||||||
public static readonly notebookServerToken = "notebookservertoken";
|
|
||||||
public static readonly notebookBasePath = "notebookbasepath";
|
|
||||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
|
||||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
|
||||||
public static readonly ttl90Days = "ttl90days";
|
|
||||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
|
||||||
public static readonly enableSchema = "enableschema";
|
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
|
||||||
public static readonly showMinRUSurvey = "showminrusurvey";
|
|
||||||
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
|
|
||||||
public static readonly selfServeType = "selfservetype";
|
|
||||||
public static readonly enableKOPanel = "enablekopanel";
|
|
||||||
public static readonly enableReactPane = "enablereactpane";
|
|
||||||
}
|
|
||||||
|
|
||||||
// flight names returned from the portal are always lowercase
|
// flight names returned from the portal are always lowercase
|
||||||
export class Flights {
|
export class Flights {
|
||||||
public static readonly SettingsV2 = "settingsv2";
|
public static readonly SettingsV2 = "settingsv2";
|
||||||
|
|
|
@ -376,7 +376,6 @@ export interface DataExplorerInputsFrame {
|
||||||
masterKey?: string;
|
masterKey?: string;
|
||||||
hasWriteAccess?: boolean;
|
hasWriteAccess?: boolean;
|
||||||
authorizationToken?: string;
|
authorizationToken?: string;
|
||||||
features: { [key: string]: string };
|
|
||||||
csmEndpoint?: string;
|
csmEndpoint?: string;
|
||||||
dnsSuffix?: string;
|
dnsSuffix?: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
|
|
|
@ -139,9 +139,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||||
this.shouldShowIndexingPolicyEditor =
|
this.shouldShowIndexingPolicyEditor =
|
||||||
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
|
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled(
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
Constants.Features.enableChangeFeedPolicy
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Features } from "../../../../../Common/Constants";
|
|
||||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../../Shared/Constants";
|
||||||
|
@ -465,7 +464,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||||
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
|
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
|
||||||
const oneTBinKB = 1000000000;
|
const oneTBinKB = 1000000000;
|
||||||
const minRUperGB = 10;
|
const minRUperGB = 10;
|
||||||
const featureFlagEnabled = window.dataExplorer?.isFeatureEnabled(Features.showMinRUSurvey);
|
const featureFlagEnabled = userContext.features.showMinRUSurvey;
|
||||||
const collectionIsEligible =
|
const collectionIsEligible =
|
||||||
userContext.subscriptionType !== SubscriptionType.Internal &&
|
userContext.subscriptionType !== SubscriptionType.Internal &&
|
||||||
this.props.usageSizeInKB > oneTBinKB &&
|
this.props.usageSizeInKB > oneTBinKB &&
|
||||||
|
|
|
@ -895,7 +895,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"validPartitionKeyValue": [Function],
|
"validPartitionKeyValue": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"features": [Function],
|
|
||||||
"flight": [Function],
|
"flight": [Function],
|
||||||
"graphStylingPane": GraphStylingPane {
|
"graphStylingPane": GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
@ -2092,7 +2091,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"validPartitionKeyValue": [Function],
|
"validPartitionKeyValue": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"features": [Function],
|
|
||||||
"flight": [Function],
|
"flight": [Function],
|
||||||
"graphStylingPane": GraphStylingPane {
|
"graphStylingPane": GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
@ -3302,7 +3300,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"validPartitionKeyValue": [Function],
|
"validPartitionKeyValue": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"features": [Function],
|
|
||||||
"flight": [Function],
|
"flight": [Function],
|
||||||
"graphStylingPane": GraphStylingPane {
|
"graphStylingPane": GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
@ -4499,7 +4496,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"validPartitionKeyValue": [Function],
|
"validPartitionKeyValue": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"features": [Function],
|
|
||||||
"flight": [Function],
|
"flight": [Function],
|
||||||
"graphStylingPane": GraphStylingPane {
|
"graphStylingPane": GraphStylingPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|
|
@ -329,7 +329,7 @@ export default class Explorer {
|
||||||
this.isNotebookEnabled(
|
this.isNotebookEnabled(
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
|
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
|
||||||
this.isFeatureEnabled(Constants.Features.enableNotebooks))
|
userContext.features.enableNotebooks)
|
||||||
);
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
|
@ -351,7 +351,7 @@ export default class Explorer {
|
||||||
this.isSparkEnabledForAccount() &&
|
this.isSparkEnabledForAccount() &&
|
||||||
this.arcadiaWorkspaces() &&
|
this.arcadiaWorkspaces() &&
|
||||||
this.arcadiaWorkspaces().length > 0) ||
|
this.arcadiaWorkspaces().length > 0) ||
|
||||||
this.isFeatureEnabled(Constants.Features.enableSpark)
|
userContext.features.enableSpark
|
||||||
);
|
);
|
||||||
if (this.isSparkEnabled()) {
|
if (this.isSparkEnabled()) {
|
||||||
appInsights.trackEvent(
|
appInsights.trackEvent(
|
||||||
|
@ -375,7 +375,6 @@ export default class Explorer {
|
||||||
});
|
});
|
||||||
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
||||||
|
|
||||||
this.features = ko.observable();
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
|
|
||||||
this.resourceTokenDatabaseId = ko.observable<string>();
|
this.resourceTokenDatabaseId = ko.observable<string>();
|
||||||
|
@ -387,11 +386,9 @@ export default class Explorer {
|
||||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
|
|
||||||
this.canExceedMaximumValue = ko.computed<boolean>(() =>
|
this.canExceedMaximumValue = ko.computed<boolean>(() => userContext.features.canExceedMaximumValue);
|
||||||
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.isSchemaEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSchema));
|
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
||||||
|
|
||||||
this.isAutoscaleDefaultEnabled = ko.observable<boolean>(false);
|
this.isAutoscaleDefaultEnabled = ko.observable<boolean>(false);
|
||||||
|
|
||||||
|
@ -471,7 +468,7 @@ export default class Explorer {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
||||||
if (this.isFeatureEnabled(Constants.Features.enableFixedCollectionWithSharedThroughput)) {
|
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,9 +527,7 @@ export default class Explorer {
|
||||||
() =>
|
() =>
|
||||||
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
|
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
|
||||||
);
|
);
|
||||||
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
|
this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2);
|
||||||
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
|
|
||||||
);
|
|
||||||
this.defaultExperience.subscribe((defaultExperience: string) => {
|
this.defaultExperience.subscribe((defaultExperience: string) => {
|
||||||
if (
|
if (
|
||||||
defaultExperience &&
|
defaultExperience &&
|
||||||
|
@ -883,42 +878,29 @@ export default class Explorer {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override notebook server parameters from URL parameters
|
// Override notebook server parameters from URL parameters
|
||||||
const featureSubcription = this.features.subscribe((features) => {
|
if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) {
|
||||||
const serverInfo = this.notebookServerInfo();
|
this.notebookServerInfo({
|
||||||
if (this.isFeatureEnabled(Constants.Features.notebookServerUrl)) {
|
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
||||||
serverInfo.notebookServerEndpoint = features[Constants.Features.notebookServerUrl];
|
authToken: userContext.features.notebookServerToken,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isFeatureEnabled(Constants.Features.notebookServerToken)) {
|
if (userContext.features.notebookBasePath) {
|
||||||
serverInfo.authToken = features[Constants.Features.notebookServerToken];
|
this.notebookBasePath(userContext.features.notebookBasePath);
|
||||||
}
|
}
|
||||||
this.notebookServerInfo(serverInfo);
|
|
||||||
this.notebookServerInfo.valueHasMutated();
|
|
||||||
|
|
||||||
if (this.isFeatureEnabled(Constants.Features.notebookBasePath)) {
|
if (userContext.features.livyEndpoint) {
|
||||||
this.notebookBasePath(features[Constants.Features.notebookBasePath]);
|
this.sparkClusterConnectionInfo({
|
||||||
}
|
userName: undefined,
|
||||||
|
password: undefined,
|
||||||
if (this.isFeatureEnabled(Constants.Features.livyEndpoint)) {
|
endpoints: [
|
||||||
this.sparkClusterConnectionInfo({
|
{
|
||||||
userName: undefined,
|
endpoint: userContext.features.livyEndpoint,
|
||||||
password: undefined,
|
kind: DataModels.SparkClusterEndpointKind.Livy,
|
||||||
endpoints: [
|
},
|
||||||
{
|
],
|
||||||
endpoint: features[Constants.Features.livyEndpoint],
|
});
|
||||||
kind: DataModels.SparkClusterEndpointKind.Livy,
|
}
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
this.sparkClusterConnectionInfo.valueHasMutated();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
|
|
||||||
updateUserContext({ useSDKOperations: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
featureSubcription.dispose();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openEnableSynapseLinkDialog(): void {
|
public openEnableSynapseLinkDialog(): void {
|
||||||
|
@ -1002,20 +984,6 @@ export default class Explorer {
|
||||||
return this.selectedNode() == null;
|
return this.selectedNode() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFeatureEnabled(feature: string): boolean {
|
|
||||||
const features = this.features();
|
|
||||||
|
|
||||||
if (!features) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feature in features && features[feature]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public logConsoleData(consoleData: ConsoleData): void {
|
public logConsoleData(consoleData: ConsoleData): void {
|
||||||
this.setNotificationConsoleData(consoleData);
|
this.setNotificationConsoleData(consoleData);
|
||||||
}
|
}
|
||||||
|
@ -1258,12 +1226,12 @@ export default class Explorer {
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
// Overwrite with feature flags
|
// Overwrite with feature flags
|
||||||
if (this.isFeatureEnabled(Constants.Features.notebookServerUrl)) {
|
if (userContext.features.notebookServerUrl) {
|
||||||
connectionInfo.notebookServerEndpoint = this.features()[Constants.Features.notebookServerUrl];
|
connectionInfo.notebookServerEndpoint = userContext.features.notebookServerUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isFeatureEnabled(Constants.Features.notebookServerToken)) {
|
if (userContext.features.notebookServerToken) {
|
||||||
connectionInfo.authToken = this.features()[Constants.Features.notebookServerToken];
|
connectionInfo.authToken = userContext.features.notebookServerToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notebookServerInfo(connectionInfo);
|
this.notebookServerInfo(connectionInfo);
|
||||||
|
@ -1413,7 +1381,6 @@ export default class Explorer {
|
||||||
if (inputs.defaultCollectionThroughput) {
|
if (inputs.defaultCollectionThroughput) {
|
||||||
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
|
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
|
||||||
}
|
}
|
||||||
this.features(inputs.features);
|
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
||||||
this.hasWriteAccess(inputs.hasWriteAccess ?? true);
|
this.hasWriteAccess(inputs.hasWriteAccess ?? true);
|
||||||
|
@ -2367,7 +2334,7 @@ export default class Explorer {
|
||||||
public onNewCollectionClicked(): void {
|
public onNewCollectionClicked(): void {
|
||||||
if (this.isPreferredApiCassandra()) {
|
if (this.isPreferredApiCassandra()) {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.cassandraAddCollectionPane.open();
|
||||||
} else if (this.isFeatureEnabled(Constants.Features.enableReactPane)) {
|
} else if (userContext.features.enableReactPane) {
|
||||||
this.openAddCollectionPanel();
|
this.openAddCollectionPanel();
|
||||||
} else {
|
} else {
|
||||||
this.addCollectionPane.open(this.selectedDatabaseId());
|
this.addCollectionPane.open(this.selectedDatabaseId());
|
||||||
|
@ -2501,7 +2468,7 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public openDeleteCollectionConfirmationPane(): void {
|
public openDeleteCollectionConfirmationPane(): void {
|
||||||
this.isFeatureEnabled(Constants.Features.enableKOPanel)
|
userContext.features.enableKOPanel
|
||||||
? this.deleteCollectionConfirmationPane.open()
|
? this.deleteCollectionConfirmationPane.open()
|
||||||
: this.openSidePanel(
|
: this.openSidePanel(
|
||||||
"Delete Collection",
|
"Delete Collection",
|
||||||
|
|
|
@ -994,7 +994,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||||
this.container.openEnableSynapseLinkDialog();
|
this.container.openEnableSynapseLinkDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ttl90DaysEnabled: () => boolean = () => this.container.isFeatureEnabled(Constants.Features.ttl90Days);
|
public ttl90DaysEnabled: () => boolean = () => userContext.features.ttl90Days;
|
||||||
|
|
||||||
public isValid(): boolean {
|
public isValid(): boolean {
|
||||||
// TODO add feature flag that disables validation for customers with custom accounts
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
|
@ -1202,7 +1202,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||||
|
|
||||||
if (this.isAnalyticalStorageOn()) {
|
if (this.isAnalyticalStorageOn()) {
|
||||||
// TODO: always default to 90 days once the backend hotfix is deployed
|
// TODO: always default to 90 days once the backend hotfix is deployed
|
||||||
return this.container.isFeatureEnabled(Constants.Features.ttl90Days)
|
return userContext.features.ttl90Days
|
||||||
? Constants.AnalyticalStorageTtl.Days90
|
? Constants.AnalyticalStorageTtl.Days90
|
||||||
: Constants.AnalyticalStorageTtl.Infinite;
|
: Constants.AnalyticalStorageTtl.Infinite;
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,7 +905,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
|
|
||||||
if (this.state.enableAnalyticalStore) {
|
if (this.state.enableAnalyticalStore) {
|
||||||
// TODO: always default to 90 days once the backend hotfix is deployed
|
// TODO: always default to 90 days once the backend hotfix is deployed
|
||||||
return this.props.explorer.isFeatureEnabled(Constants.Features.ttl90Days)
|
return userContext.features.ttl90Days
|
||||||
? Constants.AnalyticalStorageTtl.Days90
|
? Constants.AnalyticalStorageTtl.Days90
|
||||||
: Constants.AnalyticalStorageTtl.Infinite;
|
: Constants.AnalyticalStorageTtl.Infinite;
|
||||||
}
|
}
|
||||||
|
|
|
@ -882,8 +882,6 @@ export default class DocumentsTab extends TabsBase {
|
||||||
buttons.push(DocumentsTab._createUploadButton(this.collection.container));
|
buttons.push(DocumentsTab._createUploadButton(this.collection.container));
|
||||||
}
|
}
|
||||||
|
|
||||||
const features = this.collection.container.features() || {};
|
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,9 +58,7 @@ export default class Database implements ViewModels.Database {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||||
const useDatabaseSettingsTabV1: boolean = this.container.isFeatureEnabled(
|
const useDatabaseSettingsTabV1 = userContext.features.enableDatabaseSettingsTabV1;
|
||||||
Constants.Features.enableDatabaseSettingsTabV1
|
|
||||||
);
|
|
||||||
const tabKind: ViewModels.CollectionTabKind = useDatabaseSettingsTabV1
|
const tabKind: ViewModels.CollectionTabKind = useDatabaseSettingsTabV1
|
||||||
? ViewModels.CollectionTabKind.DatabaseSettings
|
? ViewModels.CollectionTabKind.DatabaseSettings
|
||||||
: ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
: ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||||
|
|
|
@ -3,13 +3,14 @@ import * as ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||||
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
|
||||||
|
|
||||||
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
||||||
function sample(prefix) {
|
function sample(prefix) {
|
||||||
|
@ -56,7 +57,7 @@ export default class StoredProcedure {
|
||||||
this.rid = data._rid;
|
this.rid = data._rid;
|
||||||
this.id = ko.observable(data.id);
|
this.id = ko.observable(data.id);
|
||||||
this.body = ko.observable(data.body as string);
|
this.body = ko.observable(data.body as string);
|
||||||
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
|
this.isExecuteEnabled = userContext.features.executeSproc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import { extractFeatures } from "./extractFeatures";
|
import { extractFeatures } from "./extractFeatures";
|
||||||
|
|
||||||
describe("extractFeatures", () => {
|
describe("extractFeatures", () => {
|
||||||
it("correctly detects feature flags", () => {
|
it("correctly detects feature flags in a case insensitive manner", () => {
|
||||||
// Search containing non-features, with Camelcase keys and uri encoded values
|
const url = "https://localhost:10001/12345/notebook";
|
||||||
const params = new URLSearchParams(
|
const token = "super secret";
|
||||||
"?platform=Hosted&feature.notebookserverurl=https%3A%2F%2Flocalhost%3A10001%2F12345%2Fnotebook&feature.notebookServerToken=token&feature.enablenotebooks=true&key=mykey"
|
const notebooksEnabled = false;
|
||||||
);
|
const params = new URLSearchParams({
|
||||||
|
platform: "Hosted",
|
||||||
|
"feature.NOTEBOOKSERVERURL": url,
|
||||||
|
"feature.NoTeBooKServerToken": token,
|
||||||
|
"feature.NotAFeature": "nope",
|
||||||
|
"feature.ENABLEnotebooks": notebooksEnabled.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
const features = extractFeatures(params);
|
const features = extractFeatures(params);
|
||||||
|
|
||||||
expect(features).toEqual({
|
expect(features.notebookServerUrl).toBe(url);
|
||||||
notebookserverurl: "https://localhost:10001/12345/notebook",
|
expect(features.notebookServerToken).toBe(token);
|
||||||
notebookservertoken: "token",
|
expect(features.enableNotebooks).toBe(notebooksEnabled);
|
||||||
enablenotebooks: "true",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,56 @@
|
||||||
export function extractFeatures(params?: URLSearchParams): { [key: string]: string } {
|
export type Features = {
|
||||||
|
readonly canExceedMaximumValue: boolean;
|
||||||
|
readonly cosmosdb: boolean;
|
||||||
|
readonly enableChangeFeedPolicy: boolean;
|
||||||
|
readonly enableDatabaseSettingsTabV1: boolean;
|
||||||
|
readonly enableFixedCollectionWithSharedThroughput: boolean;
|
||||||
|
readonly enableKOPanel: boolean;
|
||||||
|
readonly enableNotebooks: boolean;
|
||||||
|
readonly enableReactPane: boolean;
|
||||||
|
readonly enableRightPanelV2: boolean;
|
||||||
|
readonly enableSchema: boolean;
|
||||||
|
readonly enableSDKoperations: boolean;
|
||||||
|
readonly enableSpark: boolean;
|
||||||
|
readonly enableTtl: boolean;
|
||||||
|
readonly executeSproc: boolean;
|
||||||
|
readonly hostedDataExplorer: boolean;
|
||||||
|
readonly livyEndpoint?: string;
|
||||||
|
readonly notebookBasePath?: string;
|
||||||
|
readonly notebookServerToken?: string;
|
||||||
|
readonly notebookServerUrl?: string;
|
||||||
|
readonly selfServeType?: string;
|
||||||
|
readonly showMinRUSurvey: boolean;
|
||||||
|
readonly ttl90Days: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function extractFeatures(params?: URLSearchParams): Features {
|
||||||
params = params || new URLSearchParams(window.parent.location.search);
|
params = params || new URLSearchParams(window.parent.location.search);
|
||||||
const featureParamRegex = /feature.(.*)/i;
|
const downcased = new URLSearchParams();
|
||||||
const features: { [key: string]: string } = {};
|
params.forEach((value, key) => downcased.append(key.toLocaleLowerCase(), value));
|
||||||
params.forEach((value: string, param: string) => {
|
const get = (key: string) => downcased.get("feature." + key.toLocaleLowerCase()) ?? undefined;
|
||||||
if (featureParamRegex.test(param)) {
|
|
||||||
const matches: string[] = param.match(featureParamRegex);
|
return {
|
||||||
if (matches.length > 0) {
|
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||||
features[matches[1].toLowerCase()] = value;
|
cosmosdb: "true" === get("cosmosdb"),
|
||||||
}
|
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
||||||
}
|
enableDatabaseSettingsTabV1: "true" === get("enabledbsettingsv1"),
|
||||||
});
|
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
||||||
return features;
|
enableKOPanel: "true" === get("enablekopanel"),
|
||||||
|
enableNotebooks: "true" === get("enablenotebooks"),
|
||||||
|
enableReactPane: "true" === get("enablereactpane"),
|
||||||
|
enableRightPanelV2: "true" === get("enablerightpanelv2"),
|
||||||
|
enableSchema: "true" === get("enableschema"),
|
||||||
|
enableSDKoperations: "true" === get("enablesdkoperations"),
|
||||||
|
enableSpark: "true" === get("enablespark"),
|
||||||
|
enableTtl: "true" === get("enablettl"),
|
||||||
|
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
||||||
|
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
||||||
|
livyEndpoint: get("livyendpoint"),
|
||||||
|
notebookBasePath: get("notebookbasepath"),
|
||||||
|
notebookServerToken: get("notebookservertoken"),
|
||||||
|
notebookServerUrl: get("notebookserverurl"),
|
||||||
|
selfServeType: get("selfservetype"),
|
||||||
|
showMinRUSurvey: "true" === get("showminrusurvey"),
|
||||||
|
ttl90Days: "true" === get("ttl90days"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,34 +2,44 @@ import { AuthType } from "./AuthType";
|
||||||
import { DatabaseAccount } from "./Contracts/DataModels";
|
import { DatabaseAccount } from "./Contracts/DataModels";
|
||||||
import { SubscriptionType } from "./Contracts/SubscriptionType";
|
import { SubscriptionType } from "./Contracts/SubscriptionType";
|
||||||
import { DefaultAccountExperienceType } from "./DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "./DefaultAccountExperienceType";
|
||||||
|
import { extractFeatures, Features } from "./Platform/Hosted/extractFeatures";
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
authType?: AuthType;
|
readonly authType?: AuthType;
|
||||||
masterKey?: string;
|
readonly masterKey?: string;
|
||||||
subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
resourceGroup?: string;
|
readonly resourceGroup?: string;
|
||||||
databaseAccount?: DatabaseAccount;
|
readonly databaseAccount?: DatabaseAccount;
|
||||||
endpoint?: string;
|
readonly endpoint?: string;
|
||||||
accessToken?: string;
|
readonly accessToken?: string;
|
||||||
authorizationToken?: string;
|
readonly authorizationToken?: string;
|
||||||
resourceToken?: string;
|
readonly resourceToken?: string;
|
||||||
defaultExperience?: DefaultAccountExperienceType;
|
readonly useSDKOperations: boolean;
|
||||||
useSDKOperations?: boolean;
|
readonly defaultExperience?: DefaultAccountExperienceType;
|
||||||
subscriptionType?: SubscriptionType;
|
readonly subscriptionType?: SubscriptionType;
|
||||||
quotaId?: string;
|
readonly quotaId?: string;
|
||||||
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
|
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
|
||||||
// This is coming in a future Cosmos ARM API version as a prperty on databaseAccount
|
// This is coming in a future Cosmos ARM API version as a prperty on databaseAccount
|
||||||
apiType?: ApiType;
|
readonly apiType?: ApiType;
|
||||||
isTryCosmosDBSubscription?: boolean;
|
readonly isTryCosmosDBSubscription?: boolean;
|
||||||
portalEnv?: PortalEnv;
|
readonly portalEnv?: PortalEnv;
|
||||||
|
readonly features: Features;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra";
|
type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra";
|
||||||
export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "prod" | "dev";
|
export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "prod" | "dev";
|
||||||
|
|
||||||
const userContext: UserContext = { isTryCosmosDBSubscription: false, portalEnv: "prod" };
|
const features = extractFeatures();
|
||||||
|
const { enableSDKoperations: useSDKOperations } = features;
|
||||||
|
|
||||||
function updateUserContext(newContext: UserContext): void {
|
const userContext: UserContext = {
|
||||||
|
isTryCosmosDBSubscription: false,
|
||||||
|
portalEnv: "prod",
|
||||||
|
features,
|
||||||
|
useSDKOperations,
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateUserContext(newContext: Partial<UserContext>): void {
|
||||||
Object.assign(userContext, newContext);
|
Object.assign(userContext, newContext);
|
||||||
Object.assign(userContext, { apiType: apiType(userContext.databaseAccount) });
|
Object.assign(userContext, { apiType: apiType(userContext.databaseAccount) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
ResourceToken,
|
ResourceToken,
|
||||||
} from "../HostedExplorerChildFrame";
|
} from "../HostedExplorerChildFrame";
|
||||||
import { emulatorAccount } from "../Platform/Emulator/emulatorAccount";
|
import { emulatorAccount } from "../Platform/Emulator/emulatorAccount";
|
||||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
|
||||||
import { parseResourceTokenConnectionString } from "../Platform/Hosted/Helpers/ResourceTokenUtils";
|
import { parseResourceTokenConnectionString } from "../Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||||
import {
|
import {
|
||||||
getDatabaseAccountKindFromExperience,
|
getDatabaseAccountKindFromExperience,
|
||||||
|
@ -101,7 +100,6 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
masterKey: keys.primaryMasterKey,
|
masterKey: keys.primaryMasterKey,
|
||||||
authorizationToken: `Bearer ${config.authorizationToken}`,
|
authorizationToken: `Bearer ${config.authorizationToken}`,
|
||||||
features: extractFeatures(),
|
|
||||||
});
|
});
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +126,6 @@ function configureHostedWithConnectionString(config: ConnectionString, explorerP
|
||||||
explorer.configure({
|
explorer.configure({
|
||||||
databaseAccount,
|
databaseAccount,
|
||||||
masterKey: config.masterKey,
|
masterKey: config.masterKey,
|
||||||
features: extractFeatures(),
|
|
||||||
});
|
});
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
@ -157,10 +154,7 @@ function configureHostedWithResourceToken(config: ResourceToken, explorerParams:
|
||||||
if (parsedResourceToken.partitionKey) {
|
if (parsedResourceToken.partitionKey) {
|
||||||
explorer.resourceTokenPartitionKey(parsedResourceToken.partitionKey);
|
explorer.resourceTokenPartitionKey(parsedResourceToken.partitionKey);
|
||||||
}
|
}
|
||||||
explorer.configure({
|
explorer.configure({ databaseAccount });
|
||||||
databaseAccount,
|
|
||||||
features: extractFeatures(),
|
|
||||||
});
|
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +175,6 @@ function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParam
|
||||||
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
|
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
|
||||||
tags: {},
|
tags: {},
|
||||||
},
|
},
|
||||||
features: extractFeatures(),
|
|
||||||
});
|
});
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue