mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
2 Commits
users/kche
...
eslint/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cfa0b04c9 | ||
|
|
3c72351040 |
@@ -85,14 +85,10 @@ src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
||||
src/Explorer/Tables/TableDataClient.ts
|
||||
src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
src/Explorer/Tabs/ConflictsTab.ts
|
||||
src/Explorer/Tabs/DatabaseSettingsTab.ts
|
||||
src/Explorer/Tabs/DocumentsTab.test.ts
|
||||
src/Explorer/Tabs/DocumentsTab.ts
|
||||
src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
|
||||
@@ -96,7 +96,7 @@ export class Flights {
|
||||
public static readonly AutoscaleTest = "autoscaletest";
|
||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||
public static readonly NotebooksDownBanner = "notebooksdownbanner";
|
||||
public static readonly Phoenix = "phoenix";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
@@ -343,12 +343,7 @@ export enum ConnectionStatusType {
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Failed = "Connection Failed",
|
||||
Reconnect = "Reconnect",
|
||||
}
|
||||
|
||||
export enum ContainerStatusType {
|
||||
Active = "Active",
|
||||
Disconnected = "Disconnected",
|
||||
ReConnect = "Reconnect",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
@@ -361,25 +356,20 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
||||
export class Notebook {
|
||||
public static readonly defaultBasePath = "./notebooks";
|
||||
public static readonly heartbeatDelayMs = 60000;
|
||||
public static readonly containerStatusHeartbeatDelayMs = 30000;
|
||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||
public static readonly autoSaveIntervalMs = 120000;
|
||||
public static readonly memoryGuageToGB = 1048576;
|
||||
public static readonly lowMemoryThreshold = 0.8;
|
||||
public static readonly remainingTimeForAlert = 10;
|
||||
public static readonly retryAttempts = 3;
|
||||
public static readonly retryAttemptDelayMs = 5000;
|
||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||
public static readonly mongoShellTemporarilyDownMsg =
|
||||
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||
public static readonly cassandraShellTemporarilyDownMsg =
|
||||
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||
public static saveNotebookModalTitle = "Save notebook in temporary workspace";
|
||||
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||
public static saveNotebookModalContent =
|
||||
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
|
||||
public static newNotebookModalTitle = "Create notebook in temporary workspace";
|
||||
public static newNotebookUploadModalTitle = "Upload notebook to temporary workspace";
|
||||
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
|
||||
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||
public static newNotebookModalContent1 =
|
||||
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
||||
public static newNotebookModalContent2 =
|
||||
|
||||
@@ -58,7 +58,6 @@ let configContext: Readonly<ConfigContext> = {
|
||||
allowedJunoOrigins: [
|
||||
"https://juno-test.documents-dev.windows-int.net",
|
||||
"https://juno-test2.documents-dev.windows-int.net",
|
||||
"https://juno-test3.documents-dev.windows-int.net",
|
||||
"https://tools.cosmos.azure.com",
|
||||
"https://tools-staging.cosmos.azure.com",
|
||||
"https://localhost",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
import { ConnectionStatusType } from "../Common/Constants";
|
||||
|
||||
export interface DatabaseAccount {
|
||||
id: string;
|
||||
@@ -26,8 +26,6 @@ export interface DatabaseAccountExtendedProperties {
|
||||
isVirtualNetworkFilterEnabled?: boolean;
|
||||
ipRules?: IpRule[];
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
}
|
||||
|
||||
export interface DatabaseAccountResponseLocation {
|
||||
@@ -428,38 +426,6 @@ export interface OperationStatus {
|
||||
export interface NotebookWorkspaceConnectionInfo {
|
||||
authToken: string;
|
||||
notebookServerEndpoint: string;
|
||||
forwardingId: string;
|
||||
}
|
||||
|
||||
export interface ContainerInfo {
|
||||
durationLeftInMinutes: number;
|
||||
notebookServerInfo: NotebookWorkspaceConnectionInfo;
|
||||
status: ContainerStatusType;
|
||||
}
|
||||
|
||||
export interface IProvisionData {
|
||||
cosmosEndpoint: string;
|
||||
}
|
||||
|
||||
export interface IAccountData {
|
||||
subscriptionId: string;
|
||||
resourceGroup: string;
|
||||
dbAccountName: string;
|
||||
}
|
||||
|
||||
export interface IContainerData extends IAccountData {
|
||||
forwardingId: string;
|
||||
}
|
||||
|
||||
export interface IResponse<T> {
|
||||
status: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface IPhoenixConnectionInfoResult {
|
||||
readonly notebookAuthToken?: string;
|
||||
readonly notebookServerUrl?: string;
|
||||
readonly forwardingId?: string;
|
||||
}
|
||||
|
||||
export interface NotebookWorkspaceFeedResponse {
|
||||
|
||||
@@ -83,7 +83,7 @@ export const createCollectionContextMenuButton = (
|
||||
|
||||
items.push({
|
||||
iconSrc: HostedTerminalIcon,
|
||||
isDisabled: useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false,
|
||||
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Link,
|
||||
PrimaryButton,
|
||||
ProgressIndicator,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import React, { FC } from "react";
|
||||
@@ -196,7 +197,7 @@ export const Dialog: FC = () => {
|
||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
)}
|
||||
{contentHtml}
|
||||
{contentHtml && <Text>{contentHtml}</Text>}
|
||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||
<DialogFooter>
|
||||
<PrimaryButton {...primaryButtonProps} />
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import { RepoListItem } from "./GitHubReposComponent";
|
||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||
import Explorer from "../../Explorer";
|
||||
|
||||
export interface AddRepoComponentProps {
|
||||
container: Explorer;
|
||||
@@ -27,6 +27,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
private static readonly ButtonText = "Add";
|
||||
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
||||
private static readonly TextFieldErrorMessage = "Invalid url";
|
||||
private static readonly DefaultBranchName = "master";
|
||||
|
||||
constructor(props: AddRepoComponentProps) {
|
||||
super(props);
|
||||
@@ -77,7 +78,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
});
|
||||
let enteredUrl = this.state.textFieldValue;
|
||||
if (enteredUrl.indexOf("/tree/") === -1) {
|
||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`);
|
||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
|
||||
}
|
||||
|
||||
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
||||
@@ -92,7 +93,11 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
const item: RepoListItem = {
|
||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||
repo,
|
||||
branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [],
|
||||
branches: [
|
||||
{
|
||||
name: repoInfo.branch,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
TelemetryProcessor.traceSuccess(
|
||||
|
||||
@@ -24,11 +24,11 @@ import { RepoListItem } from "./GitHubReposComponent";
|
||||
import {
|
||||
BranchesDropdownCheckboxStyles,
|
||||
BranchesDropdownOptionContainerStyle,
|
||||
BranchesDropdownStyles,
|
||||
BranchesDropdownWidth,
|
||||
ReposListBranchesColumnWidth,
|
||||
ReposListCheckboxStyles,
|
||||
ReposListRepoColumnMinWidth,
|
||||
ReposListBranchesColumnWidth,
|
||||
BranchesDropdownWidth,
|
||||
BranchesDropdownStyles,
|
||||
} from "./GitHubStyleConstants";
|
||||
|
||||
export interface ReposListComponentProps {
|
||||
@@ -44,7 +44,6 @@ export interface BranchesProps {
|
||||
lastPageInfo?: IGitHubPageInfo;
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
defaultBranchName: string;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
private static readonly BranchesColumnName = "Branches";
|
||||
private static readonly LoadingText = "Loading...";
|
||||
private static readonly LoadMoreText = "Load more";
|
||||
private static readonly DefaultBranchNames = "master/main";
|
||||
private static readonly DefaultBranchName = "master";
|
||||
private static readonly FooterIndex = -1;
|
||||
|
||||
public render(): JSX.Element {
|
||||
@@ -156,10 +155,6 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
}
|
||||
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||
if (item.branches.length === 0 && branchesProps.defaultBranchName) {
|
||||
item.branches = [{ name: branchesProps.defaultBranchName }];
|
||||
}
|
||||
|
||||
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
||||
key: branch.name,
|
||||
text: branch.name,
|
||||
@@ -203,7 +198,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
const dropdownProps: IDropdownProps = {
|
||||
styles: BranchesDropdownStyles,
|
||||
options: [],
|
||||
placeholder: ReposListComponent.DefaultBranchNames,
|
||||
placeholder: ReposListComponent.DefaultBranchName,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
@@ -277,7 +272,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
styles: ReposListCheckboxStyles,
|
||||
onChange: () => {
|
||||
const repoListItem = { ...item };
|
||||
repoListItem.branches = [];
|
||||
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
|
||||
this.props.pinRepo(repoListItem);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,19 +35,16 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
describe("NotebookTerminalComponent", () => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
@@ -52,7 +53,7 @@ export class NotebookViewerComponent
|
||||
super(props);
|
||||
|
||||
this.clientManager = new NotebookClientV2({
|
||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
|
||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined },
|
||||
databaseAccountName: undefined,
|
||||
defaultExperience: "NotebookViewer",
|
||||
isReadOnly: true,
|
||||
@@ -147,7 +148,9 @@ export class NotebookViewerComponent
|
||||
<NotebookMetadataComponent
|
||||
data={this.state.galleryItem}
|
||||
isFavorite={this.state.isFavorite}
|
||||
downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`}
|
||||
downloadButtonText={
|
||||
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||
}
|
||||
onTagClick={this.props.onTagClick}
|
||||
onFavoriteClick={this.favoriteItem}
|
||||
onUnfavoriteClick={this.unfavoriteItem}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import * as React from "react";
|
||||
import DiscardIcon from "../../../../images/discard.svg";
|
||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||
@@ -72,7 +71,6 @@ export interface SettingsComponentState {
|
||||
wasAutopilotOriginallySet: boolean;
|
||||
isScaleSaveable: boolean;
|
||||
isScaleDiscardable: boolean;
|
||||
throughputError: string;
|
||||
|
||||
timeToLive: TtlType;
|
||||
timeToLiveBaseline: TtlType;
|
||||
@@ -126,7 +124,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
private totalThroughputUsed: number;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
@@ -158,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
wasAutopilotOriginallySet: false,
|
||||
isScaleSaveable: false,
|
||||
isScaleDiscardable: false,
|
||||
throughputError: undefined,
|
||||
|
||||
timeToLive: undefined,
|
||||
timeToLiveBaseline: undefined,
|
||||
@@ -212,10 +208,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) {
|
||||
this.calculateTotalThroughputUsed();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -262,10 +254,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.state.throughputError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
this.state.isScaleSaveable ||
|
||||
this.state.isSubSettingsSaveable ||
|
||||
@@ -493,26 +481,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||
|
||||
private calculateTotalThroughputUsed = (): void => {
|
||||
this.totalThroughputUsed = 0;
|
||||
(useDatabases.getState().databases || []).forEach(async (database) => {
|
||||
if (database.offer()) {
|
||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
||||
this.totalThroughputUsed += dbThroughput;
|
||||
}
|
||||
|
||||
(database.collections() || []).forEach(async (collection) => {
|
||||
if (collection.offer()) {
|
||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
||||
this.totalThroughputUsed += colThroughput;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
this.totalThroughputUsed *= numberOfRegions;
|
||||
};
|
||||
|
||||
public getAnalyticalStorageTtl = (): number => {
|
||||
if (this.isAnalyticalStorageEnabled) {
|
||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||
@@ -675,31 +643,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return buttons;
|
||||
};
|
||||
|
||||
private onMaxAutoPilotThroughputChange = (newThroughput: number): void => {
|
||||
let throughputError = "";
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
|
||||
if (throughputCap && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
this.totalThroughputUsed + throughputDelta
|
||||
} RU/s. Change total throughput limit in cost management.`;
|
||||
}
|
||||
this.setState({ autoPilotThroughput: newThroughput, throughputError });
|
||||
};
|
||||
private onMaxAutoPilotThroughputChange = (newThroughput: number): void =>
|
||||
this.setState({ autoPilotThroughput: newThroughput });
|
||||
|
||||
private onThroughputChange = (newThroughput: number): void => {
|
||||
let throughputError = "";
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
|
||||
if (throughputCap && throughputCap - this.totalThroughputUsed < newThroughput - this.offer.manualThroughput) {
|
||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
this.totalThroughputUsed + throughputDelta
|
||||
} RU/s. Change total throughput limit in cost management.`;
|
||||
}
|
||||
this.setState({ throughput: newThroughput, throughputError });
|
||||
};
|
||||
private onThroughputChange = (newThroughput: number): void => this.setState({ throughput: newThroughput });
|
||||
|
||||
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
||||
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
||||
@@ -946,7 +893,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||
throughputError: this.state.throughputError,
|
||||
};
|
||||
|
||||
if (!this.isCollectionSettingsTab) {
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface ScaleComponentProps {
|
||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
initialNotification: DataModels.Notification;
|
||||
throughputError?: string;
|
||||
}
|
||||
|
||||
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
@@ -190,7 +189,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
||||
throughputError={this.props.throughputError}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
getThroughputWarningMessage: () => JSX.Element;
|
||||
usageSizeInKB: number;
|
||||
throughputError?: string;
|
||||
}
|
||||
|
||||
interface ThroughputInputAutoPilotV3State {
|
||||
@@ -541,7 +540,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||
onChange={this.onAutoPilotThroughputChange}
|
||||
min={minAutoPilotThroughput}
|
||||
errorMessage={this.props.throughputError}
|
||||
/>
|
||||
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
||||
{this.minRUperGBSurvey()}
|
||||
@@ -581,7 +579,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
}
|
||||
onChange={this.onThroughputChange}
|
||||
min={this.props.minimum}
|
||||
errorMessage={this.props.throughputError}
|
||||
/>
|
||||
{this.state.exceedFreeTierThroughput && (
|
||||
<MessageBar
|
||||
|
||||
@@ -34,13 +34,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
@@ -108,13 +102,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
||||
@@ -7,7 +7,6 @@ const props = {
|
||||
isSharded: true,
|
||||
setThroughputValue: () => jest.fn(),
|
||||
setIsAutoscale: () => jest.fn(),
|
||||
setIsThroughputCapExceeded: () => jest.fn(),
|
||||
onCostAcknowledgeChange: () => jest.fn(),
|
||||
};
|
||||
describe("ThroughputInput Pane", () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
@@ -17,7 +16,6 @@ export interface ThroughputInputProps {
|
||||
showFreeTierExceedThroughputTooltip: boolean;
|
||||
setThroughputValue: (throughput: number) => void;
|
||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
|
||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -26,7 +24,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
showFreeTierExceedThroughputTooltip,
|
||||
setThroughputValue,
|
||||
setIsAutoscale,
|
||||
setIsThroughputCapExceeded,
|
||||
isSharded,
|
||||
onCostAcknowledgeChange,
|
||||
}: ThroughputInputProps) => {
|
||||
@@ -34,60 +31,10 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||
const [throughputError, setThroughputError] = useState<string>("");
|
||||
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
||||
|
||||
setIsAutoscale(isAutoscaleSelected);
|
||||
setThroughputValue(throughput);
|
||||
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||
|
||||
useEffect(() => {
|
||||
// throughput cap check for the initial state
|
||||
let totalThroughput = 0;
|
||||
(useDatabases.getState().databases || []).forEach((database) => {
|
||||
if (database.offer()) {
|
||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
||||
totalThroughput += dbThroughput;
|
||||
}
|
||||
|
||||
(database.collections() || []).forEach((collection) => {
|
||||
if (collection.offer()) {
|
||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
||||
totalThroughput += colThroughput;
|
||||
}
|
||||
});
|
||||
});
|
||||
totalThroughput *= numberOfRegions;
|
||||
setTotalThroughputUsed(totalThroughput);
|
||||
|
||||
if (throughputCap && throughputCap - totalThroughput < throughput) {
|
||||
setThroughputError(
|
||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
totalThroughput + throughput * numberOfRegions
|
||||
} RU/s. Change total throughput limit in cost management.`
|
||||
);
|
||||
|
||||
setIsThroughputCapExceeded(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const checkThroughputCap = (newThroughput: number): boolean => {
|
||||
if (throughputCap && throughputCap - totalThroughputUsed < newThroughput) {
|
||||
setThroughputError(
|
||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
||||
totalThroughputUsed + newThroughput * numberOfRegions
|
||||
} RU/s. Change total throughput limit in cost management.`
|
||||
);
|
||||
setIsThroughputCapExceeded(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
setThroughputError("");
|
||||
setIsThroughputCapExceeded(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
const getThroughputLabelText = (): string => {
|
||||
let throughputHeaderText: string;
|
||||
if (isAutoscaleSelected) {
|
||||
@@ -113,17 +60,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
const newThroughput = parseInt(newInput);
|
||||
setThroughput(newThroughput);
|
||||
setThroughputValue(newThroughput);
|
||||
|
||||
if (!isSharded && newThroughput > 10000) {
|
||||
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||
return;
|
||||
} else {
|
||||
setThroughputError("");
|
||||
}
|
||||
|
||||
if (!checkThroughputCap(newThroughput)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setThroughputError("");
|
||||
};
|
||||
|
||||
const getAutoScaleTooltip = (): string => {
|
||||
@@ -155,13 +96,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
setIsAutoScaleSelected(true);
|
||||
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||
setIsAutoscale(true);
|
||||
checkThroughputCap(AutoPilotUtils.minAutoPilotThroughput);
|
||||
} else {
|
||||
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
setIsAutoScaleSelected(false);
|
||||
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
setIsAutoscale(false);
|
||||
checkThroughputCap(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
isSharded={true}
|
||||
onCostAcknowledgeChange={[Function]}
|
||||
setIsAutoscale={[Function]}
|
||||
setIsThroughputCapExceeded={[Function]}
|
||||
setThroughputValue={[Function]}
|
||||
showFreeTierExceedThroughputTooltip={true}
|
||||
>
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
import { Link } from "@fluentui/react/lib/Link";
|
||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import _ from "underscore";
|
||||
import shallow from "zustand/shallow";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { QueriesClient } from "../Common/QueriesClient";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import {
|
||||
ContainerConnectionInfo,
|
||||
IAccountData,
|
||||
IContainerData,
|
||||
IPhoenixConnectionInfoResult,
|
||||
IProvisionData,
|
||||
IResponse,
|
||||
} from "../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
@@ -37,6 +29,7 @@ import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import {
|
||||
get as getWorkspace,
|
||||
listByDatabaseAccount,
|
||||
listConnectionInfo,
|
||||
start,
|
||||
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { stringToBlob } from "../Utils/BlobUtils";
|
||||
@@ -172,9 +165,11 @@ export default class Explorer {
|
||||
);
|
||||
|
||||
useNotebook.subscribe(
|
||||
async () => this.initiateAndRefreshNotebookList(),
|
||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
||||
shallow
|
||||
async () => {
|
||||
this.initiateAndRefreshNotebookList();
|
||||
useNotebook.getState().setIsRefreshed(false);
|
||||
},
|
||||
(state) => state.isNotebookEnabled || state.isRefreshed
|
||||
);
|
||||
|
||||
this.resourceTree = new ResourceTreeAdapter(this);
|
||||
@@ -184,7 +179,6 @@ export default class Explorer {
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
||||
authToken: userContext.features.notebookServerToken,
|
||||
forwardingId: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,29 +347,41 @@ export default class Explorer {
|
||||
if (!databaseAccount) {
|
||||
throw new Error("No database account specified");
|
||||
}
|
||||
|
||||
if (this._isInitializingNotebooks) {
|
||||
return;
|
||||
}
|
||||
this._isInitializingNotebooks = true;
|
||||
if (userContext.features.phoenix === false) {
|
||||
await this.ensureNotebookWorkspaceRunning();
|
||||
const connectionInfo = await listConnectionInfo(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
databaseAccount.name,
|
||||
"default"
|
||||
);
|
||||
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshNotebookList();
|
||||
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public async allocateContainer(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const isAllocating = useNotebook.getState().isAllocating;
|
||||
if (
|
||||
isAllocating === false &&
|
||||
(notebookServerInfo === undefined ||
|
||||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
|
||||
) {
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
};
|
||||
const accountData: IAccountData = {
|
||||
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
|
||||
const provisionData = {
|
||||
aadToken: userContext.authorizationToken,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
};
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
@@ -383,50 +389,37 @@ export default class Explorer {
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
try {
|
||||
useNotebook.getState().setIsAllocating(true);
|
||||
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData, accountData);
|
||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||
if (
|
||||
connectionInfo.status === HttpStatusCodes.OK &&
|
||||
connectionInfo.data &&
|
||||
connectionInfo.data.notebookServerUrl
|
||||
) {
|
||||
connectionStatus.status = ConnectionStatusType.Connected;
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||
});
|
||||
this.notebookManager?.notebookClient
|
||||
.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
} else {
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
throw error;
|
||||
}
|
||||
this.refreshCommandBarButtons();
|
||||
this.refreshNotebookList();
|
||||
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async setNotebookInfo(
|
||||
connectionInfo: IResponse<IPhoenixConnectionInfoResult>,
|
||||
connectionStatus: DataModels.ContainerConnectionInfo
|
||||
) {
|
||||
if (connectionInfo.status === HttpStatusCodes.OK && connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
||||
const containerData: IContainerData = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
};
|
||||
await this.phoenixClient.initiateContainerHeartBeat(containerData);
|
||||
|
||||
connectionStatus.status = ConnectionStatusType.Connected;
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
});
|
||||
this.notebookManager?.notebookClient
|
||||
.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||
} else {
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
}
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
}
|
||||
|
||||
public resetNotebookWorkspace(): void {
|
||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||
handleError(
|
||||
@@ -435,10 +428,11 @@ export default class Explorer {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const resetConfirmationDialogProps: DialogProps = {
|
||||
isModal: true,
|
||||
title: "Reset Workspace",
|
||||
subText: "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?",
|
||||
subText: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?",
|
||||
primaryButtonText: "OK",
|
||||
secondaryButtonText: "Cancel",
|
||||
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
||||
@@ -496,48 +490,16 @@ export default class Explorer {
|
||||
private _resetNotebookWorkspace = async () => {
|
||||
useDialog.getState().closeDialog();
|
||||
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
||||
let connectionStatus: ContainerConnectionInfo;
|
||||
try {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
||||
logConsoleError(error);
|
||||
return;
|
||||
}
|
||||
useTabs.getState().closeAllNotebookTabs(true);
|
||||
connectionStatus = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace();
|
||||
if (connectionInfo && connectionInfo.status && connectionInfo.status === HttpStatusCodes.OK) {
|
||||
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
}
|
||||
logConsoleInfo("Successfully reset notebook workspace");
|
||||
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||
} else {
|
||||
logConsoleError(`Failed to reset notebook workspace`);
|
||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace);
|
||||
connectionStatus = {
|
||||
status: ConnectionStatusType.Reconnect,
|
||||
};
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
}
|
||||
await this.notebookManager?.notebookClient.resetWorkspace();
|
||||
logConsoleInfo("Successfully reset notebook workspace");
|
||||
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
connectionStatus = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
throw error;
|
||||
} finally {
|
||||
clearInProgressMessage();
|
||||
@@ -729,8 +691,8 @@ export default class Explorer {
|
||||
if (!notebookContentItem || !notebookContentItem.path) {
|
||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||
}
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenix) {
|
||||
await this.allocateContainer();
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
|
||||
this.allocateContainer();
|
||||
}
|
||||
|
||||
const notebookTabs = useTabs
|
||||
@@ -953,7 +915,7 @@ export default class Explorer {
|
||||
handleError(error, "Explorer/onNewNotebookClicked");
|
||||
throw new Error(error);
|
||||
}
|
||||
const isPhoenixEnabled = useNotebook.getState().isPhoenix;
|
||||
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
|
||||
if (isPhoenixEnabled) {
|
||||
if (isGithubTree) {
|
||||
async () => {
|
||||
@@ -1045,7 +1007,7 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
await this.allocateContainer();
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
@@ -1054,8 +1016,8 @@ export default class Explorer {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Failed to connect",
|
||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again."
|
||||
"Failed to Connect",
|
||||
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -1157,9 +1119,7 @@ export default class Explorer {
|
||||
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||
);
|
||||
} else {
|
||||
userContext.databaseAccount?.properties.capacity?.totalThroughputLimit
|
||||
? await useDatabases.getState().loadAllOffers()
|
||||
: await useDatabases.getState().loadDatabaseOffers();
|
||||
await useDatabases.getState().loadDatabaseOffers();
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||
@@ -1185,9 +1145,10 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async handleOpenFileAction(path: string): Promise<void> {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
await this.allocateContainer();
|
||||
} else if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||
) {
|
||||
this._openSetupNotebooksPaneForQuickstart();
|
||||
}
|
||||
|
||||
@@ -1219,7 +1180,7 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
Notebook.newNotebookUploadModalTitle,
|
||||
undefined,
|
||||
@@ -1233,6 +1194,9 @@ export default class Explorer {
|
||||
undefined,
|
||||
this.getNewNoteWarningText()
|
||||
);
|
||||
} else {
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.uploadFilePanel(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,7 +1210,7 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public getDownloadModalConent(fileName: string): JSX.Element {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
return (
|
||||
<>
|
||||
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
||||
@@ -1268,17 +1232,22 @@ export default class Explorer {
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
|
||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenix;
|
||||
let isNotebookEnabled = true;
|
||||
if (!userContext.features.phoenix) {
|
||||
isNotebookEnabled =
|
||||
userContext.authType !== AuthType.ResourceToken &&
|
||||
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||
userContext.features.enableNotebooks);
|
||||
}
|
||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||
useNotebook.getState().setIsShellEnabled(useNotebook.getState().isPhoenix && isPublicInternetAccessAllowed());
|
||||
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||
|
||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||
isNotebookEnabled,
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
});
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
|
||||
if (!userContext.features.notebooksTemporarilyDown) {
|
||||
if (isNotebookEnabled) {
|
||||
await this.initNotebooks(userContext.databaseAccount);
|
||||
} else if (this.notebookToImport) {
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
* and update any knockout observables passed from the parent.
|
||||
*/
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
import * as CommandBarUtil from "./CommandBarUtil";
|
||||
@@ -53,10 +56,18 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
}
|
||||
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||
) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="commandBarContainer">
|
||||
<FluentCommandBar
|
||||
|
||||
@@ -194,26 +194,29 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsPhoenix(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openMongoShellBtn.disabled).toBe(true);
|
||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsPhoenix(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
expect(openMongoShellBtn).toBeDefined();
|
||||
expect(openMongoShellBtn.disabled).toBe(false);
|
||||
expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openMongoShellBtn.disabled).toBe(true);
|
||||
//expect(openMongoShellBtn.disabled).toBe(false);
|
||||
//expect(openMongoShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
|
||||
@@ -296,27 +299,30 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsPhoenix(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsPhoenix(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeDefined();
|
||||
expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
|
||||
//TODO: modify once notebooks are available
|
||||
expect(openCassandraShellBtn.disabled).toBe(true);
|
||||
//expect(openCassandraShellBtn.disabled).toBe(false);
|
||||
//expect(openCassandraShellBtn.tooltipText).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
|
||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||
import GitHubIcon from "../../../../images/github.svg";
|
||||
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
||||
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
|
||||
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
||||
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
||||
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
||||
@@ -79,7 +80,7 @@ export function createStaticCommandBarButtons(
|
||||
}
|
||||
|
||||
notebookButtons.push(createOpenTerminalButton(container));
|
||||
if (selectedNodeState.isConnectedToContainer()) {
|
||||
if (userContext.features.phoenix === false) {
|
||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||
}
|
||||
if (
|
||||
@@ -97,7 +98,7 @@ export function createStaticCommandBarButtons(
|
||||
}
|
||||
|
||||
notebookButtons.forEach((btn) => {
|
||||
if (useNotebook.getState().isPhoenix === false && userContext.features.notebooksDownBanner) {
|
||||
if (userContext.features.notebooksTemporarilyDown) {
|
||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||
@@ -108,6 +109,11 @@ export function createStaticCommandBarButtons(
|
||||
}
|
||||
buttons.push(btn);
|
||||
});
|
||||
} else {
|
||||
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(createEnableNotebooksButton(container));
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||
@@ -162,7 +168,7 @@ export function createContextCommandBarButtons(
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (!userContext.features.notebooksTemporarilyDown) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
}
|
||||
} else {
|
||||
@@ -173,12 +179,12 @@ export function createContextCommandBarButtons(
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
tooltipText:
|
||||
useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false
|
||||
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
|
||||
? Constants.Notebook.mongoShellTemporarilyDownMsg
|
||||
: undefined,
|
||||
disabled:
|
||||
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
|
||||
(useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false),
|
||||
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
|
||||
};
|
||||
buttons.push(newMongoShellBtn);
|
||||
}
|
||||
@@ -301,15 +307,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
|
||||
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "New " + getDatabaseName();
|
||||
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||
|
||||
return {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: async () => {
|
||||
if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) {
|
||||
await useDatabases.getState().loadAllOffers();
|
||||
}
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />);
|
||||
},
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"New " + getDatabaseName(),
|
||||
<AddDatabasePanel explorer={container} buttonElement={newDatabaseButton} />
|
||||
),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -470,6 +479,33 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
||||
};
|
||||
}
|
||||
|
||||
function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return undefined;
|
||||
}
|
||||
const label = "Enable Notebooks (Preview)";
|
||||
const tooltip =
|
||||
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||
const description =
|
||||
"Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||
return {
|
||||
iconSrc: EnableNotebooksIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
label,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
|
||||
),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
|
||||
ariaLabel: label,
|
||||
tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||
const label = "Open Terminal";
|
||||
return {
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import {
|
||||
FocusTrapCallout,
|
||||
FocusZone,
|
||||
FocusZoneTabbableElements,
|
||||
FontWeights,
|
||||
Icon,
|
||||
mergeStyleSets,
|
||||
ProgressIndicator,
|
||||
Stack,
|
||||
Text,
|
||||
TooltipHost,
|
||||
} from "@fluentui/react";
|
||||
import { useId } from "@fluentui/react-hooks";
|
||||
import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
|
||||
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
||||
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
@@ -22,33 +10,12 @@ interface Props {
|
||||
container: Explorer;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const [counter, setCounter] = React.useState(0);
|
||||
const [statusColor, setStatusColor] = React.useState("");
|
||||
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||
const [isBarDismissed, setIsBarDismissed] = React.useState<boolean>(false);
|
||||
const buttonId = useId("callout-button");
|
||||
const containerInfo = useNotebook((state) => state.containerStatus);
|
||||
|
||||
const styles = mergeStyleSets({
|
||||
callout: {
|
||||
width: 320,
|
||||
padding: "20px 24px",
|
||||
},
|
||||
title: {
|
||||
marginBottom: 12,
|
||||
fontWeight: FontWeights.semilight,
|
||||
},
|
||||
buttons: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
@@ -68,15 +35,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
return () => clearInterval(intervalId);
|
||||
}, [isActive, counter]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (connectionInfo?.status === ConnectionStatusType.Reconnect) {
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
} else if (connectionInfo?.status === ConnectionStatusType.Failed) {
|
||||
setStatusColor("status failed is-animating");
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
}
|
||||
}, [connectionInfo.status]);
|
||||
|
||||
const stopTimer = () => {
|
||||
setIsActive(false);
|
||||
setCounter(0);
|
||||
@@ -84,13 +42,15 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
setMinute("00");
|
||||
};
|
||||
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
|
||||
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||
|
||||
if (
|
||||
connectionInfo &&
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
|
||||
) {
|
||||
return (
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||
@@ -105,7 +65,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
}
|
||||
|
||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||
stopTimer();
|
||||
setIsActive(true);
|
||||
setStatusColor("status connecting is-animating");
|
||||
setToolTipContent("Connecting to temporary workspace.");
|
||||
@@ -119,68 +78,30 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TooltipHost
|
||||
content={
|
||||
containerInfo?.status === ContainerStatusType.Active
|
||||
? `Connected to temporary workspace. This temporary workspace will get disconnected in ${Math.round(
|
||||
containerInfo.durationLeftInMinutes
|
||||
)} minutes.`
|
||||
: toolTipContent
|
||||
}
|
||||
>
|
||||
<ActionButton
|
||||
id={buttonId}
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<i className={statusColor}></i>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||
<ProgressIndicator
|
||||
className={totalGB !== 0 && usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={totalGB !== 0 ? usedGB / totalGB : 0}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{!isBarDismissed &&
|
||||
containerInfo.status &&
|
||||
containerInfo.status === ContainerStatusType.Active &&
|
||||
Math.round(containerInfo.durationLeftInMinutes) <= Notebook.remainingTimeForAlert ? (
|
||||
<FocusTrapCallout
|
||||
role="alertdialog"
|
||||
className={styles.callout}
|
||||
gapSpace={0}
|
||||
target={`#${buttonId}`}
|
||||
onDismiss={() => setIsBarDismissed(true)}
|
||||
setInitialFocus
|
||||
>
|
||||
<Text block variant="xLarge" className={styles.title}>
|
||||
Remaining Time
|
||||
</Text>
|
||||
<Text block variant="small">
|
||||
This temporary workspace will get disconnected in {Math.round(containerInfo.durationLeftInMinutes)}{" "}
|
||||
minutes. To save your work permanently, save your notebooks to a GitHub repository or download the
|
||||
notebooks to your local machine before the session ends.
|
||||
</Text>
|
||||
<FocusZone handleTabKey={FocusZoneTabbableElements.all} isCircularNavigation>
|
||||
<Stack className={styles.buttons} gap={8} horizontal>
|
||||
<DefaultButton onClick={() => setIsBarDismissed(true)}>Dimiss</DefaultButton>
|
||||
</Stack>
|
||||
</FocusZone>
|
||||
</FocusTrapCallout>
|
||||
) : undefined}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<i className={statusColor}></i>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
</>
|
||||
</ActionButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
/**
|
||||
* Notebook container related stuff
|
||||
*/
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import {
|
||||
ContainerConnectionInfo,
|
||||
IAccountData,
|
||||
IPhoenixConnectionInfoResult,
|
||||
IProvisionData,
|
||||
IResponse,
|
||||
} from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContainerClient {
|
||||
private clearReconnectionAttemptMessage? = () => {};
|
||||
private isResettingWorkspace: boolean;
|
||||
private phoenixClient: PhoenixClient;
|
||||
private retryOptions: promiseRetry.Options;
|
||||
|
||||
constructor(private onConnectionLost: () => void) {
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
this.retryOptions = {
|
||||
retries: Notebook.retryAttempts,
|
||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||
minTimeout: Notebook.retryAttemptDelayMs,
|
||||
};
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
@@ -54,23 +38,10 @@ export class NotebookContainerClient {
|
||||
* Heartbeat: each ping schedules another ping
|
||||
*/
|
||||
private scheduleHeartbeat(delayMs: number): void {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const memoryUsageInfo = await this.getMemoryUsage();
|
||||
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
} catch (exception) {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
@@ -88,27 +59,6 @@ export class NotebookContainerClient {
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
try {
|
||||
const runMemoryAsync = async () => {
|
||||
return await this._getMemoryAsync(notebookServerEndpoint, authToken);
|
||||
};
|
||||
return await promiseRetry(runMemoryAsync, this.retryOptions);
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||
if (!this.clearReconnectionAttemptMessage) {
|
||||
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||
);
|
||||
}
|
||||
this.onConnectionLost();
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async _getMemoryAsync(
|
||||
notebookServerEndpoint: string,
|
||||
authToken: string
|
||||
): Promise<DataModels.MemoryUsageInfo> {
|
||||
if (this.checkStatus()) {
|
||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -128,43 +78,44 @@ export class NotebookContainerClient {
|
||||
freeKB: memoryUsageInfo.free,
|
||||
};
|
||||
}
|
||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
||||
throw new AbortError(response.statusText);
|
||||
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.ReConnect,
|
||||
};
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(true);
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
} else {
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||
if (!this.clearReconnectionAttemptMessage) {
|
||||
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||
);
|
||||
}
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(true);
|
||||
}
|
||||
this.onConnectionLost();
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private checkStatus(): boolean {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Disconnected) {
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Reconnect,
|
||||
};
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
public async resetWorkspace(): Promise<void> {
|
||||
this.isResettingWorkspace = true;
|
||||
let response: IResponse<IPhoenixConnectionInfoResult>;
|
||||
try {
|
||||
response = await this._resetWorkspace();
|
||||
await this._resetWorkspace();
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
return response;
|
||||
}
|
||||
this.isResettingWorkspace = false;
|
||||
return response;
|
||||
}
|
||||
|
||||
private async _resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
private async _resetWorkspace(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
@@ -172,22 +123,15 @@ export class NotebookContainerClient {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
try {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
};
|
||||
const accountData: IAccountData = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
};
|
||||
return await this.phoenixClient.resetContainer(provisionData, accountData);
|
||||
}
|
||||
return null;
|
||||
await fetch(`${notebookServerEndpoint}/api/shutdown`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: authToken },
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||
throw error;
|
||||
await this.recreateNotebookWorkspaceAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,12 +163,4 @@ export class NotebookContainerClient {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
private getHeaders(): HeadersInit {
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
return {
|
||||
[authorizationHeader.header]: authorizationHeader.token,
|
||||
[HttpHeaders.contentType]: "application/json",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AppState, selectors } from "@nteract/core";
|
||||
import domtoimage from "dom-to-image";
|
||||
import Html2Canvas from "html2canvas";
|
||||
import path from "path";
|
||||
import { userContext } from "../../UserContext";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||
@@ -328,4 +329,16 @@ export class NotebookUtil {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
public static getNotebookBtnTitle(fileName: string): string {
|
||||
if (this.isPhoenixEnabled()) {
|
||||
return `Download to ${fileName}`;
|
||||
} else {
|
||||
return `Download to my notebooks`;
|
||||
}
|
||||
}
|
||||
|
||||
public static isPhoenixEnabled(): boolean {
|
||||
return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ describe("auto start kernel", () => {
|
||||
connectionInfo: {
|
||||
authToken: "autToken",
|
||||
notebookServerEndpoint: "notebookServerEndpoint",
|
||||
forwardingId: "Id",
|
||||
},
|
||||
databaseAccountName: undefined,
|
||||
defaultExperience: undefined,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { cloneDeep } from "lodash";
|
||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
@@ -8,8 +7,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo, ContainerInfo, IAccountData } from "../../Contracts/DataModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -18,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import NotebookManager from "./NotebookManager";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
|
||||
interface NotebookState {
|
||||
isNotebookEnabled: boolean;
|
||||
@@ -36,8 +35,6 @@ interface NotebookState {
|
||||
notebookFolderName: string;
|
||||
isAllocating: boolean;
|
||||
isRefreshed: boolean;
|
||||
containerStatus: ContainerInfo;
|
||||
isPhoenix: boolean;
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||
@@ -56,11 +53,8 @@ interface NotebookState {
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||
setIsAllocating: (isAllocating: boolean) => void;
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||
setIsRefreshed: (isAllocating: boolean) => void;
|
||||
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
||||
getPhoenixStatus: () => Promise<void>;
|
||||
setIsPhoenix: (isPhoenix: boolean) => void;
|
||||
}
|
||||
|
||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
@@ -69,7 +63,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
notebookServerInfo: {
|
||||
notebookServerEndpoint: undefined,
|
||||
authToken: undefined,
|
||||
forwardingId: undefined,
|
||||
},
|
||||
sparkClusterConnectionInfo: {
|
||||
userName: undefined,
|
||||
@@ -90,12 +83,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
notebookFolderName: undefined,
|
||||
isAllocating: false,
|
||||
isRefreshed: false,
|
||||
containerStatus: {
|
||||
status: undefined,
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
},
|
||||
isPhoenix: undefined,
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||
@@ -108,7 +95,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||
await get().getPhoenixStatus();
|
||||
const { databaseAccount, authType } = userContext;
|
||||
if (
|
||||
authType === AuthType.EncryptedToken ||
|
||||
@@ -201,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||
const notebookFolderName = "Temporary Notebooks";
|
||||
const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
|
||||
set({ notebookFolderName });
|
||||
const myNotebooksContentRoot = {
|
||||
name: get().notebookFolderName,
|
||||
@@ -284,30 +270,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
},
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||
useTabs.getState().closeAllNotebookTabs(true);
|
||||
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
useNotebook.getState().setContainerStatus({
|
||||
status: undefined,
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: undefined,
|
||||
authToken: undefined,
|
||||
});
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
},
|
||||
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||
getPhoenixStatus: async () => {
|
||||
if (get().isPhoenix === undefined) {
|
||||
const phoenixClient = new PhoenixClient();
|
||||
const accountData: IAccountData = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
};
|
||||
const isPhoenix = await phoenixClient.IsDbAcountWhitelisted(accountData);
|
||||
set({ isPhoenix });
|
||||
}
|
||||
},
|
||||
setIsPhoenix: (isPhoenix: boolean) => set({ isPhoenix }),
|
||||
}));
|
||||
|
||||
@@ -92,7 +92,6 @@ export interface AddCollectionPanelState {
|
||||
errorMessage: string;
|
||||
showErrorDetails: boolean;
|
||||
isExecuting: boolean;
|
||||
isThroughputCapExceeded: boolean;
|
||||
}
|
||||
|
||||
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
||||
@@ -123,7 +122,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
errorMessage: "",
|
||||
showErrorDetails: false,
|
||||
isExecuting: false,
|
||||
isThroughputCapExceeded: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,9 +249,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
isSharded={this.state.isSharded}
|
||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||
this.setState({ isThroughputCapExceeded })
|
||||
}
|
||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||
/>
|
||||
)}
|
||||
@@ -485,9 +480,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
isSharded={this.state.isSharded}
|
||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||
this.setState({ isThroughputCapExceeded })
|
||||
}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
||||
this.isCostAcknowledged = isAcknowledged;
|
||||
}}
|
||||
@@ -684,7 +676,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
||||
<PanelFooterComponent buttonLabel="OK" />
|
||||
|
||||
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||
</form>
|
||||
|
||||
@@ -23,10 +23,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
|
||||
|
||||
export interface AddDatabasePaneProps {
|
||||
explorer: Explorer;
|
||||
buttonElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
explorer: container,
|
||||
buttonElement,
|
||||
}: AddDatabasePaneProps) => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
let throughput: number;
|
||||
@@ -50,7 +52,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
);
|
||||
const [formErrors, setFormErrors] = useState<string>("");
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
||||
|
||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||
|
||||
@@ -78,6 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
};
|
||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||
buttonElement.focus();
|
||||
}, []);
|
||||
|
||||
const onSubmit = () => {
|
||||
@@ -167,7 +169,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
formError: formErrors,
|
||||
isExecuting,
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
|
||||
@@ -238,7 +239,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
isSharded={databaseCreateNewShared}
|
||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||
<RightPaneForm
|
||||
formError=""
|
||||
isExecuting={false}
|
||||
isSubmitButtonDisabled={false}
|
||||
onSubmit={[Function]}
|
||||
submitButtonText="OK"
|
||||
>
|
||||
@@ -93,7 +92,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||
isSharded={true}
|
||||
onCostAcknowledgeChange={[Function]}
|
||||
setIsAutoscale={[Function]}
|
||||
setIsThroughputCapExceeded={[Function]}
|
||||
setThroughputValue={[Function]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||
const [formError, setFormError] = useState<string>("");
|
||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||
|
||||
const addCollectionPaneOpenMessage = {
|
||||
@@ -150,7 +149,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
formError,
|
||||
isExecuting,
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
|
||||
@@ -264,7 +262,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
isSharded
|
||||
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
@@ -337,7 +334,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
isSharded={false}
|
||||
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
@@ -75,7 +76,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
selectedLocation.owner,
|
||||
selectedLocation.repo
|
||||
)} - ${selectedLocation.branch}`;
|
||||
} else if (selectedLocation.type === "MyNotebooks") {
|
||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||
destination = useNotebook.getState().notebookFolderName;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,21 +369,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -663,7 +660,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -945,7 +941,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1228,7 +1223,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -5305,21 +5305,18 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Execute"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Execute"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Execute"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Execute"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Execute"
|
||||
theme={
|
||||
@@ -5599,7 +5596,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Execute"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -5881,7 +5877,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Execute"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -6164,7 +6159,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Execute"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -35,9 +35,6 @@ interface IGitHubReposPanelState {
|
||||
}
|
||||
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
||||
private static readonly PageSize = 30;
|
||||
private static readonly MasterBranchName = "master";
|
||||
private static readonly MainBranchName = "main";
|
||||
|
||||
private isAddedRepo = false;
|
||||
private gitHubClient: GitHubClient;
|
||||
private junoClient: JunoClient;
|
||||
@@ -119,8 +116,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||
}
|
||||
|
||||
this.props.explorer.notebookManager?.refreshPinnedRepos();
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||
}
|
||||
@@ -212,14 +207,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
if (response.data) {
|
||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||
branchesProps.lastPageInfo = response.pageInfo;
|
||||
branchesProps.defaultBranchName = branchesProps.branches[0].name;
|
||||
const defaultbranchName = branchesProps.branches.find(
|
||||
(branch) =>
|
||||
branch.name === GitHubReposPanel.MasterBranchName || branch.name === GitHubReposPanel.MainBranchName
|
||||
)?.name;
|
||||
if (defaultbranchName) {
|
||||
branchesProps.defaultBranchName = defaultbranchName;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
||||
@@ -311,17 +298,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
||||
if (existingRepo) {
|
||||
existingRepo.branches = item.branches;
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
||||
}
|
||||
@@ -398,7 +374,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
lastPageInfo: undefined,
|
||||
hasMore: true,
|
||||
isLoading: true,
|
||||
defaultBranchName: undefined,
|
||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||
};
|
||||
this.loadMoreBranches(item.repo);
|
||||
|
||||
@@ -23,13 +23,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
||||
@@ -3,20 +3,12 @@ import React from "react";
|
||||
|
||||
export interface PanelFooterProps {
|
||||
buttonLabel: string;
|
||||
isButtonDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
||||
buttonLabel,
|
||||
isButtonDisabled,
|
||||
}: PanelFooterProps): JSX.Element => (
|
||||
<div className="panelFooter">
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
id="sidePanelOkButton"
|
||||
text={buttonLabel}
|
||||
ariaLabel={buttonLabel}
|
||||
disabled={!!isButtonDisabled}
|
||||
/>
|
||||
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface RightPaneFormProps {
|
||||
onSubmit: () => void;
|
||||
submitButtonText: string;
|
||||
isSubmitButtonHidden?: boolean;
|
||||
isSubmitButtonDisabled?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -19,7 +18,6 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||
onSubmit,
|
||||
submitButtonText,
|
||||
isSubmitButtonHidden = false,
|
||||
isSubmitButtonDisabled = false,
|
||||
children,
|
||||
}: RightPaneFormProps) => {
|
||||
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -32,9 +30,7 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
||||
{formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
|
||||
{children}
|
||||
{!isSubmitButtonHidden && (
|
||||
<PanelFooterComponent buttonLabel={submitButtonText} isButtonDisabled={isSubmitButtonDisabled} />
|
||||
)}
|
||||
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
||||
</form>
|
||||
{isExecuting && <PanelLoadingScreen />}
|
||||
</>
|
||||
|
||||
@@ -14,21 +14,18 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Load"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Load"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Load"
|
||||
theme={
|
||||
@@ -308,7 +305,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -590,7 +586,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -873,7 +868,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Load"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -13,13 +13,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
@@ -681,21 +675,18 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Create"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Create"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Create"
|
||||
theme={
|
||||
@@ -975,7 +966,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1257,7 +1247,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1540,7 +1529,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Create"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -1262,21 +1262,18 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -1556,7 +1553,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1838,7 +1834,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -2121,7 +2116,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -356,21 +356,18 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Add Entity"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Add Entity"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Add Entity"
|
||||
theme={
|
||||
@@ -650,7 +647,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -932,7 +928,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1215,7 +1210,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Add Entity"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -357,21 +357,18 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Update"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Update"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Update"
|
||||
theme={
|
||||
@@ -651,7 +648,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -933,7 +929,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1216,7 +1211,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Update"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -1041,21 +1041,18 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -1335,7 +1332,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1617,7 +1613,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1900,7 +1895,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -84,7 +84,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
const mainItems = this.createMainItems();
|
||||
const commonTaskItems = this.createCommonTaskItems();
|
||||
let recentItems = this.createRecentItems();
|
||||
recentItems = recentItems.filter((item) => item.description !== "Notebook");
|
||||
if (userContext.features.notebooksTemporarilyDown) {
|
||||
recentItems = recentItems.filter((item) => item.description !== "Notebook");
|
||||
}
|
||||
|
||||
const tipsItems = this.createTipsItems();
|
||||
const onClearRecent = this.clearMostRecent;
|
||||
|
||||
@@ -220,7 +223,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
});
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (useNotebook.getState().isNotebookEnabled && !userContext.features.notebooksTemporarilyDown) {
|
||||
heroes.push({
|
||||
iconSrc: NewNotebookIcon,
|
||||
title: "New Notebook",
|
||||
@@ -304,20 +307,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
title: "New " + getDatabaseName(),
|
||||
description: undefined,
|
||||
onClick: async () => {
|
||||
if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) {
|
||||
await useDatabases.getState().loadAllOffers();
|
||||
}
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />);
|
||||
},
|
||||
onClick: () => this.openAddDatabasePanel(),
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private openAddDatabasePanel() {
|
||||
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"New " + getDatabaseName(),
|
||||
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
|
||||
);
|
||||
}
|
||||
|
||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||
return {
|
||||
iconSrc: NotebookIcon,
|
||||
|
||||
@@ -202,21 +202,14 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
|
||||
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
||||
let isPropertyUpdated = false;
|
||||
let isFirstPropertyToUpdate = true;
|
||||
for (let property in newEntity) {
|
||||
if (
|
||||
!originalDocument[property] ||
|
||||
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
||||
) {
|
||||
let propertyQuerySegment = this.isStringType(newEntity[property].$)
|
||||
? `${property} = '${newEntity[property]._}',`
|
||||
: `${property} = ${newEntity[property]._},`;
|
||||
// Only add the "SET" keyword once
|
||||
if (isFirstPropertyToUpdate) {
|
||||
propertyQuerySegment = " SET " + propertyQuerySegment;
|
||||
isFirstPropertyToUpdate = false;
|
||||
}
|
||||
updateQuery += propertyQuerySegment;
|
||||
updateQuery += this.isStringType(newEntity[property].$)
|
||||
? ` SET ${property} = '${newEntity[property]._}',`
|
||||
: ` SET ${property} = ${newEntity[property]._},`;
|
||||
isPropertyUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as Constants from "./Constants";
|
||||
import * as Entities from "./Entities";
|
||||
@@ -16,12 +17,12 @@ enum DataTypes {
|
||||
Int64 = 18,
|
||||
}
|
||||
|
||||
var tablesIndexers = {
|
||||
const tablesIndexers = {
|
||||
Value: "$v",
|
||||
Type: "$t",
|
||||
};
|
||||
|
||||
export var keyProperties = {
|
||||
export const keyProperties = {
|
||||
PartitionKey: "$pk",
|
||||
Id: "id",
|
||||
Id2: "$id", // This should always be the same value as Id
|
||||
@@ -33,14 +34,17 @@ export var keyProperties = {
|
||||
};
|
||||
|
||||
export function convertDocumentsToEntities(documents: any[]): Entities.ITableEntityForTablesAPI[] {
|
||||
let results: Entities.ITableEntityForTablesAPI[] = [];
|
||||
const results: Entities.ITableEntityForTablesAPI[] = [];
|
||||
documents &&
|
||||
documents.forEach((document) => {
|
||||
if (!document.hasOwnProperty(keyProperties.PartitionKey) || !document.hasOwnProperty(keyProperties.Id)) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(document, keyProperties.PartitionKey) ||
|
||||
Object.prototype.hasOwnProperty.call(document, keyProperties.Id)
|
||||
) {
|
||||
//Document does not match the current required format for Tables, so we ignore it
|
||||
return; // The rest of the key properties should be guaranteed as DocumentDB properties
|
||||
}
|
||||
let entity: Entities.ITableEntityForTablesAPI = <Entities.ITableEntityForTablesAPI>{
|
||||
const entity: Entities.ITableEntityForTablesAPI = <Entities.ITableEntityForTablesAPI>{
|
||||
PartitionKey: {
|
||||
_: document[keyProperties.PartitionKey],
|
||||
$: Constants.TableType.String,
|
||||
@@ -71,8 +75,8 @@ export function convertDocumentsToEntities(documents: any[]): Entities.ITableEnt
|
||||
$: Constants.TableType.String,
|
||||
},
|
||||
};
|
||||
for (var property in document) {
|
||||
if (document.hasOwnProperty(property)) {
|
||||
for (const property in document) {
|
||||
if (Object.prototype.hasOwnProperty.call(document, property)) {
|
||||
if (
|
||||
property !== keyProperties.PartitionKey &&
|
||||
property !== keyProperties.Id &&
|
||||
@@ -83,7 +87,10 @@ export function convertDocumentsToEntities(documents: any[]): Entities.ITableEnt
|
||||
property !== keyProperties.attachments &&
|
||||
property !== keyProperties.Id2
|
||||
) {
|
||||
if (!document[property].hasOwnProperty("$v") || !document[property].hasOwnProperty("$t")) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(document[property], "$v") ||
|
||||
!Object.prototype.hasOwnProperty.call(document[property], "$t")
|
||||
) {
|
||||
return; //Document property does not match the current required format for Tables, so we ignore it
|
||||
}
|
||||
if (DataTypes[document[property][tablesIndexers.Type]] === DataTypes[DataTypes.DateTime]) {
|
||||
@@ -111,10 +118,10 @@ export function convertEntitiesToDocuments(
|
||||
entities: Entities.ITableEntityForTablesAPI[],
|
||||
collection: ViewModels.Collection
|
||||
): any[] {
|
||||
let results: any[] = [];
|
||||
const results: any[] = [];
|
||||
entities &&
|
||||
entities.forEach((entity) => {
|
||||
let document: any = {
|
||||
const document: any = {
|
||||
$id: entity.RowKey._,
|
||||
id: entity.RowKey._,
|
||||
ts: DateTimeUtilities.convertJSDateToUnix(entity.Timestamp._), // Convert back to unix time
|
||||
@@ -129,7 +136,7 @@ export function convertEntitiesToDocuments(
|
||||
document[collection.partitionKeyProperty] = entity.PartitionKey._;
|
||||
document["partitionKeyValue"] = entity.PartitionKey._;
|
||||
}
|
||||
for (var property in entity) {
|
||||
for (const property in entity) {
|
||||
if (
|
||||
property !== Constants.EntityKeyNames.PartitionKey &&
|
||||
property !== Constants.EntityKeyNames.RowKey &&
|
||||
@@ -160,7 +167,7 @@ export function convertEntitiesToDocuments(
|
||||
}
|
||||
|
||||
export function convertEntityToNewDocument(entity: Entities.ITableEntityForTablesAPI): any {
|
||||
let document: any = {
|
||||
const document: any = {
|
||||
$pk: entity.PartitionKey._,
|
||||
$id: entity.RowKey._,
|
||||
id: entity.RowKey._,
|
||||
|
||||
@@ -92,7 +92,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
|
||||
this.partitionKeyPropertyHeader =
|
||||
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
|
||||
this.partitionKeyProperty = !!this.partitionKeyPropertyHeader
|
||||
this.partitionKeyProperty = this.partitionKeyPropertyHeader
|
||||
? this.partitionKeyPropertyHeader.replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "")
|
||||
: null;
|
||||
|
||||
@@ -446,8 +446,8 @@ export default class DocumentsTab extends TabsBase {
|
||||
this.partitionKey as PartitionKeyDefinition
|
||||
);
|
||||
const partitionKeyValue = partitionKeyValueArray && partitionKeyValueArray[0];
|
||||
let id = new DocumentId(this, savedDocument, partitionKeyValue);
|
||||
let ids = this.documentIds();
|
||||
const id = new DocumentId(this, savedDocument, partitionKeyValue);
|
||||
const ids = this.documentIds();
|
||||
ids.push(id);
|
||||
|
||||
this.selectedDocumentId(id);
|
||||
@@ -682,10 +682,10 @@ export default class DocumentsTab extends TabsBase {
|
||||
}
|
||||
|
||||
public createIterator(): QueryIterator<ItemDefinition & Resource> {
|
||||
let filters = this.lastFilterContents();
|
||||
const filters = this.lastFilterContents();
|
||||
const filter: string = this.filterContent().trim();
|
||||
const query: string = this.buildQuery(filter);
|
||||
let options: any = {};
|
||||
const options: any = {};
|
||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
||||
|
||||
if (this._resourceTokenPartitionKey) {
|
||||
@@ -778,7 +778,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
|
||||
protected _onEditorContentChange(newContent: string) {
|
||||
try {
|
||||
let parsed: any = JSON.parse(newContent);
|
||||
const parsed: any = JSON.parse(newContent);
|
||||
this.onValidDocumentEdit();
|
||||
} catch (e) {
|
||||
this.onInvalidDocumentEdit();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { extractPartitionKey, PartitionKeyDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
@@ -44,7 +46,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
super.buildCommandBarOptions();
|
||||
}
|
||||
|
||||
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||
public onSaveNewDocumentClick = (): Promise<void> => {
|
||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||
this.displayedError("");
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||
@@ -59,9 +61,8 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
) {
|
||||
const message = `The document is lacking the shard property: ${this.partitionKeyProperty}`;
|
||||
this.displayedError(message);
|
||||
let that = this;
|
||||
setTimeout(() => {
|
||||
that.displayedError("");
|
||||
this.displayedError("");
|
||||
}, Constants.ClientDefaults.errorNotificationTimeoutMs);
|
||||
this.isExecutionError(true);
|
||||
TelemetryProcessor.traceFailure(
|
||||
@@ -82,19 +83,19 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
return createDocument(this.collection.databaseId, this.collection, this.partitionKeyProperty, documentContent)
|
||||
.then(
|
||||
(savedDocument: any) => {
|
||||
let partitionKeyArray = extractPartitionKey(
|
||||
const partitionKeyArray = extractPartitionKey(
|
||||
savedDocument,
|
||||
this._getPartitionKeyDefinition() as PartitionKeyDefinition
|
||||
);
|
||||
|
||||
let partitionKeyValue = partitionKeyArray && partitionKeyArray[0];
|
||||
const partitionKeyValue = partitionKeyArray && partitionKeyArray[0];
|
||||
|
||||
let id = new ObjectId(this, savedDocument, partitionKeyValue);
|
||||
let ids = this.documentIds();
|
||||
const id = new ObjectId(this, savedDocument, partitionKeyValue);
|
||||
const ids = this.documentIds();
|
||||
ids.push(id);
|
||||
delete savedDocument._self;
|
||||
|
||||
let value: string = this.renderObjectForEditor(savedDocument || {}, null, 4);
|
||||
const value: string = this.renderObjectForEditor(savedDocument || {}, null, 4);
|
||||
this.selectedDocumentContent.setBaseline(value);
|
||||
|
||||
this.selectedDocumentId(id);
|
||||
@@ -128,7 +129,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
.finally(() => this.isExecuting(false));
|
||||
};
|
||||
|
||||
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||
public onSaveExisitingDocumentClick = (): Promise<void> => {
|
||||
const selectedDocumentId = this.selectedDocumentId();
|
||||
const documentContent = this.selectedDocumentContent();
|
||||
this.isExecutionError(false);
|
||||
@@ -141,7 +142,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
return updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent)
|
||||
.then(
|
||||
(updatedDocument: any) => {
|
||||
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
||||
const value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
||||
this.selectedDocumentContent.setBaseline(value);
|
||||
|
||||
this.documentIds().forEach((documentId: DocumentId) => {
|
||||
@@ -151,7 +152,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
this._getPartitionKeyDefinition() as PartitionKeyDefinition
|
||||
);
|
||||
|
||||
let partitionKeyValue = partitionKeyArray && partitionKeyArray[0];
|
||||
const partitionKeyValue = partitionKeyArray && partitionKeyArray[0];
|
||||
|
||||
const id = new ObjectId(this, updatedDocument, partitionKeyValue);
|
||||
documentId.id(id.id());
|
||||
@@ -196,7 +197,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
this.initDocumentEditor(documentId, content);
|
||||
}
|
||||
|
||||
public loadNextPage(): Q.Promise<any> {
|
||||
public loadNextPage(): Q.Promise<void> {
|
||||
this.isExecuting(true);
|
||||
this.isExecutionError(false);
|
||||
const filter: string = this.filterContent().trim();
|
||||
@@ -228,7 +229,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
this.selectedDocumentId(null);
|
||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||
}
|
||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||
if (this.onLoadStartKey !== null && this.onLoadStartKey !== undefined) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.Tab,
|
||||
{
|
||||
@@ -243,8 +244,8 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
this.onLoadStartKey = null;
|
||||
}
|
||||
},
|
||||
(error: any) => {
|
||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||
(error: Error) => {
|
||||
if (this.onLoadStartKey !== null && this.onLoadStartKey !== undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
@@ -265,13 +266,13 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
||||
.finally(() => this.isExecuting(false));
|
||||
}
|
||||
|
||||
protected _onEditorContentChange(newContent: string) {
|
||||
protected _onEditorContentChange(newContent: string): void {
|
||||
try {
|
||||
if (
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid ||
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid
|
||||
) {
|
||||
let parsed: any = JSON.parse(newContent);
|
||||
const parsed: any = JSON.parse(newContent);
|
||||
}
|
||||
|
||||
// Mongo uses BSON format for _id, trying to parse it as JSON blocks normal flow in an edit
|
||||
|
||||
@@ -17,7 +17,6 @@ export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
||||
|
||||
/**
|
||||
* Every notebook-based tab inherits from this class. It holds the static reference to a notebook client (singleton)
|
||||
* Re-initiating the constructor when ever a new container got allocated.
|
||||
*/
|
||||
export default class NotebookTabBase extends TabsBase {
|
||||
protected static clientManager: NotebookClientV2;
|
||||
@@ -28,15 +27,6 @@ export default class NotebookTabBase extends TabsBase {
|
||||
|
||||
this.container = options.container;
|
||||
|
||||
useNotebook.subscribe(
|
||||
() => {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint) {
|
||||
NotebookTabBase.clientManager = undefined;
|
||||
}
|
||||
},
|
||||
(state) => state.notebookServerInfo
|
||||
);
|
||||
if (!NotebookTabBase.clientManager) {
|
||||
NotebookTabBase.clientManager = new NotebookClientV2({
|
||||
connectionInfo: useNotebook.getState().notebookServerInfo,
|
||||
|
||||
@@ -53,16 +53,14 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
||||
onUpdateKernelInfo: this.onKernelUpdate,
|
||||
});
|
||||
}
|
||||
/*
|
||||
* Hard cleaning the workspace(Closing tabs connected with old container connection) when new container got allocated.
|
||||
*/
|
||||
public onCloseTabButtonClick(hardClose = false): Q.Promise<any> {
|
||||
|
||||
public onCloseTabButtonClick(): Q.Promise<any> {
|
||||
const cleanup = () => {
|
||||
this.notebookComponentAdapter.notebookShutdown();
|
||||
super.onCloseTabButtonClick();
|
||||
};
|
||||
|
||||
if (this.notebookComponentAdapter.isContentDirty() && hardClose === false) {
|
||||
if (this.notebookComponentAdapter.isContentDirty()) {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkCancelModalDialog(
|
||||
|
||||
@@ -100,7 +100,6 @@ export default class TerminalTab extends TabsBase {
|
||||
return {
|
||||
authToken: info.authToken,
|
||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
||||
forwardingId: info.forwardingId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import * as ko from "knockout";
|
||||
import * as _ from "underscore";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
@@ -529,9 +528,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
};
|
||||
|
||||
public onSchemaAnalyzerClick = async () => {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
await this.container.allocateContainer();
|
||||
}
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
||||
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
||||
@@ -576,9 +572,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
public onSettingsClick = async (): Promise<void> => {
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
userContext.databaseAccount?.properties.capacity?.totalThroughputLimit
|
||||
? await useDatabases.getState().loadAllOffers()
|
||||
: await this.loadOffer();
|
||||
await this.loadOffer();
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Settings node",
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class Database implements ViewModels.Database {
|
||||
this.isOfferRead = false;
|
||||
}
|
||||
|
||||
public onSettingsClick = async (): Promise<void> => {
|
||||
public onSettingsClick = (): void => {
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
@@ -66,10 +66,6 @@ export default class Database implements ViewModels.Database {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
if (userContext.databaseAccount?.properties.capacity?.totalThroughputLimit) {
|
||||
await useDatabases.getState().loadAllOffers();
|
||||
}
|
||||
|
||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||
|
||||
@@ -121,17 +121,18 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (useNotebook.getState().isPhoenix === false && userContext.features.notebooksDownBanner) {
|
||||
if (userContext.features.notebooksTemporarilyDown) {
|
||||
notebooksTree.children.push(buildNotebooksTemporarilyDownTree());
|
||||
} else if (useNotebook.getState().isPhoenix) {
|
||||
} else {
|
||||
if (galleryContentRoot) {
|
||||
notebooksTree.children.push(buildGalleryNotebooksTree());
|
||||
}
|
||||
|
||||
if (
|
||||
myNotebooksContentRoot &&
|
||||
useNotebook.getState().isPhoenix &&
|
||||
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected
|
||||
((NotebookUtil.isPhoenixEnabled() &&
|
||||
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) ||
|
||||
userContext.features.phoenix === false)
|
||||
) {
|
||||
notebooksTree.children.push(buildMyNotebooksTree());
|
||||
}
|
||||
@@ -165,7 +166,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
const myNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||
myNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
container.openNotebook(item);
|
||||
container.openNotebook(item).then((hasOpened) => {
|
||||
if (
|
||||
hasOpened &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === false
|
||||
) {
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -180,7 +189,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||
gitHubNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
container.openNotebook(item);
|
||||
container.openNotebook(item).then((hasOpened) => {
|
||||
if (
|
||||
hasOpened &&
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === false
|
||||
) {
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
},
|
||||
true
|
||||
);
|
||||
@@ -511,7 +528,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
isNotebookEnabled &&
|
||||
userContext.apiType === "Mongo" &&
|
||||
isPublicInternetAccessAllowed() &&
|
||||
useNotebook.getState().isPhoenix
|
||||
!userContext.features.notebooksTemporarilyDown
|
||||
) {
|
||||
children.push({
|
||||
label: "Schema (Preview)",
|
||||
|
||||
@@ -18,7 +18,6 @@ interface DatabasesState {
|
||||
findCollection: (databaseId: string, collectionId: string) => ViewModels.Collection;
|
||||
isLastCollection: () => boolean;
|
||||
loadDatabaseOffers: () => Promise<void>;
|
||||
loadAllOffers: () => Promise<void>;
|
||||
isFirstResourceCreated: () => boolean;
|
||||
findSelectedDatabase: () => ViewModels.Database;
|
||||
validateDatabaseId: (id: string) => boolean;
|
||||
@@ -98,19 +97,6 @@ export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||
})
|
||||
);
|
||||
},
|
||||
loadAllOffers: async () => {
|
||||
await Promise.all(
|
||||
get().databases?.map(async (database: ViewModels.Database) => {
|
||||
await database.loadOffer();
|
||||
await database.loadCollections();
|
||||
await Promise.all(
|
||||
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
||||
await collection.loadOffer();
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
isFirstResourceCreated: () => {
|
||||
const databases = get().databases;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ConnectionStatusType } from "Common/Constants";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import create, { UseStore } from "zustand";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { useTabs } from "../hooks/useTabs";
|
||||
@@ -14,7 +12,6 @@ export interface SelectedNodeState {
|
||||
collectionId?: string,
|
||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||
) => boolean;
|
||||
isConnectedToContainer: () => boolean;
|
||||
}
|
||||
|
||||
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
|
||||
@@ -62,7 +59,4 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
||||
subnodeKinds.includes(selectedSubnodeKind)
|
||||
);
|
||||
},
|
||||
isConnectedToContainer: (): boolean => {
|
||||
return useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected;
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,59 +1,33 @@
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import { ContainerStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import {
|
||||
ContainerInfo,
|
||||
IAccountData,
|
||||
IContainerData,
|
||||
IPhoenixConnectionInfoResult,
|
||||
IProvisionData,
|
||||
IResponse,
|
||||
} from "../Contracts/DataModels";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
|
||||
export interface IPhoenixResponse<T> {
|
||||
status: number;
|
||||
data: T;
|
||||
}
|
||||
export interface IPhoenixConnectionInfoResult {
|
||||
readonly notebookAuthToken?: string;
|
||||
readonly notebookServerUrl?: string;
|
||||
}
|
||||
export interface IProvosionData {
|
||||
cosmosEndpoint: string;
|
||||
dbAccountName: string;
|
||||
aadToken: string;
|
||||
resourceGroup: string;
|
||||
subscriptionId: string;
|
||||
}
|
||||
export class PhoenixClient {
|
||||
private containerHealthHandler: NodeJS.Timeout;
|
||||
private retryOptions: promiseRetry.Options = {
|
||||
retries: Notebook.retryAttempts,
|
||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||
minTimeout: Notebook.retryAttemptDelayMs,
|
||||
};
|
||||
|
||||
public async allocateContainer(
|
||||
provisionData: IProvisionData,
|
||||
accountData: IAccountData
|
||||
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
return this.executeContainerAssignmentOperation(provisionData, accountData, "allocate");
|
||||
}
|
||||
|
||||
public async resetContainer(
|
||||
provisionData: IProvisionData,
|
||||
accountData: IAccountData
|
||||
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
return this.executeContainerAssignmentOperation(provisionData, accountData, "reset");
|
||||
}
|
||||
|
||||
private async executeContainerAssignmentOperation(
|
||||
provisionData: IProvisionData,
|
||||
accountData: IAccountData,
|
||||
operation: string
|
||||
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
public async containerConnectionInfo(
|
||||
provisionData: IProvosionData
|
||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${accountData.subscriptionId}/resourceGroups/${
|
||||
accountData.resourceGroup
|
||||
}/providers/Microsoft.DocumentDB/databaseAccounts/${accountData.dbAccountName}/containerconnections`,
|
||||
{
|
||||
method: operation === "allocate" ? "POST" : "PATCH",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
body: JSON.stringify(provisionData),
|
||||
}
|
||||
);
|
||||
|
||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
|
||||
method: "POST",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
body: JSON.stringify(provisionData),
|
||||
});
|
||||
let data: IPhoenixConnectionInfoResult;
|
||||
if (response.status === HttpStatusCodes.OK) {
|
||||
data = await response.json();
|
||||
@@ -68,90 +42,6 @@ export class PhoenixClient {
|
||||
}
|
||||
}
|
||||
|
||||
public async initiateContainerHeartBeat(containerData: IContainerData) {
|
||||
if (this.containerHealthHandler) {
|
||||
clearTimeout(this.containerHealthHandler);
|
||||
}
|
||||
await this.getContainerHealth(Notebook.containerStatusHeartbeatDelayMs, containerData);
|
||||
}
|
||||
|
||||
private scheduleContainerHeartbeat(delayMs: number, containerData: IContainerData): void {
|
||||
this.containerHealthHandler = setTimeout(async () => {
|
||||
await this.getContainerHealth(delayMs, containerData);
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
private async getContainerStatusAsync(containerData: IContainerData): Promise<ContainerInfo> {
|
||||
try {
|
||||
const runContainerStatusAsync = async () => {
|
||||
const response = await window.fetch(
|
||||
`${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${containerData.subscriptionId}/resourceGroups/${
|
||||
containerData.resourceGroup
|
||||
}/providers/Microsoft.DocumentDB/databaseAccounts/${containerData.dbAccountName}/${
|
||||
containerData.forwardingId
|
||||
}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
}
|
||||
);
|
||||
if (response.status === HttpStatusCodes.OK) {
|
||||
const containerStatus = await response.json();
|
||||
return {
|
||||
durationLeftInMinutes: containerStatus?.durationLeftInMinutes,
|
||||
notebookServerInfo: containerStatus?.notebookServerInfo,
|
||||
status: ContainerStatusType.Active,
|
||||
};
|
||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
||||
throw new AbortError(response.statusText);
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
};
|
||||
return await promiseRetry(runContainerStatusAsync, this.retryOptions);
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "PhoenixClient/getContainerStatus");
|
||||
return {
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
status: ContainerStatusType.Disconnected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async getContainerHealth(delayMs: number, containerData: IContainerData) {
|
||||
try {
|
||||
const containerInfo = await this.getContainerStatusAsync(containerData);
|
||||
useNotebook.getState().setContainerStatus(containerInfo);
|
||||
if (useNotebook.getState().containerStatus?.status === ContainerStatusType.Active) {
|
||||
this.scheduleContainerHeartbeat(delayMs, containerData);
|
||||
}
|
||||
} catch (exception) {
|
||||
useNotebook.getState().setContainerStatus({
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
status: ContainerStatusType.Disconnected,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async IsDbAcountWhitelisted(accountData: IAccountData) {
|
||||
try {
|
||||
const response = await window.fetch(
|
||||
`${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${accountData.subscriptionId}/resourceGroups/${
|
||||
accountData.resourceGroup
|
||||
}/providers/Microsoft.DocumentDB/databaseAccounts/${accountData.dbAccountName}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
}
|
||||
);
|
||||
return response.status === HttpStatusCodes.OK;
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "PhoenixClient/IsDbAcountWhitelisted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static getPhoenixEndpoint(): string {
|
||||
const phoenixEndpoint =
|
||||
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||
@@ -165,9 +55,8 @@ export class PhoenixClient {
|
||||
}
|
||||
|
||||
public getPhoenixContainerPoolingEndPoint(): string {
|
||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts`;
|
||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`;
|
||||
}
|
||||
|
||||
private static getHeaders(): HeadersInit {
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,7 @@ export type Features = {
|
||||
autoscaleDefault: boolean;
|
||||
partitionKeyDefault: boolean;
|
||||
partitionKeyDefault2: boolean;
|
||||
notebooksDownBanner: boolean;
|
||||
phoenix: boolean;
|
||||
readonly enableSDKoperations: boolean;
|
||||
readonly enableSpark: boolean;
|
||||
readonly enableTtl: boolean;
|
||||
@@ -32,7 +32,7 @@ export type Features = {
|
||||
readonly ttl90Days: boolean;
|
||||
readonly mongoProxyEndpoint?: string;
|
||||
readonly mongoProxyAPIs?: string;
|
||||
readonly enableThroughputCap: boolean;
|
||||
readonly notebooksTemporarilyDown: boolean;
|
||||
};
|
||||
|
||||
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
|
||||
@@ -82,8 +82,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||
autoscaleDefault: "true" === get("autoscaledefault"),
|
||||
partitionKeyDefault: "true" === get("partitionkeytest"),
|
||||
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
||||
notebooksDownBanner: "true" === get("notebooksDownBanner"),
|
||||
enableThroughputCap: "true" === get("enablethroughputcap"),
|
||||
notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
|
||||
phoenix: "true" === get("phoenix"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { RefreshResult } from "../SelfServeTypes";
|
||||
import SqlX from "./SqlX";
|
||||
import {
|
||||
FetchPricesResponse,
|
||||
PriceMapAndCurrencyCode,
|
||||
RegionsResponse,
|
||||
SqlxServiceResource,
|
||||
UpdateDedicatedGatewayRequestParameters,
|
||||
@@ -179,18 +178,18 @@ const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
||||
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
||||
};
|
||||
|
||||
export const getPriceMapAndCurrencyCode = async (regions: Array<string>): Promise<PriceMapAndCurrencyCode> => {
|
||||
export const getPriceMap = async (regions: Array<string>): Promise<Map<string, Map<string, number>>> => {
|
||||
const telemetryData = {
|
||||
feature: "Calculate approximate cost",
|
||||
function: "getPriceMapAndCurrencyCode",
|
||||
function: "getPriceMap",
|
||||
description: "fetch prices API call",
|
||||
selfServeClassName: SqlX.name,
|
||||
};
|
||||
const getPriceMapAndCurrencyCodeTimestamp = selfServeTraceStart(telemetryData);
|
||||
const getPriceMapTimestamp = selfServeTraceStart(telemetryData);
|
||||
|
||||
try {
|
||||
const priceMap = new Map<string, Map<string, number>>();
|
||||
let currencyCode;
|
||||
|
||||
for (const region of regions) {
|
||||
const regionPriceMap = new Map<string, number>();
|
||||
|
||||
@@ -208,21 +207,17 @@ export const getPriceMapAndCurrencyCode = async (regions: Array<string>): Promis
|
||||
});
|
||||
|
||||
for (const item of response.result.Items) {
|
||||
if (currencyCode === undefined) {
|
||||
currencyCode = item.currencyCode;
|
||||
} else if (item.currencyCode !== currencyCode) {
|
||||
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
||||
}
|
||||
regionPriceMap.set(item.skuName, item.retailPrice);
|
||||
}
|
||||
priceMap.set(region, regionPriceMap);
|
||||
}
|
||||
|
||||
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
||||
return { priceMap: priceMap, currencyCode: currencyCode };
|
||||
selfServeTraceSuccess(telemetryData, getPriceMapTimestamp);
|
||||
return priceMap;
|
||||
} catch (err) {
|
||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
||||
return { priceMap: undefined, currencyCode: undefined };
|
||||
selfServeTraceFailure(failureTelemetry, getPriceMapTimestamp);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import { BladeType, generateBladeLink } from "../SelfServeUtils";
|
||||
import {
|
||||
deleteDedicatedGatewayResource,
|
||||
getCurrentProvisioningState,
|
||||
getPriceMapAndCurrencyCode,
|
||||
getPriceMap,
|
||||
getRegions,
|
||||
refreshDedicatedGatewayProvisioning,
|
||||
updateDedicatedGatewayResource,
|
||||
@@ -207,7 +207,6 @@ const ApproximateCostDropDownInfo: Info = {
|
||||
};
|
||||
|
||||
let priceMap: Map<string, Map<string, number>>;
|
||||
let currencyCode: string;
|
||||
let regions: Array<string>;
|
||||
|
||||
const calculateCost = (skuName: string, instanceCount: number): Description => {
|
||||
@@ -238,7 +237,7 @@ const calculateCost = (skuName: string, instanceCount: number): Description => {
|
||||
|
||||
selfServeTraceSuccess(telemetryData, calculateCostTimestamp);
|
||||
return {
|
||||
textTKey: `${costPerHour} ${currencyCode}`,
|
||||
textTKey: `${costPerHour} USD`,
|
||||
type: DescriptionType.Text,
|
||||
};
|
||||
} catch (err) {
|
||||
@@ -347,9 +346,7 @@ export default class SqlX extends SelfServeBaseClass {
|
||||
});
|
||||
|
||||
regions = await getRegions();
|
||||
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(regions);
|
||||
priceMap = priceMapAndCurrencyCode.priceMap;
|
||||
currencyCode = priceMapAndCurrencyCode.currencyCode;
|
||||
priceMap = await getPriceMap(regions);
|
||||
|
||||
const response = await getCurrentProvisioningState();
|
||||
if (response.status && response.status !== "Deleting") {
|
||||
|
||||
@@ -36,15 +36,9 @@ export type FetchPricesResponse = {
|
||||
Count: number;
|
||||
};
|
||||
|
||||
export type PriceMapAndCurrencyCode = {
|
||||
priceMap: Map<string, Map<string, number>>;
|
||||
currencyCode: string;
|
||||
};
|
||||
|
||||
export type PriceItem = {
|
||||
retailPrice: number;
|
||||
skuName: string;
|
||||
currencyCode: string;
|
||||
};
|
||||
|
||||
export type RegionsResponse = {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SortBy,
|
||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -228,7 +229,7 @@ export function downloadItem(
|
||||
undefined,
|
||||
"Download",
|
||||
async () => {
|
||||
if (useNotebook.getState().isPhoenix) {
|
||||
if (NotebookUtil.isPhoenixEnabled()) {
|
||||
await container.allocateContainer();
|
||||
}
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
@@ -238,7 +239,7 @@ export function downloadItem(
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Failed to connect",
|
||||
"Failed to Connect",
|
||||
"Failed to connect to temporary workspace. Please refresh the page and try again."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import useSWR from "swr";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||
import { userContext } from "../UserContext";
|
||||
|
||||
interface AccountListResult {
|
||||
nextLink: string;
|
||||
@@ -15,8 +14,8 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
|
||||
headers.append("Authorization", bearer);
|
||||
|
||||
let accounts: Array<DatabaseAccount> = [];
|
||||
const apiVersion = userContext.features.enableThroughputCap ? "2021-10-15-preview" : "2021-06-15";
|
||||
let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=${apiVersion}`;
|
||||
|
||||
let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2021-06-15`;
|
||||
|
||||
while (nextLink) {
|
||||
const response: Response = await fetch(nextLink, { headers });
|
||||
|
||||
@@ -339,8 +339,8 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
||||
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
|
||||
userContext.features.partitionKeyDefault2 = true;
|
||||
}
|
||||
if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) {
|
||||
userContext.features.notebooksDownBanner = true;
|
||||
if (inputs.flights.indexOf(Flights.Phoenix) !== -1) {
|
||||
userContext.features.phoenix = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import create, { UseStore } from "zustand";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||
|
||||
interface TabsState {
|
||||
@@ -14,7 +12,6 @@ interface TabsState {
|
||||
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void;
|
||||
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void;
|
||||
closeTab: (tab: TabsBase) => void;
|
||||
closeAllNotebookTabs: (hardClose: boolean) => void;
|
||||
}
|
||||
|
||||
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
@@ -81,31 +78,4 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
|
||||
set({ openedTabs: updatedTabs });
|
||||
},
|
||||
closeAllNotebookTabs: (hardClose): void => {
|
||||
const isNotebook = (tabKind: CollectionTabKind): boolean => {
|
||||
if (
|
||||
tabKind === CollectionTabKind.Notebook ||
|
||||
tabKind === CollectionTabKind.NotebookV2 ||
|
||||
tabKind === CollectionTabKind.SchemaAnalyzer ||
|
||||
tabKind === CollectionTabKind.Terminal
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const tabList = get().openedTabs;
|
||||
if (tabList && tabList.length > 0) {
|
||||
tabList.forEach((tab: NotebookTabV2) => {
|
||||
const tabKind: CollectionTabKind = tab.tabKind;
|
||||
if (tabKind && isNotebook(tabKind)) {
|
||||
tab.onCloseTabButtonClick(hardClose);
|
||||
}
|
||||
});
|
||||
|
||||
if (get().openedTabs.length === 0) {
|
||||
set({ activeTab: undefined });
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user