mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 06:56:38 +00:00
More Spark UI Cleanup (#89)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
This commit is contained in:
parent
99c6a7ebcc
commit
444e25c086
@ -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
|
||||
|
@ -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>;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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());
|
||||
|
@ -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} />;
|
||||
}
|
@ -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()} />;
|
||||
}
|
||||
}
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
@ -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()} />;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
@ -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()} />;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user