More Spark UI Cleanup (#89)

Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
This commit is contained in:
Steve Faulkner 2020-07-15 16:59:04 -05:00 committed by GitHub
parent 99c6a7ebcc
commit 444e25c086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 0 additions and 1511 deletions

View File

@ -138,7 +138,6 @@ src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ClusterLibraryPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
@ -146,7 +145,6 @@ src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
@ -332,10 +330,6 @@ src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGrid.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGridAdapter.tsx
src/Explorer/Controls/LibraryManagement/LibraryManage.tsx
src/Explorer/Controls/LibraryManagement/LibraryManageComponentAdapter.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx

View File

@ -148,8 +148,6 @@ export interface Explorer {
uploadFilePane: UploadFilePane;
stringInputPane: StringInputPane;
setupNotebooksPane: SetupNotebooksPane;
libraryManagePane: ContextualPane;
clusterLibraryPane: ContextualPane;
gitHubReposPane: ContextualPane;
publishNotebookPaneAdapter: ReactAdapter;
@ -230,11 +228,8 @@ export interface Explorer {
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
openNotebookViewer: (notebookUrl: string) => void;
notebookWorkspaceManager: NotebookWorkspaceManager;
sparkClusterManager: SparkClusterManager;
mostRecentActivity: MostRecentActivity;
initNotebooks: (databaseAccount: DataModels.DatabaseAccount) => Promise<void>;
deleteCluster(): void;
openSparkMasterTab(): Promise<void>;
handleOpenFileAction(path: string): Promise<void>;
// Notebook operations
@ -275,26 +270,6 @@ export interface KernelConnectionMetadata {
notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo;
}
export interface SparkClusterManager {
getClustersAsync(cosmosAccountResourceId: string): Promise<DataModels.SparkCluster[]>;
getClusterAsync(cosmosAccountResourceId: string, clusterId: string): Promise<DataModels.SparkCluster>;
createClusterAsync(cosmosAccountResourceId: string, cluster: Partial<DataModels.SparkCluster>): Promise<void>;
updateClusterAsync(
cosmosAccountResourceId: string,
clusterId: string,
sparkCluster: DataModels.SparkCluster
): Promise<DataModels.SparkCluster>;
deleteClusterAsync(cosmosAccountResourceId: string, clusterId: string): Promise<void>;
getClusterConnectionInfoAsync(
cosmosAccountResourceId: string,
clusterId: string
): Promise<DataModels.SparkClusterConnectionInfo>;
getLibrariesAsync(cosmosdbResourceId: string): Promise<Library[]>;
getLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<Library>;
addLibraryAsync(cosmosdbResourceId: string, libraryName: string, library: Library): Promise<void>;
deleteLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<void>;
}
export interface ArcadiaResourceManager {
getWorkspacesAsync(arcadiaResourceId: string): Promise<DataModels.ArcadiaWorkspace[]>;
getWorkspaceAsync(arcadiaResourceId: string, workspaceId: string): Promise<DataModels.ArcadiaWorkspace>;

View File

@ -123,12 +123,4 @@ describe("Component Registerer", () => {
it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true);
});
it("should register library-manage-pane component", () => {
expect(ko.components.isRegistered("library-manage-pane")).toBe(true);
});
it("should register cluster-library-pane component", () => {
expect(ko.components.isRegistered("cluster-library-pane")).toBe(true);
});
});

View File

@ -78,6 +78,4 @@ ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPa
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@ -1,40 +0,0 @@
import * as React from "react";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
export interface ClusterLibraryItem extends Library {
installed: boolean;
}
export interface ClusterLibraryGridProps {
libraryItems: ClusterLibraryItem[];
onInstalledChanged: (libraryName: string, installed: boolean) => void;
}
export function ClusterLibraryGrid(props: ClusterLibraryGridProps): JSX.Element {
const onInstalledChanged = (e: React.FormEvent<HTMLInputElement>) => {
const target = e.target;
const libraryName = (target as any).dataset.name;
const checked = (target as any).checked;
return props.onInstalledChanged(libraryName, checked);
};
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 150
},
{
key: "installed",
name: "Installed",
minWidth: 100,
onRender: (item: ClusterLibraryItem) => {
return <input type="checkbox" checked={item.installed} onChange={onInstalledChanged} data-name={item.name} />;
}
}
];
return <DetailsList columns={columns} items={props.libraryItems} selectionMode={SelectionMode.none} />;
}

View File

@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ClusterLibraryGrid, ClusterLibraryGridProps } from "./ClusterLibraryGrid";
export class ClusterLibraryGridAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterLibraryGridProps>;
public renderComponent(): JSX.Element {
return <ClusterLibraryGrid {...this.parameters()} />;
}
}

View File

