mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-08-10 08:37:17 +01:00
Remove Phoenix & Notebooks - Phase 4: Remove GitHub integration (#2528)
Removes the GitHub notebook-repo integration that only existed to pin/browse notebook repositories. Deletes src/GitHub/, the GitHub controls/panes, GitHubUtils, JunoUtils, and the connectToGitHub webpack entry. Decouples NotebookManager, useNotebook, the resource tree, and Explorer from GitHub wiring. Trims JunoClient's GitHub-only methods while keeping the Schema and gallery methods. Removes GitHub config fields from ConfigContext and strips the dead github:// branches from NotebookUtil. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,121 +0,0 @@
|
||||
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import { RepoListItem } from "./GitHubReposComponent";
|
||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||
|
||||
export interface AddRepoComponentProps {
|
||||
container: Explorer;
|
||||
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
|
||||
pinRepo: (item: RepoListItem) => void;
|
||||
}
|
||||
|
||||
interface AddRepoComponentState {
|
||||
textFieldValue: string;
|
||||
textFieldErrorMessage: string;
|
||||
}
|
||||
|
||||
export class AddRepoComponent extends React.Component<AddRepoComponentProps, AddRepoComponentState> {
|
||||
private static readonly DescriptionText =
|
||||
"Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: ";
|
||||
private static readonly ButtonText = "Add";
|
||||
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
||||
private static readonly TextFieldErrorMessage = "Invalid url";
|
||||
|
||||
constructor(props: AddRepoComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
textFieldValue: "",
|
||||
textFieldErrorMessage: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const textFieldProps: ITextFieldProps = {
|
||||
placeholder: AddRepoComponent.TextFieldPlaceholder,
|
||||
autoFocus: true,
|
||||
value: this.state.textFieldValue,
|
||||
errorMessage: this.state.textFieldErrorMessage,
|
||||
onChange: this.onTextFieldChange,
|
||||
};
|
||||
|
||||
const buttonProps: IButtonProps = {
|
||||
text: AddRepoComponent.ButtonText,
|
||||
ariaLabel: AddRepoComponent.ButtonText,
|
||||
onClick: this.onAddRepoButtonClick,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p style={{ marginBottom: ChildrenMargin }}>{AddRepoComponent.DescriptionText}</p>
|
||||
<TextField {...textFieldProps} />
|
||||
<DefaultButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onTextFieldChange = (
|
||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
newValue?: string,
|
||||
): void => {
|
||||
this.setState({
|
||||
textFieldValue: newValue || "",
|
||||
textFieldErrorMessage: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
private onAddRepoButtonClick = async (): Promise<void> => {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, {
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
});
|
||||
let enteredUrl = this.state.textFieldValue;
|
||||
if (enteredUrl.indexOf("/tree/") === -1) {
|
||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`);
|
||||
}
|
||||
|
||||
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
||||
if (repoInfo) {
|
||||
this.setState({
|
||||
textFieldValue: "",
|
||||
textFieldErrorMessage: undefined,
|
||||
});
|
||||
|
||||
const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo);
|
||||
if (repo) {
|
||||
const item: RepoListItem = {
|
||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||
repo,
|
||||
branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [],
|
||||
};
|
||||
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.NotebooksGitHubManualRepoAdd,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
},
|
||||
startKey,
|
||||
);
|
||||
return this.props.pinRepo(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage,
|
||||
});
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.NotebooksGitHubManualRepoAdd,
|
||||
{
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
error: AddRepoComponent.TextFieldErrorMessage,
|
||||
},
|
||||
startKey,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { ChoiceGroup, IButtonProps, IChoiceGroupProps, PrimaryButton, IChoiceGroupOption } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||
|
||||
export interface AuthorizeAccessComponentProps {
|
||||
scope: string;
|
||||
authorizeAccess: (scope: string) => void;
|
||||
}
|
||||
|
||||
export interface AuthorizeAccessComponentState {
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export class AuthorizeAccessComponent extends React.Component<
|
||||
AuthorizeAccessComponentProps,
|
||||
AuthorizeAccessComponentState
|
||||
> {
|
||||
// Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos.
|
||||
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
|
||||
public static readonly Scopes = {
|
||||
Public: {
|
||||
key: "public_repo",
|
||||
text: "Public repos only",
|
||||
},
|
||||
PublicAndPrivate: {
|
||||
key: "repo",
|
||||
text: "Public and private repos",
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly DescriptionPara1 =
|
||||
"Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer.";
|
||||
private static readonly DescriptionPara2 =
|
||||
"Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: ";
|
||||
private static readonly AuthorizeButtonText = "Authorize access";
|
||||
|
||||
private onChoiceGroupChange = (event: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void =>
|
||||
this.setState({
|
||||
scope: option.key,
|
||||
});
|
||||
|
||||
private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope);
|
||||
|
||||
constructor(props: AuthorizeAccessComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
scope: this.props.scope,
|
||||
};
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const choiceGroupProps: IChoiceGroupProps = {
|
||||
options: [
|
||||
{
|
||||
key: AuthorizeAccessComponent.Scopes.Public.key,
|
||||
text: AuthorizeAccessComponent.Scopes.Public.text,
|
||||
ariaLabel: AuthorizeAccessComponent.Scopes.Public.text,
|
||||
},
|
||||
{
|
||||
key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key,
|
||||
text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
|
||||
ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
|
||||
},
|
||||
],
|
||||
selectedKey: this.state.scope,
|
||||
onChange: this.onChoiceGroupChange,
|
||||
};
|
||||
|
||||
const buttonProps: IButtonProps = {
|
||||
text: AuthorizeAccessComponent.AuthorizeButtonText,
|
||||
ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText,
|
||||
onClick: this.onButtonClick,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{AuthorizeAccessComponent.DescriptionPara1}</p>
|
||||
<p style={{ marginTop: ChildrenMargin }}>{AuthorizeAccessComponent.DescriptionPara2}</p>
|
||||
<ChoiceGroup style={{ marginTop: ChildrenMargin }} {...choiceGroupProps} />
|
||||
<PrimaryButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
|
||||
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
|
||||
import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants";
|
||||
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
|
||||
|
||||
export interface GitHubReposComponentProps {
|
||||
showAuthorizeAccess: boolean;
|
||||
authorizeAccessProps: AuthorizeAccessComponentProps;
|
||||
reposListProps: ReposListComponentProps;
|
||||
addRepoProps: AddRepoComponentProps;
|
||||
resetConnection: () => void;
|
||||
onOkClick: () => void;
|
||||
onCancelClick: () => void;
|
||||
}
|
||||
|
||||
export interface RepoListItem {
|
||||
key: string;
|
||||
repo: IGitHubRepo;
|
||||
branches: IGitHubBranch[];
|
||||
}
|
||||
|
||||
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
|
||||
private static readonly ManageGitHubRepoDescription =
|
||||
"Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
|
||||
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
|
||||
private static readonly OKButtonText = "OK";
|
||||
private static readonly CancelButtonText = "Cancel";
|
||||
|
||||
public render(): JSX.Element {
|
||||
const content: JSX.Element = this.props.showAuthorizeAccess ? (
|
||||
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
|
||||
) : (
|
||||
<>
|
||||
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
|
||||
<Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}>
|
||||
{GitHubReposComponent.ManageGitHubRepoResetConnection}
|
||||
</Link>
|
||||
<ReposListComponent {...this.props.reposListProps} />
|
||||
</>
|
||||
);
|
||||
|
||||
const okProps: IButtonProps = {
|
||||
text: GitHubReposComponent.OKButtonText,
|
||||
ariaLabel: GitHubReposComponent.OKButtonText,
|
||||
onClick: this.props.onOkClick,
|
||||
};
|
||||
|
||||
const cancelProps: IButtonProps = {
|
||||
text: GitHubReposComponent.CancelButtonText,
|
||||
ariaLabel: GitHubReposComponent.CancelButtonText,
|
||||
onClick: this.props.onCancelClick,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>{content}</div>
|
||||
{!this.props.showAuthorizeAccess && (
|
||||
<>
|
||||
<div className={"paneFooter"} style={ContentFooterStyle}>
|
||||
<AddRepoComponent {...this.props.addRepoProps} />
|
||||
</div>
|
||||
<div className={"paneFooter"} style={ButtonsFooterStyle}>
|
||||
<PrimaryButton {...okProps} />
|
||||
<DefaultButton style={{ marginLeft: ChildrenMargin }} {...cancelProps} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
ICheckboxStyleProps,
|
||||
ICheckboxStyles,
|
||||
IDropdownStyleProps,
|
||||
IDropdownStyles,
|
||||
IStyleFunctionOrObject,
|
||||
} from "@fluentui/react";
|
||||
|
||||
export const ButtonsFooterStyle: React.CSSProperties = {
|
||||
paddingTop: 14,
|
||||
height: "auto",
|
||||
borderTop: "2px solid lightGray",
|
||||
};
|
||||
|
||||
export const ContentFooterStyle: React.CSSProperties = {
|
||||
paddingTop: "10px",
|
||||
height: "auto",
|
||||
borderTop: "2px solid lightGray",
|
||||
};
|
||||
|
||||
export const ChildrenMargin = 10;
|
||||
export const FontSize = 12;
|
||||
|
||||
export const ReposListCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: "2 0 2 0",
|
||||
},
|
||||
text: {
|
||||
fontSize: FontSize,
|
||||
},
|
||||
};
|
||||
|
||||
export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: FontSize,
|
||||
},
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
text: {
|
||||
fontSize: FontSize,
|
||||
},
|
||||
};
|
||||
|
||||
export const BranchesDropdownStyles: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles> = {
|
||||
title: {
|
||||
fontSize: FontSize,
|
||||
},
|
||||
};
|
||||
|
||||
export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
|
||||
padding: 8,
|
||||
};
|
||||
|
||||
export const ContentMainStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
};
|
||||
|
||||
export const ReposListRepoColumnMinWidth = 192;
|
||||
export const ReposListBranchesColumnWidth = 116;
|
||||
export const BranchesDropdownWidth = 200;
|
||||
@@ -1,309 +0,0 @@
|
||||
import {
|
||||
Checkbox,
|
||||
DetailsList,
|
||||
DetailsRow,
|
||||
Dropdown,
|
||||
ICheckboxProps,
|
||||
IDetailsFooterProps,
|
||||
IDetailsListProps,
|
||||
IDetailsRowBaseProps,
|
||||
IDropdown,
|
||||
IDropdownOption,
|
||||
IDropdownProps,
|
||||
ILinkProps,
|
||||
ISelectableDroppableTextProps,
|
||||
Link,
|
||||
ResponsiveMode,
|
||||
SelectionMode,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import { RepoListItem } from "./GitHubReposComponent";
|
||||
import {
|
||||
BranchesDropdownCheckboxStyles,
|
||||
BranchesDropdownOptionContainerStyle,
|
||||
BranchesDropdownStyles,
|
||||
BranchesDropdownWidth,
|
||||
ReposListBranchesColumnWidth,
|
||||
ReposListCheckboxStyles,
|
||||
ReposListRepoColumnMinWidth,
|
||||
} from "./GitHubStyleConstants";
|
||||
|
||||
export interface ReposListComponentProps {
|
||||
branchesProps: Record<string, BranchesProps>; // key'd by repo key
|
||||
pinnedReposProps: PinnedReposProps;
|
||||
unpinnedReposProps: UnpinnedReposProps;
|
||||
pinRepo: (repo: RepoListItem) => void;
|
||||
unpinRepo: (repo: RepoListItem) => void;
|
||||
}
|
||||
|
||||
export interface BranchesProps {
|
||||
branches: IGitHubBranch[];
|
||||
lastPageInfo?: IGitHubPageInfo;
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
defaultBranchName: string;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
export interface PinnedReposProps {
|
||||
repos: RepoListItem[];
|
||||
}
|
||||
|
||||
export interface UnpinnedReposProps {
|
||||
repos: RepoListItem[];
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
export class ReposListComponent extends React.Component<ReposListComponentProps> {
|
||||
private static readonly PinnedReposColumnName = "Pinned repos";
|
||||
private static readonly UnpinnedReposColumnName = "Unpinned repos";
|
||||
private static readonly BranchesColumnName = "Branches";
|
||||
private static readonly LoadingText = "Loading...";
|
||||
private static readonly LoadMoreText = "Load more";
|
||||
private static readonly DefaultBranchNames = "master/main";
|
||||
private static readonly FooterIndex = -1;
|
||||
|
||||
public render(): JSX.Element {
|
||||
const pinnedReposListProps: IDetailsListProps = {
|
||||
styles: {
|
||||
contentWrapper: {
|
||||
height: this.props.pinnedReposProps.repos.length ? undefined : 0,
|
||||
},
|
||||
},
|
||||
items: this.props.pinnedReposProps.repos,
|
||||
getKey: ReposListComponent.getKey,
|
||||
selectionMode: SelectionMode.none,
|
||||
compact: true,
|
||||
columns: [
|
||||
{
|
||||
key: ReposListComponent.PinnedReposColumnName,
|
||||
name: ReposListComponent.PinnedReposColumnName,
|
||||
ariaLabel: ReposListComponent.PinnedReposColumnName,
|
||||
minWidth: ReposListRepoColumnMinWidth,
|
||||
onRender: this.onRenderPinnedReposColumnItem,
|
||||
},
|
||||
{
|
||||
key: ReposListComponent.BranchesColumnName,
|
||||
name: ReposListComponent.BranchesColumnName,
|
||||
ariaLabel: ReposListComponent.BranchesColumnName,
|
||||
minWidth: ReposListBranchesColumnWidth,
|
||||
maxWidth: ReposListBranchesColumnWidth,
|
||||
onRender: this.onRenderPinnedReposBranchesColumnItem,
|
||||
},
|
||||
],
|
||||
onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter,
|
||||
};
|
||||
|
||||
const unpinnedReposListProps: IDetailsListProps = {
|
||||
items: this.props.unpinnedReposProps.repos,
|
||||
getKey: ReposListComponent.getKey,
|
||||
selectionMode: SelectionMode.none,
|
||||
compact: true,
|
||||
columns: [
|
||||
{
|
||||
key: ReposListComponent.UnpinnedReposColumnName,
|
||||
name: ReposListComponent.UnpinnedReposColumnName,
|
||||
ariaLabel: ReposListComponent.UnpinnedReposColumnName,
|
||||
minWidth: ReposListRepoColumnMinWidth,
|
||||
onRender: this.onRenderUnpinnedReposColumnItem,
|
||||
},
|
||||
{
|
||||
key: ReposListComponent.BranchesColumnName,
|
||||
name: ReposListComponent.BranchesColumnName,
|
||||
ariaLabel: ReposListComponent.BranchesColumnName,
|
||||
minWidth: ReposListBranchesColumnWidth,
|
||||
maxWidth: ReposListBranchesColumnWidth,
|
||||
onRender: this.onRenderUnpinnedReposBranchesColumnItem,
|
||||
},
|
||||
],
|
||||
onRenderDetailsFooter:
|
||||
this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore
|
||||
? this.onRenderReposFooter
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailsList {...pinnedReposListProps} />
|
||||
<DetailsList {...unpinnedReposListProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
|
||||
if (index === ReposListComponent.FooterIndex) {
|
||||
return <Text>None</Text>;
|
||||
}
|
||||
|
||||
const checkboxProps: ICheckboxProps = {
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
|
||||
styles: ReposListCheckboxStyles,
|
||||
defaultChecked: true,
|
||||
onChange: () => this.props.unpinRepo(item),
|
||||
};
|
||||
|
||||
return <Checkbox {...checkboxProps} />;
|
||||
};
|
||||
|
||||
private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
|
||||
if (index === ReposListComponent.FooterIndex) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||
if (item.branches.length === 0 && branchesProps.defaultBranchName) {
|
||||
item.branches = [{ name: branchesProps.defaultBranchName }];
|
||||
}
|
||||
|
||||
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
||||
key: branch.name,
|
||||
text: branch.name,
|
||||
data: item,
|
||||
disabled: item.branches.length === 1 && branch.name === item.branches[0].name,
|
||||
selected: item.branches.findIndex((element) => element.name === branch.name) !== -1,
|
||||
}));
|
||||
|
||||
if (branchesProps.hasMore || branchesProps.isLoading) {
|
||||
const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText;
|
||||
options.push({
|
||||
key: text,
|
||||
text,
|
||||
data: item,
|
||||
index: ReposListComponent.FooterIndex,
|
||||
});
|
||||
}
|
||||
|
||||
const dropdownProps: IDropdownProps = {
|
||||
styles: BranchesDropdownStyles,
|
||||
dropdownWidth: BranchesDropdownWidth,
|
||||
responsiveMode: ResponsiveMode.large,
|
||||
options,
|
||||
onRenderList: this.onRenderBranchesDropdownList,
|
||||
};
|
||||
|
||||
if (item.branches.length === 1) {
|
||||
dropdownProps.placeholder = item.branches[0].name;
|
||||
} else if (item.branches.length > 1) {
|
||||
dropdownProps.placeholder = `${item.branches.length} branches`;
|
||||
}
|
||||
|
||||
return <Dropdown {...dropdownProps} />;
|
||||
};
|
||||
|
||||
private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
|
||||
if (index === ReposListComponent.FooterIndex) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const dropdownProps: IDropdownProps = {
|
||||
styles: BranchesDropdownStyles,
|
||||
options: [],
|
||||
placeholder: ReposListComponent.DefaultBranchNames,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
return <Dropdown {...dropdownProps} />;
|
||||
};
|
||||
|
||||
private onRenderBranchesDropdownList = (
|
||||
props: ISelectableDroppableTextProps<IDropdown, HTMLDivElement>,
|
||||
): JSX.Element => {
|
||||
const renderedList: JSX.Element[] = [];
|
||||
props.options.forEach((option: IDropdownOption) => {
|
||||
const item = (
|
||||
<div key={option.key} style={BranchesDropdownOptionContainerStyle}>
|
||||
{this.onRenderPinnedReposBranchesDropdownOption(option)}
|
||||
</div>
|
||||
);
|
||||
renderedList.push(item);
|
||||
});
|
||||
|
||||
return <>{renderedList}</>;
|
||||
};
|
||||
|
||||
private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element {
|
||||
const item: RepoListItem = option.data;
|
||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||
|
||||
if (option.index === ReposListComponent.FooterIndex) {
|
||||
const linkProps: ILinkProps = {
|
||||
disabled: branchesProps.isLoading,
|
||||
onClick: branchesProps.loadMore,
|
||||
};
|
||||
|
||||
return <Link {...linkProps}>{option.text}</Link>;
|
||||
}
|
||||
|
||||
const checkboxProps: ICheckboxProps = {
|
||||
...ReposListComponent.getCheckboxPropsForLabel(option.text),
|
||||
styles: BranchesDropdownCheckboxStyles,
|
||||
defaultChecked: option.selected,
|
||||
disabled: option.disabled,
|
||||
onChange: (event, checked) => {
|
||||
const repoListItem = { ...item };
|
||||
const branch: IGitHubBranch = { name: option.text };
|
||||
repoListItem.branches = repoListItem.branches.filter((element) => element.name !== branch.name);
|
||||
if (checked) {
|
||||
repoListItem.branches.push(branch);
|
||||
}
|
||||
|
||||
this.props.pinRepo(repoListItem);
|
||||
},
|
||||
};
|
||||
|
||||
return <Checkbox {...checkboxProps} />;
|
||||
}
|
||||
|
||||
private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
|
||||
if (index === ReposListComponent.FooterIndex) {
|
||||
const linkProps: ILinkProps = {
|
||||
disabled: this.props.unpinnedReposProps.isLoading,
|
||||
onClick: this.props.unpinnedReposProps.loadMore,
|
||||
};
|
||||
|
||||
const linkText = this.props.unpinnedReposProps.isLoading
|
||||
? ReposListComponent.LoadingText
|
||||
: ReposListComponent.LoadMoreText;
|
||||
return <Link {...linkProps}>{linkText}</Link>;
|
||||
}
|
||||
|
||||
const checkboxProps: ICheckboxProps = {
|
||||
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
|
||||
styles: ReposListCheckboxStyles,
|
||||
onChange: () => {
|
||||
const repoListItem = { ...item };
|
||||
repoListItem.branches = [];
|
||||
this.props.pinRepo(repoListItem);
|
||||
},
|
||||
};
|
||||
|
||||
return <Checkbox {...checkboxProps} />;
|
||||
};
|
||||
|
||||
private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => {
|
||||
const props: IDetailsRowBaseProps = {
|
||||
...detailsFooterProps,
|
||||
item: {},
|
||||
itemIndex: ReposListComponent.FooterIndex,
|
||||
};
|
||||
|
||||
return <DetailsRow {...props} />;
|
||||
};
|
||||
|
||||
private static getCheckboxPropsForLabel(label: string): ICheckboxProps {
|
||||
return {
|
||||
label,
|
||||
title: label,
|
||||
ariaLabel: label,
|
||||
};
|
||||
}
|
||||
|
||||
private static getKey(item: RepoListItem): string {
|
||||
return item.key;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import * as DataModels from "../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { UploadDetailsRecord } from "../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||
import MetricScenario from "../Metrics/MetricEvents";
|
||||
import { ApplicationMetricPhase } from "../Metrics/ScenarioConfig";
|
||||
import { scenarioMonitor } from "../Metrics/ScenarioMonitor";
|
||||
@@ -79,8 +78,6 @@ export default class Explorer {
|
||||
// Tabs
|
||||
public isTabsContentExpanded: ko.Observable<boolean>;
|
||||
|
||||
public gitHubOAuthService: GitHubOAuthService;
|
||||
|
||||
// Notebooks
|
||||
public notebookManager?: NotebookManager;
|
||||
|
||||
|
||||
@@ -2,21 +2,11 @@
|
||||
* Contains all notebook related stuff meant to be dynamically loaded by explorer
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { HttpStatusCodes } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { JunoClient } from "../../Juno/JunoClient";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { useDialog } from "../Controls/Dialog";
|
||||
import Explorer from "../Explorer";
|
||||
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export interface NotebookManagerOptions {
|
||||
container: Explorer;
|
||||
@@ -31,85 +21,12 @@ export default class NotebookManager {
|
||||
|
||||
public notebookClient: NotebookContainerClient;
|
||||
|
||||
public gitHubOAuthService: GitHubOAuthService;
|
||||
public gitHubClient: GitHubClient;
|
||||
|
||||
public initialize(params: NotebookManagerOptions): void {
|
||||
this.params = params;
|
||||
this.junoClient = new JunoClient();
|
||||
|
||||
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
||||
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
|
||||
|
||||
this.notebookClient = new NotebookContainerClient(() =>
|
||||
this.params.container.initNotebooks(userContext?.databaseAccount),
|
||||
);
|
||||
|
||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||
this.gitHubClient.setToken(token?.access_token);
|
||||
if (this?.gitHubOAuthService.isLoggedIn()) {
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
setTimeout(() => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Manage GitHub settings",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>,
|
||||
);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
this.params.refreshCommandBarButtons();
|
||||
this.params.refreshNotebookList();
|
||||
});
|
||||
|
||||
this.junoClient.subscribeToPinnedRepos((pinnedRepos) => {
|
||||
this.params.resourceTree.initializeGitHubRepos(pinnedRepos);
|
||||
this.params.resourceTree.triggerRender();
|
||||
useNotebook.getState().initializeGitHubRepos(pinnedRepos);
|
||||
});
|
||||
this.refreshPinnedRepos();
|
||||
}
|
||||
|
||||
public refreshPinnedRepos(): void {
|
||||
const token = this.gitHubOAuthService.getTokenObservable()();
|
||||
if (token) {
|
||||
this.junoClient.getPinnedRepos(token.scope);
|
||||
}
|
||||
}
|
||||
|
||||
// Octokit's error handler uses any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private onGitHubClientError = (error: any): void => {
|
||||
Logger.logError(getErrorMessage(error), "NotebookManager/onGitHubClientError");
|
||||
|
||||
if (error.status === HttpStatusCodes.Unauthorized) {
|
||||
this.gitHubOAuthService.resetToken();
|
||||
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkCancelModalDialog(
|
||||
undefined,
|
||||
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
|
||||
"Connect to GitHub",
|
||||
() =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Connect to GitHub",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>,
|
||||
),
|
||||
"Cancel",
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
|
||||
const fileName = "file";
|
||||
@@ -6,9 +5,6 @@ const notebookName = "file.ipynb";
|
||||
const folderPath = "folder";
|
||||
const filePath = `${folderPath}/${fileName}`;
|
||||
const notebookPath = `${folderPath}/${notebookName}`;
|
||||
const gitHubFolderUri = GitHubUtils.toContentUri("owner", "repo", "branch", folderPath);
|
||||
const gitHubFileUri = GitHubUtils.toContentUri("owner", "repo", "branch", filePath);
|
||||
const gitHubNotebookUri = GitHubUtils.toContentUri("owner", "repo", "branch", notebookPath);
|
||||
|
||||
describe("NotebookUtil", () => {
|
||||
describe("isNotebookFile", () => {
|
||||
@@ -16,31 +12,18 @@ describe("NotebookUtil", () => {
|
||||
expect(NotebookUtil.isNotebookFile(filePath)).toBeFalsy();
|
||||
expect(NotebookUtil.isNotebookFile(notebookPath)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("works for github file uris", () => {
|
||||
expect(NotebookUtil.isNotebookFile(gitHubFileUri)).toBeFalsy();
|
||||
expect(NotebookUtil.isNotebookFile(gitHubNotebookUri)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFilePath", () => {
|
||||
it("works for jupyter file paths", () => {
|
||||
expect(NotebookUtil.getFilePath(folderPath, fileName)).toEqual(filePath);
|
||||
});
|
||||
|
||||
it("works for github file uris", () => {
|
||||
expect(NotebookUtil.getFilePath(gitHubFolderUri, fileName)).toEqual(gitHubFileUri);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getParentPath", () => {
|
||||
it("works for jupyter file paths", () => {
|
||||
expect(NotebookUtil.getParentPath(filePath)).toEqual(folderPath);
|
||||
});
|
||||
|
||||
it("works for github file uris", () => {
|
||||
expect(NotebookUtil.getParentPath(gitHubFileUri)).toEqual(gitHubFolderUri);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getName", () => {
|
||||
@@ -48,11 +31,6 @@ describe("NotebookUtil", () => {
|
||||
expect(NotebookUtil.getName(filePath)).toEqual(fileName);
|
||||
expect(NotebookUtil.getName(notebookPath)).toEqual(notebookName);
|
||||
});
|
||||
|
||||
it("works for github file uris", () => {
|
||||
expect(NotebookUtil.getName(gitHubFileUri)).toEqual(fileName);
|
||||
expect(NotebookUtil.getName(gitHubNotebookUri)).toEqual(notebookName);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replaceName", () => {
|
||||
@@ -60,12 +38,5 @@ describe("NotebookUtil", () => {
|
||||
expect(NotebookUtil.replaceName(filePath, "newName")).toEqual(filePath.replace(fileName, "newName"));
|
||||
expect(NotebookUtil.replaceName(notebookPath, "newName")).toEqual(notebookPath.replace(notebookName, "newName"));
|
||||
});
|
||||
|
||||
it("works for github file uris", () => {
|
||||
expect(NotebookUtil.replaceName(gitHubFileUri, "newName")).toEqual(gitHubFileUri.replace(fileName, "newName"));
|
||||
expect(NotebookUtil.replaceName(gitHubNotebookUri, "newName")).toEqual(
|
||||
gitHubNotebookUri.replace(notebookName, "newName"),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
|
||||
@@ -51,36 +50,12 @@ export class NotebookUtil {
|
||||
}
|
||||
|
||||
public static getFilePath(path: string, fileName: string): string {
|
||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||
if (contentInfo) {
|
||||
let path = fileName;
|
||||
if (contentInfo.path) {
|
||||
path = `${contentInfo.path}/${path}`;
|
||||
}
|
||||
return GitHubUtils.toContentUri(contentInfo.owner, contentInfo.repo, contentInfo.branch, path);
|
||||
}
|
||||
|
||||
return `${path}/${fileName}`;
|
||||
}
|
||||
|
||||
public static getParentPath(filepath: string): undefined | string {
|
||||
const basename = NotebookUtil.getName(filepath);
|
||||
if (basename) {
|
||||
const contentInfo = GitHubUtils.fromContentUri(filepath);
|
||||
if (contentInfo) {
|
||||
const parentPath = contentInfo.path.split(basename).shift();
|
||||
if (parentPath === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return GitHubUtils.toContentUri(
|
||||
contentInfo.owner,
|
||||
contentInfo.repo,
|
||||
contentInfo.branch,
|
||||
parentPath.replace(/\/$/, ""), // no trailling slash
|
||||
);
|
||||
}
|
||||
|
||||
const parentPath = filepath.split(basename).shift();
|
||||
if (parentPath) {
|
||||
return parentPath.replace(/\/$/, ""); // no trailling slash
|
||||
@@ -91,27 +66,10 @@ export class NotebookUtil {
|
||||
}
|
||||
|
||||
public static getName(path: string): undefined | string {
|
||||
let relativePath: string = path;
|
||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||
if (contentInfo) {
|
||||
relativePath = contentInfo.path;
|
||||
}
|
||||
|
||||
return relativePath.split("/").pop();
|
||||
return path.split("/").pop();
|
||||
}
|
||||
|
||||
public static replaceName(path: string, newName: string): string {
|
||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||
if (contentInfo) {
|
||||
const contentName = contentInfo.path.split("/").pop();
|
||||
if (!contentName) {
|
||||
throw new Error(`Failed to extract name from github path ${contentInfo.path}`);
|
||||
}
|
||||
|
||||
const basePath = contentInfo.path.split(contentName).shift();
|
||||
return GitHubUtils.toContentUri(contentInfo.owner, contentInfo.repo, contentInfo.branch, `${basePath}${newName}`);
|
||||
}
|
||||
|
||||
const contentName = path.split("/").pop();
|
||||
if (!contentName) {
|
||||
throw new Error(`Failed to extract name from path ${path}`);
|
||||
|
||||
@@ -8,15 +8,12 @@ import * as Logger from "../../Common/Logger";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import NotebookManager from "./NotebookManager";
|
||||
|
||||
interface NotebookState {
|
||||
isNotebookEnabled: boolean;
|
||||
@@ -29,7 +26,6 @@ interface NotebookState {
|
||||
notebookBasePath: string;
|
||||
isInitializingNotebooks: boolean;
|
||||
myNotebooksContentRoot: NotebookContentItem;
|
||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
galleryContentRoot: NotebookContentItem;
|
||||
connectionInfo: ContainerConnectionInfo;
|
||||
notebookFolderName: string;
|
||||
@@ -49,11 +45,10 @@ interface NotebookState {
|
||||
setNotebookFolderName: (notebookFolderName: string) => void;
|
||||
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
||||
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
|
||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem) => void;
|
||||
updateNotebookItem: (item: NotebookContentItem) => void;
|
||||
deleteNotebookItem: (item: NotebookContentItem) => void;
|
||||
initializeNotebooksTree: () => Promise<void>;
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||
setIsAllocating: (isAllocating: boolean) => void;
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||
@@ -83,7 +78,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
notebookBasePath: Constants.Notebook.defaultBasePath,
|
||||
isInitializingNotebooks: false,
|
||||
myNotebooksContentRoot: undefined,
|
||||
gitHubNotebooksContentRoot: undefined,
|
||||
galleryContentRoot: undefined,
|
||||
connectionInfo: {
|
||||
status: ConnectionStatusType.Connect,
|
||||
@@ -183,8 +177,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
|
||||
return undefined;
|
||||
},
|
||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem): void => {
|
||||
const root = cloneDeep(get().myNotebooksContentRoot);
|
||||
const parentItem = get().findItem(root, parent);
|
||||
item.parent = parentItem;
|
||||
if (parentItem.children) {
|
||||
@@ -192,23 +186,23 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
} else {
|
||||
parentItem.children = [item];
|
||||
}
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||
updateNotebookItem: (item: NotebookContentItem): void => {
|
||||
const root = cloneDeep(get().myNotebooksContentRoot);
|
||||
const parentItem = get().findItem(root, item.parent);
|
||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||
parentItem.children.push(item);
|
||||
item.parent = parentItem;
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||
deleteNotebookItem: (item: NotebookContentItem): void => {
|
||||
const root = cloneDeep(get().myNotebooksContentRoot);
|
||||
const parentItem = get().findItem(root, item.parent);
|
||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||
initializeNotebooksTree: async (): Promise<void> => {
|
||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
||||
set({ notebookFolderName });
|
||||
const myNotebooksContentRoot = {
|
||||
@@ -221,49 +215,12 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
path: "Gallery",
|
||||
type: NotebookContentItemType.File,
|
||||
};
|
||||
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||
? {
|
||||
name: "GitHub repos",
|
||||
path: "PsuedoDir",
|
||||
type: NotebookContentItemType.Directory,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
set({
|
||||
myNotebooksContentRoot,
|
||||
galleryContentRoot,
|
||||
gitHubNotebooksContentRoot,
|
||||
});
|
||||
},
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
|
||||
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
|
||||
if (gitHubNotebooksContentRoot) {
|
||||
gitHubNotebooksContentRoot.children = [];
|
||||
pinnedRepos?.forEach((pinnedRepo) => {
|
||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||
const repoTreeItem: NotebookContentItem = {
|
||||
name: repoFullName,
|
||||
path: "PsuedoDir",
|
||||
type: NotebookContentItemType.Directory,
|
||||
children: [],
|
||||
parent: gitHubNotebooksContentRoot,
|
||||
};
|
||||
|
||||
pinnedRepo.branches.forEach((branch) => {
|
||||
repoTreeItem.children.push({
|
||||
name: branch.name,
|
||||
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
||||
type: NotebookContentItemType.Directory,
|
||||
parent: repoTreeItem,
|
||||
});
|
||||
});
|
||||
|
||||
gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
||||
});
|
||||
|
||||
set({ gitHubNotebooksContentRoot });
|
||||
}
|
||||
},
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { GitHubClient } from "../../../GitHub/GitHubClient";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import Explorer from "../../Explorer";
|
||||
import { GitHubReposPanel } from "./GitHubReposPanel";
|
||||
const props = {
|
||||
explorer: new Explorer(),
|
||||
closePanel: (): void => undefined,
|
||||
gitHubClientProp: new GitHubClient((): void => undefined),
|
||||
junoClientProp: new JunoClient(),
|
||||
openNotificationConsole: (): void => undefined,
|
||||
};
|
||||
describe("GitHub Repos Panel", () => {
|
||||
it("should render Default properly", () => {
|
||||
const wrapper = shallow(<GitHubReposPanel {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,466 +0,0 @@
|
||||
import React from "react";
|
||||
import { Areas, HttpStatusCodes } from "../../../Common/Constants";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as JunoUtils from "../../../Utils/JunoUtils";
|
||||
import { AuthorizeAccessComponent } from "../../Controls/GitHub/AuthorizeAccessComponent";
|
||||
import {
|
||||
GitHubReposComponent,
|
||||
GitHubReposComponentProps,
|
||||
RepoListItem,
|
||||
} from "../../Controls/GitHub/GitHubReposComponent";
|
||||
import { ContentMainStyle } from "../../Controls/GitHub/GitHubStyleConstants";
|
||||
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../../Controls/GitHub/ReposListComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||
|
||||
interface IGitHubReposPanelProps {
|
||||
explorer: Explorer;
|
||||
gitHubClientProp: GitHubClient;
|
||||
junoClientProp: JunoClient;
|
||||
}
|
||||
|
||||
interface IGitHubReposPanelState {
|
||||
showAuthorizationAcessState: boolean;
|
||||
isExecuting: boolean;
|
||||
errorMessage: string;
|
||||
showErrorDetails: boolean;
|
||||
gitHubReposState: GitHubReposComponentProps;
|
||||
}
|
||||
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
||||
private static readonly PageSize = 30;
|
||||
private static readonly MasterBranchName = "master";
|
||||
private static readonly MainBranchName = "main";
|
||||
|
||||
private isAddedRepo = false;
|
||||
private gitHubClient: GitHubClient;
|
||||
private junoClient: JunoClient;
|
||||
|
||||
private branchesProps: Record<string, BranchesProps>;
|
||||
private pinnedReposProps: PinnedReposProps;
|
||||
private unpinnedReposProps: UnpinnedReposProps;
|
||||
|
||||
private allGitHubRepos: IGitHubRepo[];
|
||||
private allGitHubReposLastPageInfo?: IGitHubPageInfo;
|
||||
private pinnedReposUpdated: boolean;
|
||||
|
||||
constructor(props: IGitHubReposPanelProps) {
|
||||
super(props);
|
||||
|
||||
this.unpinnedReposProps = {
|
||||
repos: [],
|
||||
hasMore: true,
|
||||
isLoading: true,
|
||||
loadMore: (): Promise<void> => this.loadMoreUnpinnedRepos(),
|
||||
};
|
||||
this.branchesProps = {};
|
||||
this.pinnedReposProps = {
|
||||
repos: [],
|
||||
};
|
||||
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
this.pinnedReposUpdated = false;
|
||||
|
||||
this.state = {
|
||||
showAuthorizationAcessState: true,
|
||||
isExecuting: false,
|
||||
errorMessage: "",
|
||||
showErrorDetails: false,
|
||||
gitHubReposState: {
|
||||
showAuthorizeAccess: !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn(),
|
||||
authorizeAccessProps: {
|
||||
scope: this.getOAuthScope(),
|
||||
authorizeAccess: (scope): void => this.connectToGitHub(scope),
|
||||
},
|
||||
reposListProps: {
|
||||
branchesProps: this.branchesProps,
|
||||
pinnedReposProps: this.pinnedReposProps,
|
||||
unpinnedReposProps: this.unpinnedReposProps,
|
||||
pinRepo: (item): Promise<void> => this.pinRepo(item),
|
||||
unpinRepo: (item): Promise<void> => this.unpinRepo(item),
|
||||
},
|
||||
addRepoProps: {
|
||||
container: this.props.explorer,
|
||||
getRepo: (owner, repo): Promise<IGitHubRepo> => this.getRepo(owner, repo),
|
||||
pinRepo: (item): Promise<void> => this.pinRepo(item),
|
||||
},
|
||||
resetConnection: (): void => this.setup(true),
|
||||
onOkClick: (): Promise<void> => this.submit(),
|
||||
onCancelClick: (): void => useSidePanel.getState().closeSidePanel(),
|
||||
},
|
||||
};
|
||||
this.gitHubClient = this.props.gitHubClientProp;
|
||||
this.junoClient = this.props.junoClientProp;
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.open();
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.resetData();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
public async submit(): Promise<void> {
|
||||
const pinnedReposUpdated = this.pinnedReposUpdated;
|
||||
const reposToPin: IPinnedRepo[] = this.pinnedReposProps.repos.map((repo) => JunoUtils.toPinnedRepo(repo));
|
||||
|
||||
if (pinnedReposUpdated) {
|
||||
try {
|
||||
const response = await this.junoClient.updatePinnedRepos(reposToPin);
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||
}
|
||||
|
||||
this.props.explorer.notebookManager?.refreshPinnedRepos();
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||
}
|
||||
}
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
}
|
||||
|
||||
public resetData(): void {
|
||||
this.branchesProps = {};
|
||||
|
||||
this.pinnedReposProps.repos = [];
|
||||
this.unpinnedReposProps.repos = [];
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
|
||||
this.pinnedReposUpdated = false;
|
||||
this.unpinnedReposProps.hasMore = true;
|
||||
this.unpinnedReposProps.isLoading = true;
|
||||
}
|
||||
|
||||
private getOAuthScope(): string {
|
||||
return (
|
||||
this.props.explorer.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope ||
|
||||
AuthorizeAccessComponent.Scopes.Public.key
|
||||
);
|
||||
}
|
||||
|
||||
private setup(forceShowConnectToGitHub = false): void {
|
||||
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
|
||||
? this.setupForConnectToGitHub(forceShowConnectToGitHub)
|
||||
: this.setupForManageRepos();
|
||||
}
|
||||
|
||||
private setupForConnectToGitHub(forceShowConnectToGitHub: boolean): void {
|
||||
if (forceShowConnectToGitHub) {
|
||||
const newState = { ...this.state.gitHubReposState };
|
||||
newState.showAuthorizeAccess = forceShowConnectToGitHub;
|
||||
this.setState({
|
||||
gitHubReposState: newState,
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
isExecuting: false,
|
||||
});
|
||||
}
|
||||
|
||||
private async setupForManageRepos(): Promise<void> {
|
||||
this.setState({
|
||||
isExecuting: false,
|
||||
});
|
||||
TelemetryProcessor.trace(Action.NotebooksGitHubManageRepo, ActionModifiers.Mark, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
|
||||
this.refreshManageReposComponent();
|
||||
}
|
||||
|
||||
private calculateUnpinnedRepos(): RepoListItem[] {
|
||||
const unpinnedGitHubRepos = this.allGitHubRepos.filter(
|
||||
(gitHubRepo) =>
|
||||
this.pinnedReposProps.repos.findIndex(
|
||||
(pinnedRepo) => pinnedRepo.key === GitHubUtils.toRepoFullName(gitHubRepo.owner, gitHubRepo.name),
|
||||
) === -1,
|
||||
);
|
||||
return unpinnedGitHubRepos.map((gitHubRepo) => ({
|
||||
key: GitHubUtils.toRepoFullName(gitHubRepo.owner, gitHubRepo.name),
|
||||
repo: gitHubRepo,
|
||||
branches: [],
|
||||
}));
|
||||
}
|
||||
|
||||
private async loadMoreBranches(repo: IGitHubRepo): Promise<void> {
|
||||
const branchesProps = this.branchesProps[GitHubUtils.toRepoFullName(repo.owner, repo.name)];
|
||||
branchesProps.hasMore = true;
|
||||
branchesProps.isLoading = true;
|
||||
|
||||
try {
|
||||
const response = await this.gitHubClient.getBranchesAsync(
|
||||
repo.owner,
|
||||
repo.name,
|
||||
GitHubReposPanel.PageSize,
|
||||
branchesProps.lastPageInfo?.endCursor,
|
||||
);
|
||||
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching branches`);
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||
branchesProps.lastPageInfo = response.pageInfo;
|
||||
branchesProps.defaultBranchName = branchesProps.branches[0].name;
|
||||
const defaultbranchName = branchesProps.branches.find(
|
||||
(branch) =>
|
||||
branch.name === GitHubReposPanel.MasterBranchName || branch.name === GitHubReposPanel.MainBranchName,
|
||||
)?.name;
|
||||
if (defaultbranchName) {
|
||||
branchesProps.defaultBranchName = defaultbranchName;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
||||
}
|
||||
|
||||
branchesProps.isLoading = false;
|
||||
branchesProps.hasMore = branchesProps.lastPageInfo?.hasNextPage;
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
branchesProps: {
|
||||
...this.state.gitHubReposState.reposListProps.branchesProps,
|
||||
[GitHubUtils.toRepoFullName(repo.owner, repo.name)]: branchesProps,
|
||||
},
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async loadMoreUnpinnedRepos(): Promise<void> {
|
||||
this.unpinnedReposProps.isLoading = true;
|
||||
this.unpinnedReposProps.hasMore = true;
|
||||
|
||||
try {
|
||||
const response = await this.gitHubClient.getReposAsync(
|
||||
GitHubReposPanel.PageSize,
|
||||
this.allGitHubReposLastPageInfo?.endCursor,
|
||||
);
|
||||
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching unpinned repos`);
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
this.allGitHubRepos = this.allGitHubRepos.concat(response.data);
|
||||
this.allGitHubReposLastPageInfo = response.pageInfo;
|
||||
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/loadMoreUnpinnedRepos", "Failed to fetch unpinned repos");
|
||||
}
|
||||
|
||||
this.unpinnedReposProps.isLoading = false;
|
||||
this.unpinnedReposProps.hasMore = this.allGitHubReposLastPageInfo?.hasNextPage;
|
||||
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
isLoading: this.unpinnedReposProps.isLoading,
|
||||
hasMore: this.unpinnedReposProps.hasMore,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getRepo(owner: string, repo: string): Promise<IGitHubRepo> {
|
||||
try {
|
||||
const response = await this.gitHubClient.getRepoAsync(owner, repo);
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching repo`);
|
||||
}
|
||||
this.isAddedRepo = true;
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/getRepo", "Failed to fetch repo");
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private async pinRepo(item: RepoListItem): Promise<void> {
|
||||
this.pinnedReposUpdated = true;
|
||||
const initialReposLength = this.pinnedReposProps.repos.length;
|
||||
|
||||
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
||||
if (existingRepo) {
|
||||
existingRepo.branches = item.branches;
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
||||
}
|
||||
|
||||
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
|
||||
|
||||
if (this.pinnedReposProps.repos.length > initialReposLength) {
|
||||
this.refreshBranchesForPinnedRepos();
|
||||
}
|
||||
}
|
||||
|
||||
private async unpinRepo(item: RepoListItem): Promise<void> {
|
||||
this.pinnedReposUpdated = true;
|
||||
this.pinnedReposProps.repos = this.pinnedReposProps.repos.filter((pinnedRepo) => pinnedRepo.key !== item.key);
|
||||
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
|
||||
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async refreshManageReposComponent(): Promise<void> {
|
||||
await this.refreshPinnedRepoListItems();
|
||||
this.refreshBranchesForPinnedRepos();
|
||||
this.refreshUnpinnedRepoListItems();
|
||||
}
|
||||
|
||||
private async refreshPinnedRepoListItems(): Promise<void> {
|
||||
this.pinnedReposProps.repos = [];
|
||||
|
||||
try {
|
||||
const response = await this.junoClient.getPinnedRepos(
|
||||
this.props.explorer.notebookManager?.gitHubOAuthService.getTokenObservable()()?.scope,
|
||||
);
|
||||
|
||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||
throw new Error(`Received HTTP ${response.status} when fetching pinned repos`);
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
const pinnedRepos = response.data.map(
|
||||
(pinnedRepo) =>
|
||||
({
|
||||
key: GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name),
|
||||
branches: pinnedRepo.branches,
|
||||
repo: JunoUtils.toGitHubRepo(pinnedRepo),
|
||||
}) as RepoListItem,
|
||||
);
|
||||
|
||||
this.pinnedReposProps.repos = pinnedRepos;
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/refreshPinnedReposListItems", "Failed to fetch pinned repos");
|
||||
}
|
||||
}
|
||||
|
||||
private refreshBranchesForPinnedRepos(): void {
|
||||
this.pinnedReposProps.repos.map((item) => {
|
||||
if (!this.branchesProps[item.key]) {
|
||||
this.branchesProps[item.key] = {
|
||||
branches: [],
|
||||
lastPageInfo: undefined,
|
||||
hasMore: true,
|
||||
isLoading: true,
|
||||
defaultBranchName: undefined,
|
||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||
};
|
||||
this.loadMoreBranches(item.repo);
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
branchesProps: {
|
||||
...this.branchesProps,
|
||||
},
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.isAddedRepo = false;
|
||||
}
|
||||
|
||||
private async refreshUnpinnedRepoListItems(): Promise<void> {
|
||||
this.allGitHubRepos = [];
|
||||
this.allGitHubReposLastPageInfo = undefined;
|
||||
this.unpinnedReposProps.repos = [];
|
||||
|
||||
this.loadMoreUnpinnedRepos();
|
||||
}
|
||||
|
||||
private connectToGitHub(scope: string): void {
|
||||
this.setState({
|
||||
isExecuting: true,
|
||||
});
|
||||
TelemetryProcessor.trace(Action.NotebooksGitHubAuthorize, ActionModifiers.Mark, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
scopesSelected: scope,
|
||||
});
|
||||
this.props.explorer.notebookManager?.gitHubOAuthService.startOAuth(scope);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<form className="panelFormWrapper">
|
||||
{this.state.errorMessage && (
|
||||
<PanelInfoErrorComponent
|
||||
message={this.state.errorMessage}
|
||||
messageType="error"
|
||||
showErrorDetails={this.state.showErrorDetails}
|
||||
/>
|
||||
)}
|
||||
<div className="panelMainContent" style={ContentMainStyle}>
|
||||
<GitHubReposComponent {...this.state.gitHubReposState} />
|
||||
</div>
|
||||
|
||||
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
<form
|
||||
className="panelFormWrapper"
|
||||
>
|
||||
<div
|
||||
className="panelMainContent"
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
>
|
||||
<GitHubReposComponent
|
||||
addRepoProps={
|
||||
{
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"databasesRefreshed": Promise {},
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
},
|
||||
"getRepo": [Function],
|
||||
"pinRepo": [Function],
|
||||
}
|
||||
}
|
||||
authorizeAccessProps={
|
||||
{
|
||||
"authorizeAccess": [Function],
|
||||
"scope": "public_repo",
|
||||
}
|
||||
}
|
||||
onCancelClick={[Function]}
|
||||
onOkClick={[Function]}
|
||||
reposListProps={
|
||||
{
|
||||
"branchesProps": {},
|
||||
"pinRepo": [Function],
|
||||
"pinnedReposProps": {
|
||||
"repos": [],
|
||||
},
|
||||
"unpinRepo": [Function],
|
||||
"unpinnedReposProps": {
|
||||
"hasMore": true,
|
||||
"isLoading": true,
|
||||
"loadMore": [Function],
|
||||
"repos": [],
|
||||
},
|
||||
}
|
||||
}
|
||||
resetConnection={[Function]}
|
||||
showAuthorizeAccess={true}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
@@ -24,7 +24,6 @@ import Explorer from "../Explorer";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
|
||||
export const MyNotebooksTitle = "My Notebooks";
|
||||
export const GitHubReposTitle = "GitHub repos";
|
||||
|
||||
interface ResourceTreeProps {
|
||||
explorer: Explorer;
|
||||
|
||||
@@ -16,12 +16,10 @@ import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
||||
import { useDialog } from "../Controls/Dialog";
|
||||
@@ -40,7 +38,6 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
||||
|
||||
export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||
public static readonly GitHubReposTitle = "GitHub repos";
|
||||
|
||||
private static readonly DataTitle = "DATA";
|
||||
private static readonly NotebooksTitle = "NOTEBOOKS";
|
||||
@@ -49,7 +46,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
|
||||
public myNotebooksContentRoot: NotebookContentItem;
|
||||
public gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
|
||||
public constructor(private container: Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
@@ -106,42 +102,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
|
||||
this.gitHubNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.GitHubReposTitle,
|
||||
path: ResourceTreeAdapter.PseudoDirPath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
|
||||
return Promise.all(refreshTasks);
|
||||
}
|
||||
|
||||
public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void {
|
||||
if (this.gitHubNotebooksContentRoot) {
|
||||
this.gitHubNotebooksContentRoot.children = [];
|
||||
pinnedRepos?.forEach((pinnedRepo) => {
|
||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||
const repoTreeItem: NotebookContentItem = {
|
||||
name: repoFullName,
|
||||
path: ResourceTreeAdapter.PseudoDirPath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
children: [],
|
||||
};
|
||||
|
||||
pinnedRepo.branches.forEach((branch) => {
|
||||
repoTreeItem.children.push({
|
||||
name: branch.name,
|
||||
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
||||
type: NotebookContentItemType.Directory,
|
||||
});
|
||||
});
|
||||
|
||||
this.gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
||||
});
|
||||
|
||||
this.triggerRender();
|
||||
}
|
||||
}
|
||||
|
||||
private buildDataTree(): LegacyTreeNode {
|
||||
const databaseTreeNodes: LegacyTreeNode[] = useDatabases
|
||||
.getState()
|
||||
|
||||
Reference in New Issue
Block a user