mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 00:47:01 +00:00
Users/kcheekuri/aciconatinerpooling (#1008)
* initial changes for CP * Added container unprovisioning * Added postgreSQL terminal * changed postgres terminal -> shell * Initialize Container Request payload change * added postgres button * Added notebookServerInfo * Feature flag enabling and integration with phoenix * Remove postgre implementations * fix issues * fix format issues * fix format issues-1 * fix format issues-2 * fix format issues-3 * fix format issues-4 * fix format issues-5 * connection status component * connection status component-1 * connection status component-2 * connection status component-3 * address issues * removal of ms * removal of ms * removal of ms-1 * removal of time after connected * removal of time after connected * removing unnecessary code Co-authored-by: Srinath Narayanan <srnara@microsoft.com> Co-authored-by: Bala Lakshmi Narayanasami <balalakshmin@microsoft.com>
This commit is contained in:
parent
39dd293fc1
commit
95c9b7ee31
@ -96,6 +96,7 @@ export class Flights {
|
||||
public static readonly AutoscaleTest = "autoscaletest";
|
||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||
public static readonly Phoenix = "phoenix";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
@ -337,6 +338,13 @@ export enum ConflictOperationType {
|
||||
Delete = "delete",
|
||||
}
|
||||
|
||||
export enum ConnectionStatusType {
|
||||
Connecting = "Connecting",
|
||||
Allocating = "Allocating",
|
||||
Connected = "Connected",
|
||||
Failed = "Connection Failed",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ConnectionStatusType } from "../Common/Constants";
|
||||
|
||||
export interface DatabaseAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -496,3 +498,8 @@ export interface MemoryUsageInfo {
|
||||
freeKB: number;
|
||||
totalKB: number;
|
||||
}
|
||||
|
||||
export interface ContainerConnectionInfo {
|
||||
status: ConnectionStatusType;
|
||||
//need to add ram and rom info
|
||||
}
|
||||
|
@ -181,8 +181,7 @@ export const Dialog: FC = () => {
|
||||
text: secondaryButtonText,
|
||||
onClick: onSecondaryButtonClick,
|
||||
}
|
||||
: {};
|
||||
|
||||
: undefined;
|
||||
return visible ? (
|
||||
<FluentDialog {...dialogProps}>
|
||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||
|
@ -34,6 +34,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
@ -101,6 +102,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
@ -4,6 +4,7 @@ import _ from "underscore";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import { ConnectionStatusType } from "../Common/Constants";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||
@ -16,6 +17,7 @@ import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
import { useTabs } from "../hooks/useTabs";
|
||||
import { IGalleryItem } from "../Juno/JunoClient";
|
||||
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
@ -87,12 +89,13 @@ export default class Explorer {
|
||||
};
|
||||
|
||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||
|
||||
private phoenixClient: PhoenixClient;
|
||||
constructor() {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
this._isInitializingNotebooks = false;
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
useNotebook.subscribe(
|
||||
() => this.refreshCommandBarButtons(),
|
||||
(state) => state.isNotebooksEnabledForAccount
|
||||
@ -343,7 +346,30 @@ export default class Explorer {
|
||||
return;
|
||||
}
|
||||
this._isInitializingNotebooks = true;
|
||||
if (userContext.features.phoenix) {
|
||||
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Allocating,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
const provisionData = {
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
resourceId: userContext.databaseAccount.id,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
aadToken: userContext.authorizationToken,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
};
|
||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||
if (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,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.ensureNotebookWorkspaceRunning();
|
||||
const connectionInfo = await listConnectionInfo(
|
||||
userContext.subscriptionId,
|
||||
@ -356,6 +382,7 @@ export default class Explorer {
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||
});
|
||||
}
|
||||
|
||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
||||
|
||||
@ -364,7 +391,7 @@ export default class Explorer {
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public resetNotebookWorkspace() {
|
||||
public resetNotebookWorkspace(): void {
|
||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||
handleError(
|
||||
"Attempt to reset notebook workspace, but notebook is not enabled",
|
||||
@ -389,7 +416,6 @@ export default class Explorer {
|
||||
if (!databaseAccount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const { value: workspaces } = await listByDatabaseAccount(
|
||||
userContext.subscriptionId,
|
||||
@ -906,7 +932,7 @@ export default class Explorer {
|
||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||
}
|
||||
|
||||
public openNotebookTerminal(kind: ViewModels.TerminalKind) {
|
||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
let title: string;
|
||||
|
||||
switch (kind) {
|
||||
@ -1026,7 +1052,10 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async handleOpenFileAction(path: string): Promise<void> {
|
||||
if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||
) {
|
||||
this._openSetupNotebooksPaneForQuickstart();
|
||||
}
|
||||
|
||||
@ -1072,10 +1101,13 @@ export default class Explorer {
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
const isNotebookEnabled: boolean =
|
||||
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(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||
|
||||
|
@ -54,6 +54,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
||||
|
||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
}
|
||||
|
@ -80,8 +80,9 @@ export function createStaticCommandBarButtons(
|
||||
}
|
||||
|
||||
notebookButtons.push(createOpenTerminalButton(container));
|
||||
|
||||
if (userContext.features.phoenix === false) {
|
||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||
}
|
||||
if (
|
||||
(userContext.apiType === "Mongo" &&
|
||||
useNotebook.getState().isShellEnabled &&
|
||||
|
@ -13,6 +13,7 @@ import { StyleConstants } from "../../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
|
||||
/**
|
||||
@ -201,3 +202,10 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
onRender: () => <MemoryTracker />,
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus />,
|
||||
};
|
||||
};
|
||||
|
79
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less
Normal file
79
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.less
Normal file
@ -0,0 +1,79 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.connectionStatusContainer {
|
||||
cursor: default;
|
||||
align-items: center;
|
||||
margin: 0 9px;
|
||||
border: 1px;
|
||||
min-height: 44px;
|
||||
|
||||
> span {
|
||||
padding-right: 12px;
|
||||
font-size: 13px;
|
||||
font-family: @DataExplorerFont;
|
||||
color: @DefaultFontColor;
|
||||
}
|
||||
}
|
||||
.connectionStatusFailed{
|
||||
color: #bd1919;
|
||||
}
|
||||
.ring-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ringringGreen {
|
||||
border: 3px solid green;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringYellow{
|
||||
border: 3px solid #ffbf00;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringRed{
|
||||
border: 3px solid #bd1919;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
@keyframes pulsate {
|
||||
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
|
||||
15% {opacity: 0.8;}
|
||||
25% {opacity: 0.6;}
|
||||
45% {opacity: 0.4;}
|
||||
70% {opacity: 0.3;}
|
||||
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
|
||||
}
|
||||
.locationGreenDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: green;
|
||||
}
|
||||
.locationYellowDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #ffbf00;
|
||||
}
|
||||
.locationRedDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #bd1919;
|
||||
}
|
77
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
Normal file
77
src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { Icon, ProgressIndicator, Spinner, SpinnerSize, Stack, TooltipHost } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
|
||||
export const ConnectionStatus: React.FC = (): JSX.Element => {
|
||||
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("locationYellowDot");
|
||||
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
||||
const toolTipContent = "Hosted runtime status.";
|
||||
React.useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
if (isActive) {
|
||||
intervalId = setInterval(() => {
|
||||
const secondCounter = counter % 60;
|
||||
const minuteCounter = Math.floor(counter / 60);
|
||||
const computedSecond: string = String(secondCounter).length === 1 ? `0${secondCounter}` : `${secondCounter}`;
|
||||
const computedMinute: string = String(minuteCounter).length === 1 ? `0${minuteCounter}` : `${minuteCounter}`;
|
||||
|
||||
setSecond(computedSecond);
|
||||
setMinute(computedMinute);
|
||||
|
||||
setCounter((counter) => counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
return () => clearInterval(intervalId);
|
||||
}, [isActive, counter]);
|
||||
|
||||
const stopTimer = () => {
|
||||
setIsActive(false);
|
||||
setCounter(0);
|
||||
setSecond("00");
|
||||
setMinute("00");
|
||||
};
|
||||
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
if (!connectionInfo) {
|
||||
return (
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<span>Connecting</span>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Allocating && isActive === false) {
|
||||
setIsActive(true);
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("locationGreenDot");
|
||||
setStatusColorAnimation("ringringGreen");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("locationRedDot");
|
||||
setStatusColorAnimation("ringringRed");
|
||||
}
|
||||
return (
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<div className="ring-container">
|
||||
<div className={statusColorAnimation}></div>
|
||||
<Icon iconName="LocationDot" className={statusColor} />
|
||||
</div>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Allocating && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
);
|
||||
};
|
@ -109,7 +109,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
|
||||
const q = params.toString();
|
||||
const suffix = q !== "" ? `?${q}` : "";
|
||||
|
||||
const url = (serverConfig.endpoint || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
||||
const url = (serverConfig.endpoint.slice(0, -1) || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
||||
|
||||
return url.replace(/^http(s)?/, "ws$1");
|
||||
};
|
||||
|
@ -56,7 +56,7 @@ export class NotebookContainerClient {
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
try {
|
||||
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: authToken,
|
||||
|
@ -36,7 +36,10 @@ export class NotebookContentClient {
|
||||
*
|
||||
* @param parent parent folder
|
||||
*/
|
||||
public createNewNotebookFile(parent: NotebookContentItem, isGithubTree?: boolean): Promise<NotebookContentItem> {
|
||||
public async createNewNotebookFile(
|
||||
parent: NotebookContentItem,
|
||||
isGithubTree?: boolean
|
||||
): Promise<NotebookContentItem> {
|
||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||
throw new Error(`Parent must be a directory: ${parent}`);
|
||||
}
|
||||
@ -99,7 +102,6 @@ export class NotebookContentClient {
|
||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||
throw new Error(`Parent must be a directory: ${parent}`);
|
||||
}
|
||||
|
||||
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
||||
if (await this.checkIfFilepathExists(filepath)) {
|
||||
throw new Error(`File already exists: ${filepath}`);
|
||||
|
@ -28,6 +28,8 @@ interface NotebookState {
|
||||
myNotebooksContentRoot: NotebookContentItem;
|
||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
galleryContentRoot: NotebookContentItem;
|
||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
||||
NotebookFolderName: string;
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||
@ -43,6 +45,7 @@ interface NotebookState {
|
||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
||||
}
|
||||
|
||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
@ -65,6 +68,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
myNotebooksContentRoot: undefined,
|
||||
gitHubNotebooksContentRoot: undefined,
|
||||
galleryContentRoot: undefined,
|
||||
connectionInfo: undefined,
|
||||
NotebookFolderName: userContext.features.phoenix ? "My Notebooks Scratch" : "My Notebooks",
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||
@ -169,7 +174,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
},
|
||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||
const myNotebooksContentRoot = {
|
||||
name: "My Notebooks",
|
||||
name: get().NotebookFolderName,
|
||||
path: get().notebookBasePath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
@ -178,13 +183,11 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
path: "Gallery",
|
||||
type: NotebookContentItemType.File,
|
||||
};
|
||||
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||
? {
|
||||
const gitHubNotebooksContentRoot = {
|
||||
name: "GitHub repos",
|
||||
path: "PsuedoDir",
|
||||
type: NotebookContentItemType.Directory,
|
||||
}
|
||||
: undefined;
|
||||
};
|
||||
set({
|
||||
myNotebooksContentRoot,
|
||||
galleryContentRoot,
|
||||
@ -246,4 +249,5 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
set({ gitHubNotebooksContentRoot });
|
||||
}
|
||||
},
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
}));
|
||||
|
@ -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,6 +76,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
selectedLocation.owner,
|
||||
selectedLocation.repo
|
||||
)} - ${selectedLocation.branch}`;
|
||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||
destination = "My Notebooks Scratch";
|
||||
}
|
||||
|
||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
import React, { FormEvent, FunctionComponent } from "react";
|
||||
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||
|
||||
interface Location {
|
||||
@ -46,11 +47,10 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
||||
|
||||
const getDropDownOptions = (): IDropdownOption[] => {
|
||||
const options: IDropdownOption[] = [];
|
||||
|
||||
options.push({
|
||||
key: "MyNotebooks-Item",
|
||||
text: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
title: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
text: useNotebook.getState().NotebookFolderName,
|
||||
title: useNotebook.getState().NotebookFolderName,
|
||||
data: {
|
||||
type: "MyNotebooks",
|
||||
} as Location,
|
||||
|
@ -23,6 +23,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
@ -13,6 +13,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
@ -131,14 +131,14 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
if (myNotebooksContentRoot) {
|
||||
notebooksTree.children.push(buildMyNotebooksTree());
|
||||
}
|
||||
|
||||
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
// collapse all other notebook nodes
|
||||
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
||||
notebooksTree.children.push(buildGitHubNotebooksTree());
|
||||
notebooksTree.children.push(buildGitHubNotebooksTree(true));
|
||||
} else if (container.notebookManager && !container.notebookManager.gitHubOAuthService.isLoggedIn()) {
|
||||
notebooksTree.children.push(buildGitHubNotebooksTree(false));
|
||||
}
|
||||
}
|
||||
|
||||
return notebooksTree;
|
||||
};
|
||||
|
||||
@ -178,7 +178,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
return myNotebooksTree;
|
||||
};
|
||||
|
||||
const buildGitHubNotebooksTree = (): TreeNode => {
|
||||
const buildGitHubNotebooksTree = (isConnected: boolean): TreeNode => {
|
||||
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||
gitHubNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
@ -190,8 +190,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
gitHubNotebooksTree.contextMenu = [
|
||||
const manageGitContextMenu: TreeNodeMenuItem[] = [
|
||||
{
|
||||
label: "Manage GitHub settings",
|
||||
onClick: () =>
|
||||
@ -216,7 +215,23 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const connectGitContextMenu: TreeNodeMenuItem[] = [
|
||||
{
|
||||
label: "Connect to GitHub",
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Connect to GitHub",
|
||||
<GitHubReposPanel
|
||||
explorer={container}
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={container.notebookManager.junoClient}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
gitHubNotebooksTree.contextMenu = isConnected ? manageGitContextMenu : connectGitContextMenu;
|
||||
gitHubNotebooksTree.isExpanded = true;
|
||||
gitHubNotebooksTree.isAlphaSorted = true;
|
||||
|
||||
|
@ -45,6 +45,7 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
||||
|
||||
export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||
public static readonly MyNotebooksScratchTitle = "My Notebooks Scratch";
|
||||
public static readonly GitHubReposTitle = "GitHub repos";
|
||||
|
||||
private static readonly DataTitle = "DATA";
|
||||
@ -130,9 +131,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
path: "Gallery",
|
||||
type: NotebookContentItemType.File,
|
||||
};
|
||||
|
||||
this.myNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
name: useNotebook.getState().NotebookFolderName,
|
||||
path: useNotebook.getState().notebookBasePath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
@ -146,16 +146,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
this.gitHubNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.GitHubReposTitle,
|
||||
path: ResourceTreeAdapter.PseudoDirPath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
} else {
|
||||
this.gitHubNotebooksContentRoot = undefined;
|
||||
}
|
||||
|
||||
return Promise.all(refreshTasks);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less";
|
||||
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
|
||||
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
|
||||
import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
|
70
src/Phoenix/PhoenixClient.ts
Normal file
70
src/Phoenix/PhoenixClient.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import * as DataModels 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;
|
||||
resourceId: string;
|
||||
dbAccountName: string;
|
||||
aadToken: string;
|
||||
resourceGroup: string;
|
||||
subscriptionId: string;
|
||||
}
|
||||
export class PhoenixClient {
|
||||
public async containerConnectionInfo(
|
||||
provisionData: IProvosionData
|
||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
||||
method: "POST",
|
||||
headers: PhoenixClient.getHeaders(),
|
||||
body: JSON.stringify(provisionData),
|
||||
});
|
||||
let data: IPhoenixConnectionInfoResult;
|
||||
if (response.status === HttpStatusCodes.OK) {
|
||||
data = await response.json();
|
||||
} else {
|
||||
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
public static getPhoenixEndpoint(): string {
|
||||
const phoenixEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
||||
console.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return phoenixEndpoint;
|
||||
}
|
||||
|
||||
public getPhoenixContainerPoolingEndPoint(): string {
|
||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/containerpooling`;
|
||||
}
|
||||
private static getHeaders(): HeadersInit {
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
return {
|
||||
[authorizationHeader.header]: authorizationHeader.token,
|
||||
[HttpHeaders.contentType]: "application/json",
|
||||
};
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ export type Features = {
|
||||
autoscaleDefault: boolean;
|
||||
partitionKeyDefault: boolean;
|
||||
partitionKeyDefault2: boolean;
|
||||
phoenix: boolean;
|
||||
readonly enableSDKoperations: boolean;
|
||||
readonly enableSpark: boolean;
|
||||
readonly enableTtl: boolean;
|
||||
@ -76,5 +77,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||
partitionKeyDefault: "true" === get("partitionkeytest"),
|
||||
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
||||
notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
|
||||
phoenix: "true" === get("phoenix"),
|
||||
};
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
SortBy,
|
||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor";
|
||||
@ -223,11 +224,13 @@ export function downloadItem(
|
||||
|
||||
const name = data.name;
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
"Download to My Notebooks",
|
||||
`Download to ${useNotebook.getState().NotebookFolderName}`,
|
||||
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
||||
"Download",
|
||||
async () => {
|
||||
const clearInProgressMessage = logConsoleProgress(`Downloading ${name} to My Notebooks`);
|
||||
const clearInProgressMessage = logConsoleProgress(
|
||||
`Downloading ${name} to ${useNotebook.getState().NotebookFolderName}`
|
||||
);
|
||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||
notebookId: data.id,
|
||||
downloadCount: data.downloads,
|
||||
|
@ -339,6 +339,9 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
||||
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
|
||||
userContext.features.partitionKeyDefault2 = true;
|
||||
}
|
||||
if (inputs.flights.indexOf(Flights.Phoenix) !== -1) {
|
||||
userContext.features.phoenix = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user