@ -1,156 +0,0 @@
import * as React from "react";
import DeleteIcon from "../../../../images/delete.svg";
import { Button } from "office-ui-fabric-react/lib/Button";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
import { Label } from "office-ui-fabric-react/lib/Label";
import { SparkLibrary } from "../../../Common/Constants";
import { TextField } from "office-ui-fabric-react/lib/TextField";
export interface LibraryManageComponentProps {
addProps: {
nameProps: LibraryAddNameTextFieldProps;
urlProps: LibraryAddUrlTextFieldProps;
buttonProps: LibraryAddButtonProps;
};
gridProps: LibraryManageGridProps;
}
export function LibraryManageComponent(props: LibraryManageComponentProps): JSX.Element {
const {
addProps: { nameProps, urlProps, buttonProps },
gridProps
} = props;
return (
<div>
<div className="library-add-container">
<LibraryAddNameTextField {...nameProps} />
<LibraryAddUrlTextField {...urlProps} />
<LibraryAddButton {...buttonProps} />
</div>
<div className="library-grid-container">
<Label>All Libraries</Label>
<LibraryManageGrid {...gridProps} />
</div>
</div>
);
}
export interface LibraryManageGridProps {
items: Library[];
onLibraryDeleteClick: (libraryName: string) => void;
}
function LibraryManageGrid(props: LibraryManageGridProps): JSX.Element {
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 200
},
{
key: "delete",
name: "Delete",
minWidth: 60,
onRender: (item: Library) => {
const onDelete = () => {
props.onLibraryDeleteClick(item.name);
};
return (
<span className="library-delete">
<img src={DeleteIcon} alt="Delete" onClick={onDelete} />
</span>
);
}
}
];
return <DetailsList columns={columns} items={props.items} selectionMode={SelectionMode.none} />;
}
export interface LibraryAddButtonProps {
disabled: boolean;
onLibraryAddClick: (event: React.FormEvent<any>) => void;
}
function LibraryAddButton(props: LibraryAddButtonProps): JSX.Element {
return (
<Button text="Add" className="library-add-button" onClick={props.onLibraryAddClick} disabled={props.disabled} />
);
}
export interface LibraryAddUrlTextFieldProps {
libraryAddress: string;
onLibraryAddressChange: (libraryAddress: string) => void;
onLibraryAddressValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddUrlTextField(props: LibraryAddUrlTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryAddress: string) => {
props.onLibraryAddressChange(libraryAddress);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
const isValidUrl = libraryUrlRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid https uri";
};
return (
<TextField
value={props.libraryAddress}
label="Url"
type="url"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryAddressValidated}
placeholder="https://myrepo/myjar.jar"
autoComplete="off"
/>
);
}
export interface LibraryAddNameTextFieldProps {
libraryName: string;
onLibraryNameChange: (libraryName: string) => void;
onLibraryNameValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddNameTextField(props: LibraryAddNameTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryName: string) => {
props.onLibraryNameChange(libraryName);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const length = text.length;
if (length < SparkLibrary.nameMinLength || length > SparkLibrary.nameMaxLength) {
return "Library name length need to be between 3 and 63.";
}
const nameRegex = /^[a-z0-9][-a-z0-9]*[a-z0-9]$/gi;
const isValidUrl = nameRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid name. Letters, numbers and - are allowed";
};
return (
<TextField
value={props.libraryName}
label="Name"
type="text"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryNameValidated}
placeholder="myjar"
autoComplete="off"
/>
);
}

View File

@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { LibraryManageComponent, LibraryManageComponentProps } from "./LibraryManage";
export class LibraryManageComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<LibraryManageComponentProps>;
public renderComponent(): JSX.Element {
return <LibraryManageComponent {...this.parameters()} />;
}
}

View File

@ -1,17 +0,0 @@
@import "../../../../less/Common/Constants";
.labelWithRedAsterisk {
line-height: 18px;
font-size: @DefaultFontSize;
font-family: @DataExplorerFont;
color: @DefaultFontColor;
}
.labelWithRedAsterisk::before {
content: "* ";
color: @SelectionHigh;
}
.clusterSettingsDropdown {
margin-bottom: 10px;
}

View File

@ -1,159 +0,0 @@
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { Slider, ISliderProps } from "office-ui-fabric-react/lib/Slider";
import { Stack, IStackItemStyles, IStackStyles } from "office-ui-fabric-react/lib/Stack";
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
import { Spark } from "../../../Common/Constants";
import { SparkCluster } from "../../../Contracts/DataModels";
export interface ClusterSettingsComponentProps {
cluster: SparkCluster;
onClusterSettingsChanged: (cluster: SparkCluster) => void;
}
export class ClusterSettingsComponent extends React.Component<ClusterSettingsComponentProps, {}> {
constructor(props: ClusterSettingsComponentProps) {
super(props);
}
public render(): JSX.Element {
return (
<>
{this.getMasterSizeDropdown()}
{this.getWorkerSizeDropdown()}
{this.getWorkerCountSliderInput()}
</>
);
}
private getMasterSizeDropdown(): JSX.Element {
const driverSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.driverSize;
const masterSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const masterSizeDropdownProps: IDropdownProps = {
label: "Master Size",
options: masterSizeOptions,
defaultSelectedKey: driverSize,
onChange: this._onDriverSizeChange,
styles: {
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...masterSizeDropdownProps} />;
}
private getWorkerSizeDropdown(): JSX.Element {
const workerSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.workerSize;
const workerSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const workerSizeDropdownProps: IDropdownProps = {
label: "Worker Size",
options: workerSizeOptions,
defaultSelectedKey: workerSize,
onChange: this._onWorkerSizeChange,
styles: {
label: "labelWithRedAsterisk",
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...workerSizeDropdownProps} />;
}
private getWorkerCountSliderInput(): JSX.Element {
const workerCount: number =
(this.props.cluster &&
this.props.cluster.properties &&
this.props.cluster.properties.workerInstanceCount !== undefined &&
this.props.cluster.properties.workerInstanceCount) ||
0;
const stackStyle: IStackStyles = {
root: {
paddingTop: 5
}
};
const sliderItemStyle: IStackItemStyles = {
root: {
width: "100%",
paddingRight: 20
}
};
const workerCountSliderProps: ISliderProps = {
min: 0,
max: Spark.MaxWorkerCount,
step: 1,
value: workerCount,
showValue: false,
onChange: this._onWorkerCountChange,
styles: {
root: {
width: "100%",
paddingRight: 20
}
}
};
const workerCountTextFieldProps: ITextFieldProps = {
value: workerCount.toString(),
styles: {
fieldGroup: {
width: 45,
height: 25
},
field: {
textAlign: "center"
}
},
onChange: this._onWorkerCountTextFieldChange
};
return (
<Stack styles={stackStyle}>
<span className="labelWithRedAsterisk">Worker Nodes</span>
<Stack horizontal verticalAlign="center">
<Slider {...workerCountSliderProps} />
<TextField {...workerCountTextFieldProps} />
</Stack>
</Stack>
);
}
private _onDriverSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.driverSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountChange = (count: number) => {
count = Math.min(count, Spark.MaxWorkerCount);
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerInstanceCount = count;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountTextFieldChange = (_event: React.FormEvent, newValue: string) => {
const count = parseInt(newValue);
if (!isNaN(count)) {
this._onWorkerCountChange(count);
}
};
}

View File

@ -1,11 +0,0 @@
import * as React from "react";
import { ClusterSettingsComponent, ClusterSettingsComponentProps } from "./ClusterSettingsComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class ClusterSettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterSettingsComponentProps>;
public renderComponent(): JSX.Element {
return <ClusterSettingsComponent {...this.parameters()} />;
}
}

View File

@ -24,7 +24,6 @@ import NewVertexPane from "./Panes/NewVertexPane";
import NotebookV2Tab from "./Tabs/NotebookV2Tab";
import Q from "q";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import SparkMasterTab from "./Tabs/SparkMasterTab";
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import TerminalTab from "./Tabs/TerminalTab";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@ -36,7 +35,6 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraApi } from "../Api/Apis";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { ClusterLibraryPane } from "./Panes/ClusterLibraryPane";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { config } from "../Config";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
@ -52,7 +50,6 @@ import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { handleOpenAction } from "./OpenActions";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import { IGalleryItem } from "../Juno/JunoClient";
import { LibraryManagePane } from "./Panes/LibraryManagePane";
import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger";
import { MessageHandler } from "../Common/MessageHandler";
@ -72,7 +69,6 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane";
@ -190,8 +186,6 @@ export default class Explorer implements ViewModels.Explorer {
public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter;
@ -221,7 +215,6 @@ export default class Explorer implements ViewModels.Explorer {
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public notebookWorkspaceManager: ViewModels.NotebookWorkspaceManager;
public sparkClusterManager: ViewModels.SparkClusterManager;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public isSparkEnabled: ko.Observable<boolean>;
public isSparkEnabledForAccount: ko.Observable<boolean>;
@ -313,7 +306,6 @@ export default class Explorer implements ViewModels.Explorer {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
this.sparkClusterManager = new SparkClusterManager(this.armEndpoint());
this.arcadiaWorkspaces = ko.observableArray();
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
@ -750,22 +742,6 @@ export default class Explorer implements ViewModels.Explorer {
container: this
});
this.libraryManagePane = new LibraryManagePane({
documentClientUtility: this.documentClientUtility,
id: "libraryManagePane",
visible: ko.observable<boolean>(false),
container: this
});
this.clusterLibraryPane = new ClusterLibraryPane({
documentClientUtility: this.documentClientUtility,
id: "clusterLibraryPane",
visible: ko.observable<boolean>(false),
container: this
});
this.tabsManager = new TabsManager();
this._panes = [
@ -1607,70 +1583,6 @@ export default class Explorer implements ViewModels.Explorer {
window.open(Constants.Urls.feedbackEmail, "_self");
};
public async initSparkConnectionInfo(databaseAccount: DataModels.DatabaseAccount) {
if (!databaseAccount) {
throw new Error("No database account specified");
}
if (this._isInitializingSparkConnectionInfo) {
return;
}
this._isInitializingSparkConnectionInfo = true;
let connectionInfo: DataModels.SparkClusterConnectionInfo;
try {
connectionInfo = await this.sparkClusterManager.getClusterConnectionInfoAsync(databaseAccount.id, "default");
} catch (error) {
this._isInitializingSparkConnectionInfo = false;
Logger.logError(error, "initSparkConnectionInfo/getClusterConnectionInfoAsync");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to get cluster connection info: ${JSON.stringify(error)}`
);
throw error;
} finally {
// Overwrite with feature flags
if (this.isFeatureEnabled(Constants.Features.livyEndpoint)) {
connectionInfo = {
userName: undefined,
password: undefined,
endpoints: [
{
kind: DataModels.SparkClusterEndpointKind.Livy,
endpoint: this.features()[Constants.Features.livyEndpoint]
}
]
};
}
}
this.sparkClusterConnectionInfo(connectionInfo);
this.sparkClusterConnectionInfo.valueHasMutated();
this._isInitializingSparkConnectionInfo = false;
}
public deleteCluster() {
if (!this.isSparkEnabled() || !this.sparkClusterManager) {
return;
}
const deleteClusterDialogProps: DialogProps = {
isModal: true,
visible: true,
title: "Delete Cluster",
subText:
"This will delete the default cluster associated with this account and interrupt any scheduled jobs. Proceed anyway?",
primaryButtonText: "OK",
secondaryButtonText: "Cancel",
onPrimaryButtonClick: async () => {
this._closeModalDialog();
await this._deleteCluster();
},
onSecondaryButtonClick: this._closeModalDialog
};
this._dialogProps(deleteClusterDialogProps);
}
public async getArcadiaToken(): Promise<string> {
return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => {
MessageHandler.sendCachedDataMessage<string>(MessageTypes.GetArcadiaToken, undefined /** params **/).then(
@ -1821,53 +1733,6 @@ export default class Explorer implements ViewModels.Explorer {
}
}
private _deleteCluster = async () => {
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSparkCluster, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
"Deleting the default spark cluster associated with this account"
);
try {
await this.sparkClusterManager.deleteClusterAsync(this.databaseAccount().id, "default");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
"Successfully deleted the default spark cluster associated with this account"
);
TelemetryProcessor.traceSuccess(
Action.DeleteSparkCluster,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
},
startKey
);
} catch (error) {
const errorMessage = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to delete default spark cluster: ${errorMessage}`
);
TelemetryProcessor.traceFailure(
Action.DeleteSparkCluster,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error,
errorMessage
},
startKey
);
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
}
};
private _resetNotebookWorkspace = async () => {
this._closeModalDialog();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Resetting notebook workspace");
@ -2835,42 +2700,6 @@ export default class Explorer implements ViewModels.Explorer {
return false;
}
};
public async openSparkMasterTab() {
if (!this.sparkClusterConnectionInfo()) {
await this.initSparkConnectionInfo(this.databaseAccount());
}
const sparkMasterTabs: SparkMasterTab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.SparkMasterTab
) as SparkMasterTab[];
let sparkMasterTab: SparkMasterTab = sparkMasterTabs && sparkMasterTabs[0];
if (sparkMasterTab) {
this.tabsManager.activateTab(sparkMasterTab);
} else {
sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this
});
this.tabsManager.activateNewTab(sparkMasterTab);
}
}
private refreshNotebookList = async (): Promise<void> => {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
return;

View File

@ -108,11 +108,8 @@ export class ExplorerStub implements ViewModels.Explorer {
public isSparkEnabledForAccount: ko.Observable<boolean>;
public arcadiaToken: ko.Observable<string>;
public notebookWorkspaceManager: ViewModels.NotebookWorkspaceManager;
public sparkClusterManager: ViewModels.SparkClusterManager;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter;
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
@ -405,14 +402,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented");
}
public deleteCluster(): void {
throw new Error("Not implemented");
}
public async openSparkMasterTab(): Promise<void> {
throw new Error("Not implemented");
}
public createNotebookContentItemFile(name: string, filepath: string): NotebookContentItem {
throw new Error("Not implemented");
}

View File

@ -1,59 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="clusterLibraryPane">
<!-- Cluster Library -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Cluster Library header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Cluster Library header - End -->
<!-- Cluster Library errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Cluster Library errors - End -->
<!-- Cluster Library inputs - Start -->
<div class="paneMainContent"><div data-bind="react: clusterLibraryGridAdapter"></div></div>
<!-- Cluster Library inputs - End -->
<div class="paneFooter">
<div class="leftpanel-okbut"><input type="submit" value="Save" class="btncreatecoll1" /></div>
</div>
</form>
</div>
<!-- Cluster Library - End -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@ -1,237 +0,0 @@
import _ from "underscore";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ClusterLibraryGridAdapter } from "../Controls/LibraryManagement/ClusterLibraryGridAdapter";
import { ClusterLibraryGridProps, ClusterLibraryItem } from "../Controls/LibraryManagement/ClusterLibraryGrid";
import { Library, SparkCluster, SparkClusterLibrary } from "../../Contracts/DataModels";
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class ClusterLibraryPane extends ContextualPaneBase {
public clusterLibraryGridAdapter: ClusterLibraryGridAdapter;
private _clusterLibraryProps: ko.Observable<ClusterLibraryGridProps>;
private _originalCluster: SparkCluster;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Cluster Libraries");
this._clusterLibraryProps = ko.observable<ClusterLibraryGridProps>({
libraryItems: [],
onInstalledChanged: this._onInstalledChanged
});
this.clusterLibraryGridAdapter = new ClusterLibraryGridAdapter();
this.clusterLibraryGridAdapter.parameters = this._clusterLibraryProps;
this.resetData();
}
public open(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
Promise.all([this._getLibraries(resourceId), this._getDefaultCluster(resourceId)]).then(
result => {
const [libraries, cluster] = result;
this._originalCluster = cluster;
const libraryItems = this._mapClusterLibraries(cluster, libraries);
this._updateClusterLibraryGridStates({ libraryItems });
},
reason => {
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
super.open();
}
public submit(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
if (this._areLibrariesChanged()) {
const newLibraries = this._clusterLibraryProps()
.libraryItems.filter(lib => lib.installed)
.map(lib => ({ name: lib.name }));
this._updateClusterLibraries(resourceId, this._originalCluster, newLibraries).then(
() => {
this.isExecuting(false);
this.close();
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
} else {
this.isExecuting(false);
this.close();
}
}
private _updateClusterLibraryGridStates(states: Partial<ClusterLibraryGridProps>): void {
const merged = { ...this._clusterLibraryProps(), ...states };
this._clusterLibraryProps(merged);
this._clusterLibraryProps.valueHasMutated();
}
private _onInstalledChanged = (libraryName: string, installed: boolean): void => {
const items = this._clusterLibraryProps().libraryItems;
const library = _.find(items, item => item.name === libraryName);
library.installed = installed;
this._clusterLibraryProps.valueHasMutated();
};
private _areLibrariesChanged(): boolean {
const original = this._originalCluster.properties && this._originalCluster.properties.libraries;
const changed = this._clusterLibraryProps()
.libraryItems.filter(lib => lib.installed)
.map(lib => lib.name);
if (original.length !== changed.length) {
return true;
}
const newLibraries = new Set(changed);
for (let o of original) {
if (!newLibraries.has(o.name)) {
return false;
}
newLibraries.delete(o.name);
}
return newLibraries.size === 0;
}
private _mapClusterLibraries(cluster: SparkCluster, libraries: Library[]): ClusterLibraryItem[] {
const clusterLibraries = cluster && cluster.properties && cluster.properties.libraries;
const libraryItems = libraries.map(lib => ({
...lib,
installed: clusterLibraries.some(clusterLib => clusterLib.name === lib.name)
}));
return libraryItems;
}
private async _getLibraries(resourceId: string): Promise<Library[]> {
if (!resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching libraries...`
);
try {
return await this.container.sparkClusterManager.getLibrariesAsync(resourceId);
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch libraries. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _getDefaultCluster(resourceId: string, clusterId: string = "default"): Promise<SparkCluster> {
if (!resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Fetching cluster...`);
try {
const cluster = await this.container.sparkClusterManager.getClusterAsync(resourceId, clusterId);
return cluster;
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch cluster. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getCluster");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _updateClusterLibraries(
resourceId: string,
originalCluster: SparkCluster,
newLibrarys: SparkClusterLibrary[]
): Promise<void> {
if (!originalCluster || !resourceId) {
return Promise.reject("Invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("Cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.ClusterLibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
originalCluster,
newLibrarys
});
let newCluster = originalCluster;
newCluster.properties.libraries = newLibrarys;
const consoleId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating ${newCluster.name} libraries...`
);
try {
const cluster = await this.container.sparkClusterManager.updateClusterAsync(
resourceId,
originalCluster.name,
newCluster
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated ${newCluster.name} libraries.`
);
TelemetryProcessor.traceSuccess(Action.ClusterLibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
cluster
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to upload ${newCluster.name} libraries. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.ClusterLibraryManage, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
error: e
});
Logger.logError(e, "Explorer/_updateClusterLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(consoleId);
}
}
}

View File

@ -1,55 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="libraryManagePane">
<!-- Library Manage -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Library Manage header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Library Manage header - End -->
<!-- Library Manage errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Library Manage errors - End -->
<!-- Library Manage inputs - Start -->
<div class="paneMainContent"><div data-bind="react: libraryManageComponentAdapter"></div></div>
<!-- Library Manage inputs - End -->
</form>
</div>
<!-- Library Manage - End -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@ -1,372 +0,0 @@
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { LibraryManageComponentAdapter } from "../Controls/LibraryManagement/LibraryManageComponentAdapter";
import {
LibraryManageComponentProps,
LibraryAddNameTextFieldProps,
LibraryAddUrlTextFieldProps,
LibraryAddButtonProps,
LibraryManageGridProps
} from "../Controls/LibraryManagement/LibraryManage";
import { Library } from "../../Contracts/DataModels";
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class LibraryManagePane extends ContextualPaneBase {
public libraryManageComponentAdapter: LibraryManageComponentAdapter;
private _libraryManageProps: ko.Observable<LibraryManageComponentProps>;
private _libraryManageStates: { isNameValid: boolean; isUrlValid: boolean };
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Libraries");
this._libraryManageStates = {
isNameValid: true,
isUrlValid: true
};
this._libraryManageProps = ko.observable<LibraryManageComponentProps>({
addProps: {
nameProps: {
libraryName: "",
onLibraryNameChange: this._onLibraryNameChange,
onLibraryNameValidated: this._onLibraryNameValidated
},
urlProps: {
libraryAddress: "",
onLibraryAddressChange: this._onLibraryAddressChange,
onLibraryAddressValidated: this._onLibraryAddressValidated
},
buttonProps: {
disabled: false,
onLibraryAddClick: this._onLibraryAddClick
}
},
gridProps: {
items: [],
onLibraryDeleteClick: this._onLibraryDeleteClick
}
});
this.libraryManageComponentAdapter = new LibraryManageComponentAdapter();
this.libraryManageComponentAdapter.parameters = this._libraryManageProps;
this.resetData();
}
public open(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this._getLibraries(resourceId).then(
(libraries: Library[]) => {
this._updateLibraryManageComponentProps(null, null, null, {
items: libraries
});
},
reason => {
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
super.open();
}
public submit(): void {
// override default behavior because this is not a form
}
private _updateLibraryManageComponentProps(
newNameProps?: Partial<LibraryAddNameTextFieldProps>,
newUrlProps?: Partial<LibraryAddUrlTextFieldProps>,
newButtonProps?: Partial<LibraryAddButtonProps>,
newGridProps?: Partial<LibraryManageGridProps>
): void {
let {
addProps: { buttonProps, nameProps, urlProps },
gridProps
} = this._libraryManageProps();
if (newNameProps) {
nameProps = { ...nameProps, ...newNameProps };
}
if (newUrlProps) {
urlProps = { ...urlProps, ...newUrlProps };
}
if (newButtonProps) {
buttonProps = { ...buttonProps, ...newButtonProps };
}
if (newGridProps) {
gridProps = { ...gridProps, ...newGridProps };
}
this._libraryManageProps({
addProps: {
nameProps,
urlProps,
buttonProps
},
gridProps
});
this._libraryManageProps.valueHasMutated();
}
private _onLibraryNameChange = (libraryName: string): void => {
this._updateLibraryManageComponentProps({ libraryName });
};
private _onLibraryNameValidated = (errorMessage: string): void => {
this._libraryManageStates.isNameValid = !errorMessage;
this._validateAddButton();
};
private _onLibraryAddressChange = (libraryAddress: string): void => {
this._updateLibraryManageComponentProps(null, {
libraryAddress
});
if (!this._libraryManageProps().addProps.nameProps.libraryName) {
const parsedLibraryAddress = this._parseLibraryUrl(libraryAddress);
if (!parsedLibraryAddress) {
return;
}
let libraryName = this._sanitizeLibraryName(parsedLibraryAddress[2]);
this._updateLibraryManageComponentProps({ libraryName });
}
};
private _sanitizeLibraryName = (libraryName: string): string => {
const invalidCharRegex = /[^a-zA-Z0-9-]/gm;
return libraryName
.replace(invalidCharRegex, "-")
.substring(0, Math.min(Constants.SparkLibrary.nameMaxLength, libraryName.length));
};
private _onLibraryAddressValidated = (errorMessage: string): void => {
this._libraryManageStates.isUrlValid = !errorMessage;
this._validateAddButton();
};
private _validateAddButton = (): void => {
const isValid = this._libraryManageStates.isNameValid && this._libraryManageStates.isUrlValid;
const isUploadDisabled = this._libraryManageProps().addProps.buttonProps.disabled;
if (isValid === isUploadDisabled) {
this._updateLibraryManageComponentProps(null, null, {
disabled: !isUploadDisabled
});
}
};
private _onLibraryDeleteClick = (libraryName: string): void => {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
this._deleteLibrary(resourceId, libraryName).then(
() => {
this.isExecuting(false);
const items = this._libraryManageProps().gridProps.items.filter(lib => lib.name !== libraryName);
this._updateLibraryManageComponentProps(null, null, null, {
items
});
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
};
private _onLibraryAddClick = (): void => {
const libraryAddress = this._libraryManageProps().addProps.urlProps.libraryAddress;
if (!libraryAddress) {
this.formErrors("Library Url cannot be null");
return;
}
const libraryName = this._libraryManageProps().addProps.nameProps.libraryName || this._generateLibraryName();
if (!libraryName) {
this.formErrors("Library Name cannot be null");
return;
}
const parsedLibraryAddress = this._parseLibraryUrl(libraryAddress);
if (!parsedLibraryAddress) {
return;
}
const library: Library = {
name: libraryName,
properties: {
kind: "Jar",
source: {
kind: "HttpsUri",
libraryFileName: `${libraryName}.${parsedLibraryAddress[3]}`,
uri: libraryAddress
}
}
};
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
this._updateLibraryManageComponentProps(null, null, { disabled: true });
this._addLibrary(resourceId, library).then(
() => {
this.isExecuting(false);
this._updateLibraryManageComponentProps(
{
libraryName: ""
},
{
libraryAddress: ""
},
{
disabled: false
},
{
items: [...this._libraryManageProps().gridProps.items, library]
}
);
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
};
private _parseLibraryUrl = (url: string): RegExpExecArray => {
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
return libraryUrlRegex.exec(url);
};
private _generateLibraryName = (): string => {
return `library-${Math.random()
.toString(32)
.substring(2)}`;
};
private async _getLibraries(resourceId: string): Promise<Library[]> {
if (!resourceId) {
return Promise.reject("Invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("Cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching libraries...`
);
try {
const libraries = await this.container.sparkClusterManager.getLibrariesAsync(resourceId);
return libraries;
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch libraries. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _addLibrary(resourceId: string, library: Library): Promise<void> {
if (!library || !resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.LibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name
});
const libraryName = library.name;
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Uploading ${libraryName}...`
);
try {
await this.container.sparkClusterManager.addLibraryAsync(resourceId, libraryName, library);
TelemetryProcessor.traceSuccess(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to upload ${libraryName}. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name,
error: e
});
Logger.logError(e, "Explorer/_uploadLibrary");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _deleteLibrary(resourceId: string, libraryName: string): Promise<void> {
if (!libraryName || !resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.LibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "LibraryManagePane/_deleteLibrary",
libraryName
});
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting ${libraryName}...`
);
try {
await this.container.sparkClusterManager.deleteLibraryAsync(resourceId, libraryName);
TelemetryProcessor.traceSuccess(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to delete ${libraryName}. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName,
error: e
});
Logger.logError(e, "Explorer/_deleteLibrary");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
}

View File

@ -19,8 +19,6 @@ import BrowseQueriesPaneTemplate from "./BrowseQueriesPane.html";
import UploadFilePaneTemplate from "./UploadFilePane.html";
import StringInputPaneTemplate from "./StringInputPane.html";
import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html";
import LibraryManagePaneTemplate from "./LibraryManagePane.html";
import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
export class PaneComponent {
@ -218,24 +216,6 @@ export class SetupNotebooksPaneComponent {
}
}
export class LibraryManagePaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: LibraryManagePaneTemplate
};
}
}
export class ClusterLibraryPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: ClusterLibraryPaneTemplate
};
}
}
export class GitHubReposPaneComponent {
constructor() {
return {

View File

@ -32,7 +32,6 @@ import "./Explorer/Controls/TreeComponent/treeComponent.less";
import "./Explorer/Controls/Accordion/AccordionComponent.less";
import "./Explorer/SplashScreen/SplashScreenComponent.less";
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/Spark/ClusterSettingsComponent.less";
// Image Dependencies
import "../images/CosmosDB_rgb_ui_lighttheme.ico";

View File

@ -1,137 +0,0 @@
import * as ViewModels from "../Contracts/ViewModels";
import { ArmApiVersions } from "../Common/Constants";
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
import * as Logger from "../Common/Logger";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import {
SparkCluster,
SparkClusterConnectionInfo,
SparkClusterFeedResponse,
Library,
LibraryFeedResponse
} from "../Contracts/DataModels";
export class SparkClusterManager implements ViewModels.SparkClusterManager {
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
constructor(private armEndpoint: string) {
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this.armEndpoint);
}
public async getClustersAsync(cosmosdbResourceId: string): Promise<SparkCluster[]> {
const uri = `${cosmosdbResourceId}/clusters`;
try {
const response = (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as SparkClusterFeedResponse;
return response && response.value;
} catch (error) {
Logger.logError(error, "SparkClusterManager/getClustersAsync");
throw error;
}
}
public async getClusterAsync(cosmosdbResourceId: string, clusterId: string): Promise<SparkCluster> {
const uri = `${cosmosdbResourceId}/clusters/${clusterId}`;
try {
return (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as SparkCluster;
} catch (error) {
Logger.logError(error, "SparkClusterManager/getClusterAsync");
throw error;
}
}
public async createClusterAsync(cosmosdbResourceId: string, cluster: Partial<SparkCluster>): Promise<void> {
if (!cluster || !cluster.name) {
throw new Error("Invalid or incomplete spark cluster properties specified");
}
const uri = `${cosmosdbResourceId}/clusters/${cluster.name}`;
try {
await this.rpClient(uri).putAsync(uri, ArmApiVersions.documentDB, cluster);
} catch (error) {
Logger.logError(error, "SparkClusterManager/createClusterAsync");
throw error;
}
}
public async updateClusterAsync(
cosmosdbResourceId: string,
clusterId: string,
cluster: SparkCluster
): Promise<SparkCluster> {
const uri = `${cosmosdbResourceId}/clusters/${clusterId}`;
try {
return await this.rpClient<SparkCluster>(uri).putAsync(uri, ArmApiVersions.documentDB, cluster);
} catch (error) {
Logger.logError(error, "SparkClusterManager/updateClusterAsync");
throw error;
}
}
public async deleteClusterAsync(cosmosdbResourceId: string, clusterId: string): Promise<void> {
const uri = `${cosmosdbResourceId}/clusters/${clusterId}`;
try {
await this.rpClient(uri).deleteAsync(uri, ArmApiVersions.documentDB);
} catch (error) {
Logger.logError(error, "SparkClusterManager/deleteClusterAsync");
throw error;
}
}
public async getClusterConnectionInfoAsync(
cosmosdbResourceId: string,
clusterId: string
): Promise<SparkClusterConnectionInfo> {
const uri = `${cosmosdbResourceId}/clusters/${clusterId}/getConnectionInfo`;
try {
return await this.rpClient<SparkClusterConnectionInfo>(uri).postAsync(uri, ArmApiVersions.documentDB, undefined);
} catch (error) {
Logger.logError(error, "SparkClusterManager/getClusterConnectionInfoAsync");
throw error;
}
}
public async getLibrariesAsync(cosmosdbResourceId: string): Promise<Library[]> {
const uri = `${cosmosdbResourceId}/libraries`;
try {
const response = (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as LibraryFeedResponse;
return response && response.value;
} catch (error) {
Logger.logError(error, "SparkClusterManager/getLibrariesAsync");
throw error;
}
}
public async getLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<Library> {
const uri = `${cosmosdbResourceId}/libraries/${libraryName}`;
try {
return (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as Library;
} catch (error) {
Logger.logError(error, "SparkClusterManager/getLibraryAsync");
throw error;
}
}
public async addLibraryAsync(cosmosdbResourceId: string, libraryName: string, library: Library): Promise<void> {
const uri = `${cosmosdbResourceId}/libraries/${encodeURIComponent(libraryName)}`;
try {
await this.rpClient(uri).putAsync(uri, ArmApiVersions.documentDB, library);
} catch (error) {
Logger.logError(error, "SparkClusterManager/putLibraryAsync");
throw error;
}
}
public async deleteLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<void> {
const uri = `${cosmosdbResourceId}/libraries/${libraryName}`;
try {
await this.rpClient(uri).deleteAsync(uri, ArmApiVersions.documentDB);
} catch (error) {
Logger.logError(error, "SparkClusterManager/deleteLibraryAsync");
throw error;
}
}
private rpClient<TResource>(uri: string): IResourceProviderClient<TResource> {
return this.resourceProviderClientFactory.getOrCreate(uri);
}
}

View File

@ -287,8 +287,6 @@
<upload-file-pane params="{data: uploadFilePane}"></upload-file-pane>
<string-input-pane params="{data: stringInputPane}"></string-input-pane>
<setup-notebooks-pane params="{data: setupNotebooksPane}"></setup-notebooks-pane>
<library-manage-pane params="{data: libraryManagePane}"></library-manage-pane>
<cluster-library-pane params="{data: clusterLibraryPane}"></cluster-library-pane>
<!-- ko if: isGitHubPaneEnabled -->
<github-repos-pane params="{data: gitHubReposPane}"></github-repos-pane>