Fix notebook updates issues

This commit is contained in:
Laurent Nguyen 2021-03-11 16:22:59 +01:00
parent 4480a7250d
commit e48a6a10cb
6 changed files with 199 additions and 164 deletions

View File

@ -7,6 +7,7 @@
.main { .main {
height: 100%; height: 100%;
} }
border-right: 1px solid @BaseMedium;
} }
.resourceTreeScroll { .resourceTreeScroll {

View File

@ -45,7 +45,7 @@ import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane";
import { ExplorerMetrics } from "../Common/Constants"; import { ExplorerMetrics } from "../Common/Constants";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { FileSystemUtil } from "./Notebook/FileSystemUtil"; import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { IGalleryItem } from "../Juno/JunoClient"; import { IGalleryItem, IPinnedRepo } from "../Juno/JunoClient";
import { LoadQueryPane } from "./Panes/LoadQueryPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { sendMessage, sendCachedDataMessage } from "../Common/MessageHandler"; import { sendMessage, sendCachedDataMessage } from "../Common/MessageHandler";
@ -103,6 +103,8 @@ export interface ExplorerParams {
openDialog: (props: DialogProps) => void; openDialog: (props: DialogProps) => void;
onRefreshNotebookList: () => void; onRefreshNotebookList: () => void;
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
getMyNotebooksContentRoot: () => NotebookContentItem;
} }
export default class Explorer { export default class Explorer {
@ -249,7 +251,7 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5; private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(private params?: ExplorerParams) { constructor(public params?: ExplorerParams) {
this.setIsNotificationConsoleExpanded = params?.setIsNotificationConsoleExpanded; this.setIsNotificationConsoleExpanded = params?.setIsNotificationConsoleExpanded;
this.setNotificationConsoleData = params?.setNotificationConsoleData; this.setNotificationConsoleData = params?.setNotificationConsoleData;
this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted; this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted;
@ -1720,7 +1722,7 @@ export default class Explorer {
const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent); const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent);
promise promise
.then(() => this.resourceTree.triggerRender()) .then(() => this.params.onRefreshNotebookList())
.catch((reason: any) => this.showOkModalDialog("Unable to upload file", reason)); .catch((reason: any) => this.showOkModalDialog("Unable to upload file", reason));
return promise; return promise;
} }
@ -1728,7 +1730,7 @@ export default class Explorer {
public async importAndOpen(path: string): Promise<boolean> { public async importAndOpen(path: string): Promise<boolean> {
const name = NotebookUtil.getName(path); const name = NotebookUtil.getName(path);
const item = NotebookUtil.createNotebookContentItem(name, path, "file"); const item = NotebookUtil.createNotebookContentItem(name, path, "file");
const parent = this.resourceTree.myNotebooksContentRoot; const parent = this.params.getMyNotebooksContentRoot();
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) { if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
const existingItem = _.find(parent.children, (node) => node.name === name); const existingItem = _.find(parent.children, (node) => node.name === name);
@ -1745,7 +1747,8 @@ export default class Explorer {
} }
public async importAndOpenContent(name: string, content: string): Promise<boolean> { public async importAndOpenContent(name: string, content: string): Promise<boolean> {
const parent = this.resourceTree.myNotebooksContentRoot; // const parent = this.params.getMyNotebooksContentRoot();
const parent = this.params.getMyNotebooksContentRoot();
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) { if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
if (this.notebookToImport && this.notebookToImport.name === name && this.notebookToImport.content === content) { if (this.notebookToImport && this.notebookToImport.name === name && this.notebookToImport.content === content) {
@ -1930,7 +1933,6 @@ export default class Explorer {
return newNotebookFile; return newNotebookFile;
}); });
result.then(() => this.resourceTree.triggerRender());
return result; return result;
} }
@ -1951,7 +1953,6 @@ export default class Explorer {
defaultInput: "", defaultInput: "",
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input), onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input),
}); });
result.then(() => this.resourceTree.triggerRender());
return result; return result;
} }
@ -2113,8 +2114,6 @@ export default class Explorer {
return; return;
} }
console.log("=======> refreshNotebookList");
// await this.resourceTree.initialize();
this.params?.onRefreshNotebookList(); this.params?.onRefreshNotebookList();
this.notebookManager?.refreshPinnedRepos(); this.notebookManager?.refreshPinnedRepos();
@ -2179,7 +2178,7 @@ export default class Explorer {
throw new Error(error); throw new Error(error);
} }
parent = parent || this.resourceTree.myNotebooksContentRoot; parent = parent || this.params.getMyNotebooksContentRoot();
const notificationProgressId = NotificationConsoleUtils.logConsoleMessage( const notificationProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
@ -2203,7 +2202,7 @@ export default class Explorer {
); );
return this.openNotebook(newFile); return this.openNotebook(newFile);
}) })
.then(() => this.resourceTree.triggerRender()) .then(() => this.params.onRefreshNotebookList())
.catch((error: any) => { .catch((error: any) => {
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`; const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage);
@ -2221,7 +2220,7 @@ export default class Explorer {
} }
public onUploadToNotebookServerClicked(parent?: NotebookContentItem): void { public onUploadToNotebookServerClicked(parent?: NotebookContentItem): void {
parent = parent || this.resourceTree.myNotebooksContentRoot; parent = parent || this.params.getMyNotebooksContentRoot();
this.uploadFilePane.openWithOptions({ this.uploadFilePane.openWithOptions({
paneTitle: "Upload file to notebook server", paneTitle: "Upload file to notebook server",

View File

@ -29,7 +29,6 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export interface NotebookManagerOptions { export interface NotebookManagerOptions {
container: Explorer; container: Explorer;
notebookBasePath: ko.Observable<string>; notebookBasePath: ko.Observable<string>;
// resourceTree: ResourceTreeAdapter;
refreshCommandBarButtons: () => void; refreshCommandBarButtons: () => void;
refreshNotebookList: () => void; refreshNotebookList: () => void;
} }
@ -106,8 +105,8 @@ export default class NotebookManager {
}); });
this.junoClient.subscribeToPinnedRepos((pinnedRepos) => { this.junoClient.subscribeToPinnedRepos((pinnedRepos) => {
this.params.resourceTree.initializeGitHubRepos(pinnedRepos); // TODO Move this out of NotebookManager?
this.params.resourceTree.triggerRender(); this.params.container.params.initializeGitHubRepos(pinnedRepos);
}); });
this.refreshPinnedRepos(); this.refreshPinnedRepos();
} }

View File

@ -17,10 +17,9 @@ import FileIcon from "../../../images/notebook/file-cosmos.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg"; import PublishIcon from "../../../images/notebook/publish_content.svg";
import { ArrayHashMap } from "../../Common/ArrayHashMap"; import { ArrayHashMap } from "../../Common/ArrayHashMap";
import { NotebookUtil } from "../Notebook/NotebookUtil"; import { NotebookUtil } from "../Notebook/NotebookUtil";
import { IPinnedRepo } from "../../Juno/JunoClient";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas, Notebook } from "../../Common/Constants"; import { Areas } from "../../Common/Constants";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import GalleryIcon from "../../../images/GalleryIcon.svg"; import GalleryIcon from "../../../images/GalleryIcon.svg";
import { Callout, Text, Link, DirectionalHint, Stack, ICalloutProps, ILinkProps } from "office-ui-fabric-react"; import { Callout, Text, Link, DirectionalHint, Stack, ICalloutProps, ILinkProps } from "office-ui-fabric-react";
@ -32,6 +31,7 @@ import Trigger from "./Trigger";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { DataTitle, NotebooksTitle, PseudoDirPath } from "../../hooks/useNotebooks";
export interface ResourceTreeProps { export interface ResourceTreeProps {
// TODO remove eventually // TODO remove eventually
@ -39,19 +39,12 @@ export interface ResourceTreeProps {
lastRefreshedTime: number; lastRefreshedTime: number;
}
interface ResourceTreeState {
galleryContentRoot: NotebookContentItem; galleryContentRoot: NotebookContentItem;
myNotebooksContentRoot: NotebookContentItem; myNotebooksContentRoot: NotebookContentItem;
gitHubNotebooksContentRoot: NotebookContentItem; gitHubNotebooksContentRoot: NotebookContentItem;
} }
export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTreeState> { export class ResourceTree extends React.Component<ResourceTreeProps> {
private static readonly DataTitle = "DATA";
private static readonly NotebooksTitle = "NOTEBOOKS";
private static readonly PseudoDirPath = "PseudoDir";
private koSubsDatabaseIdMap: ArrayHashMap<ko.Subscription>; // database id -> ko subs private koSubsDatabaseIdMap: ArrayHashMap<ko.Subscription>; // database id -> ko subs
private koSubsCollectionIdMap: ArrayHashMap<ko.Subscription>; // collection id -> ko subs private koSubsCollectionIdMap: ArrayHashMap<ko.Subscription>; // collection id -> ko subs
private databaseCollectionIdMap: ArrayHashMap<string>; // database id -> collection ids private databaseCollectionIdMap: ArrayHashMap<string>; // database id -> collection ids
@ -63,7 +56,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
this.state = { this.state = {
galleryContentRoot: undefined, galleryContentRoot: undefined,
myNotebooksContentRoot: undefined, myNotebooksContentRoot: undefined,
gitHubNotebooksContentRoot: undefined gitHubNotebooksContentRoot: undefined,
}; };
this.container = props.explorer; this.container = props.explorer;
@ -85,31 +78,6 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
}); });
this.container.nonSystemDatabases().forEach((database: ViewModels.Database) => this.watchDatabase(database)); this.container.nonSystemDatabases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
this.triggerRender();
}
private traceMyNotebookTreeInfo() {
const myNotebooksTree = this.state.myNotebooksContentRoot;
if (myNotebooksTree.children) {
// Count 1st generation children (tree is lazy-loaded)
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
myNotebooksTree.children.forEach((treeNode) => {
switch ((treeNode as NotebookContentItem).type) {
case NotebookContentItemType.File:
nodeCounts.files++;
break;
case NotebookContentItemType.Directory:
nodeCounts.directories++;
break;
case NotebookContentItemType.Notebook:
nodeCounts.notebooks++;
break;
default:
break;
}
});
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
}
} }
render(): JSX.Element { render(): JSX.Element {
@ -120,15 +88,15 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
return ( return (
<> <>
<AccordionComponent> <AccordionComponent>
<AccordionItemComponent title={ResourceTree.DataTitle} isExpanded={!this.state.gitHubNotebooksContentRoot}> <AccordionItemComponent title={DataTitle} isExpanded={!this.props.gitHubNotebooksContentRoot}>
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} /> <TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
<AccordionItemComponent title={ResourceTree.NotebooksTitle}> <AccordionItemComponent title={NotebooksTitle}>
<TreeComponent className="notebookResourceTree" rootNode={notebooksRootNode} /> <TreeComponent className="notebookResourceTree" rootNode={notebooksRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
</AccordionComponent> </AccordionComponent>
{this.state.galleryContentRoot && this.buildGalleryCallout()} {this.props.galleryContentRoot && this.buildGalleryCallout()}
</> </>
); );
} else { } else {
@ -136,90 +104,6 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
} }
} }
componentDidUpdate(prevProps: ResourceTreeProps): void {
if (this.props.lastRefreshedTime === undefined || prevProps.lastRefreshedTime === this.props.lastRefreshedTime) {
return;
}
this.initialize();
}
private async initialize(): Promise<void[]> {
const refreshTasks: Promise<void>[] = [];
this.setState({
galleryContentRoot: {
name: "Gallery",
path: "Gallery",
type: NotebookContentItemType.File,
},
myNotebooksContentRoot: {
name: Notebook.MyNotebooksTitle,
path: this.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory,
}
});
console.log("====> componentDidUpdate");
// Only if notebook server is available we can refresh
if (this.container.notebookServerInfo().notebookServerEndpoint) {
refreshTasks.push(
this.container.refreshContentItem(this.state.myNotebooksContentRoot).then(root => {
this.setState({ myNotebooksContentRoot: root });
// this.triggerRender();
this.traceMyNotebookTreeInfo();
})
);
}
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
this.setState({
gitHubNotebooksContentRoot: {
name: Notebook.GitHubReposTitle,
path: ResourceTree.PseudoDirPath,
type: NotebookContentItemType.Directory,
}
});
} else {
this.setState({
gitHubNotebooksContentRoot: undefined
})
}
return Promise.all(refreshTasks);
}
public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void {
if (this.state.gitHubNotebooksContentRoot) {
const { gitHubNotebooksContentRoot } = this.state;
gitHubNotebooksContentRoot.children = [];
this.setState({ gitHubNotebooksContentRoot });
pinnedRepos?.forEach((pinnedRepo) => {
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
const repoTreeItem: NotebookContentItem = {
name: repoFullName,
path: ResourceTree.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.state.gitHubNotebooksContentRoot.children.push(repoTreeItem);
});
this.triggerRender();
}
}
private buildDataTree(): TreeNode { private buildDataTree(): TreeNode {
const databaseTreeNodes: TreeNode[] = this.container.nonSystemDatabases().map((database: ViewModels.Database) => { const databaseTreeNodes: TreeNode[] = this.container.nonSystemDatabases().map((database: ViewModels.Database) => {
const databaseNode: TreeNode = { const databaseNode: TreeNode = {
@ -526,15 +410,15 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
children: [], children: [],
}; };
if (this.state.galleryContentRoot) { if (this.props.galleryContentRoot) {
notebooksTree.children.push(this.buildGalleryNotebooksTree()); notebooksTree.children.push(this.buildGalleryNotebooksTree());
} }
if (this.state.myNotebooksContentRoot) { if (this.props.myNotebooksContentRoot) {
notebooksTree.children.push(this.buildMyNotebooksTree()); notebooksTree.children.push(this.buildMyNotebooksTree());
} }
if (this.state.gitHubNotebooksContentRoot) { if (this.props.gitHubNotebooksContentRoot) {
// collapse all other notebook nodes // collapse all other notebook nodes
notebooksTree.children.forEach((node) => (node.isExpanded = false)); notebooksTree.children.forEach((node) => (node.isExpanded = false));
notebooksTree.children.push(this.buildGitHubNotebooksTree()); notebooksTree.children.push(this.buildGitHubNotebooksTree());
@ -604,7 +488,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
private buildMyNotebooksTree(): TreeNode { private buildMyNotebooksTree(): TreeNode {
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.state.myNotebooksContentRoot, this.props.myNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
@ -625,7 +509,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
private buildGitHubNotebooksTree(): TreeNode { private buildGitHubNotebooksTree(): TreeNode {
const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.state.gitHubNotebooksContentRoot, this.props.gitHubNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
@ -723,7 +607,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
{ {
label: "Rename", label: "Rename",
iconSrc: NotebookIcon, iconSrc: NotebookIcon,
onClick: () => this.container.renameNotebook(item), onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender()),
}, },
{ {
label: "Delete", label: "Delete",
@ -812,7 +696,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
{ {
label: "New Directory", label: "New Directory",
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
onClick: () => this.container.onCreateDirectory(item), onClick: () => this.container.onCreateDirectory(item).then(() => this.triggerRender()),
}, },
{ {
label: "New Notebook", label: "New Notebook",
@ -870,9 +754,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTre
); );
}, },
contextMenu: contextMenu:
createDirectoryContextMenu && item.path !== ResourceTree.PseudoDirPath createDirectoryContextMenu && item.path !== PseudoDirPath ? this.createDirectoryContextMenu(item) : undefined,
? this.createDirectoryContextMenu(item)
: undefined,
data: item, data: item,
children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu), children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu),
}; };

View File

@ -55,7 +55,7 @@ import "./Libs/is-integer-polyfill";
import "url-polyfill/url-polyfill.min"; import "url-polyfill/url-polyfill.min";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { ExplorerParams } from "./Explorer/Explorer"; import Explorer, { ExplorerParams } from "./Explorer/Explorer";
import React, { useState } from "react"; import React, { useState } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
@ -92,7 +92,18 @@ const App: React.FunctionComponent = () => {
}; };
const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel(); const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel();
const {lastRefreshTime, refreshList} = useNotebooks();
// TODO Figure out a better pattern: this is because we don't have container, yet
const context: { container: Explorer } = { container: undefined };
const {
lastRefreshTime,
galleryContentRoot,
myNotebooksContentRoot,
gitHubNotebooksContentRoot,
refreshList,
initializeGitHubRepos,
getMyNotebooksContentRoot,
} = useNotebooks(context);
const explorerParams: ExplorerParams = { const explorerParams: ExplorerParams = {
setIsNotificationConsoleExpanded, setIsNotificationConsoleExpanded,
@ -102,11 +113,16 @@ const App: React.FunctionComponent = () => {
closeSidePanel, closeSidePanel,
openDialog, openDialog,
closeDialog, closeDialog,
onRefreshNotebookList: refreshList onRefreshNotebookList: refreshList,
initializeGitHubRepos,
getMyNotebooksContentRoot,
}; };
const config = useConfig(); const config = useConfig();
const explorer = useKnockoutExplorer(config?.platform, explorerParams); const explorer = useKnockoutExplorer(config?.platform, explorerParams);
// TODO fix this
context.container = explorer;
// const [databases, setDatabases] = useState(); // const [databases, setDatabases] = useState();
// useEffect(() => { // useEffect(() => {
// fetchDatabases().then((dbs) => { // fetchDatabases().then((dbs) => {
@ -115,7 +131,6 @@ const App: React.FunctionComponent = () => {
// }); // });
// const databases = useDatabases(explorer) // const databases = useDatabases(explorer)
return ( return (
<div className="flexContainer"> <div className="flexContainer">
<div <div
@ -181,7 +196,13 @@ const App: React.FunctionComponent = () => {
data-bind="if: isAuthWithResourceToken(), react:resourceTreeForResourceToken" data-bind="if: isAuthWithResourceToken(), react:resourceTreeForResourceToken"
/> />
<div style={{ overflowY: "auto" }} data-bind="if: !isAuthWithResourceToken()"> <div style={{ overflowY: "auto" }} data-bind="if: !isAuthWithResourceToken()">
<ResourceTree explorer={explorer} lastRefreshedTime={lastRefreshTime} /> <ResourceTree
explorer={explorer}
lastRefreshedTime={lastRefreshTime}
galleryContentRoot={galleryContentRoot}
myNotebooksContentRoot={myNotebooksContentRoot}
gitHubNotebooksContentRoot={gitHubNotebooksContentRoot}
/>
</div> </div>
</div> </div>
{/* Collections Window - End */} {/* Collections Window - End */}

View File

@ -1,16 +1,149 @@
import { useState } from "react"; import { useState } from "react";
import { Notebook } from "../Common/Constants";
import Explorer from "../Explorer/Explorer";
import { NotebookContentItem, NotebookContentItemType } from "../Explorer/Notebook/NotebookContentItem";
import { IPinnedRepo } from "../Juno/JunoClient";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import * as GitHubUtils from "../Utils/GitHubUtils";
export const DataTitle = "DATA";
export const NotebooksTitle = "NOTEBOOKS";
export const PseudoDirPath = "PseudoDir";
export interface NotebookHooks { export interface NotebookHooks {
lastRefreshTime: number; lastRefreshTime: number;
galleryContentRoot: NotebookContentItem;
myNotebooksContentRoot: NotebookContentItem;
gitHubNotebooksContentRoot: NotebookContentItem;
refreshList: () => void; refreshList: () => void;
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
getMyNotebooksContentRoot: () => NotebookContentItem;
} }
export const useNotebooks = (): NotebookHooks => { export const useNotebooks = (context: { container: Explorer }): NotebookHooks => {
const [lastRefreshTime, setLastRefreshTime] = useState<number>(undefined); const [lastRefreshTime, setLastRefreshTime] = useState<number>(undefined);
const [galleryContentRoot, setGalleryContentRoot] = useState<NotebookContentItem>(undefined);
const [myNotebooksContentRoot, setMyNotebooksContentRoot] = useState<NotebookContentItem>(undefined);
const [gitHubNotebooksContentRoot, setGitHubNotebooksContentRoot] = useState<NotebookContentItem>(undefined);
const refreshList = (): void => { const refreshList = (): void => {
initialize();
setLastRefreshTime(new Date().getTime()); setLastRefreshTime(new Date().getTime());
};
// TODO For now, we need to rely on this, as setMyNotebooksContentRoot() is not synchronous
let _myNotebooksContentRoot: NotebookContentItem = undefined;
const _setMyNotebooksContentRoot = (newValue: NotebookContentItem) => {
_myNotebooksContentRoot = newValue;
setMyNotebooksContentRoot(newValue);
};
const initialize = (): Promise<void[]> => {
const refreshTasks: Promise<void>[] = [];
setGalleryContentRoot({
name: "Gallery",
path: "Gallery",
type: NotebookContentItemType.File,
});
const _myNotebooksContentRoot = {
name: Notebook.MyNotebooksTitle,
path: context.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory,
};
_setMyNotebooksContentRoot(_myNotebooksContentRoot);
// Only if notebook server is available we can refresh
if (context.container.notebookServerInfo().notebookServerEndpoint) {
refreshTasks.push(
context.container.refreshContentItem(_myNotebooksContentRoot).then((root) => {
_setMyNotebooksContentRoot({ ...root });
traceMyNotebookTreeInfo(root);
})
);
} }
return { lastRefreshTime, refreshList }; initializeGitHubNotebooksContentRoot();
return Promise.all(refreshTasks);
};
const traceMyNotebookTreeInfo = (myNotebooksTree: NotebookContentItem) => {
if (myNotebooksTree.children) {
// Count 1st generation children (tree is lazy-loaded)
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
myNotebooksTree.children.forEach((treeNode) => {
switch ((treeNode as NotebookContentItem).type) {
case NotebookContentItemType.File:
nodeCounts.files++;
break;
case NotebookContentItemType.Directory:
nodeCounts.directories++;
break;
case NotebookContentItemType.Notebook:
nodeCounts.notebooks++;
break;
default:
break;
}
});
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
}
};
const initializeGitHubNotebooksContentRoot = (): NotebookContentItem => {
let root: NotebookContentItem = undefined;
if (context.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
root = {
name: Notebook.GitHubReposTitle,
path: PseudoDirPath,
type: NotebookContentItemType.Directory,
};
}
setGitHubNotebooksContentRoot(root);
return root;
};
const initializeGitHubRepos = (pinnedRepos: IPinnedRepo[]): void => {
const _gitHubNotebooksContentRoot = initializeGitHubNotebooksContentRoot();
if (_gitHubNotebooksContentRoot) {
_gitHubNotebooksContentRoot.children = [];
pinnedRepos?.forEach((pinnedRepo) => {
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
const repoTreeItem: NotebookContentItem = {
name: repoFullName,
path: 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,
});
});
_gitHubNotebooksContentRoot.children.push(repoTreeItem);
});
setGitHubNotebooksContentRoot({ ..._gitHubNotebooksContentRoot });
}
};
return {
lastRefreshTime,
galleryContentRoot,
myNotebooksContentRoot,
gitHubNotebooksContentRoot,
refreshList,
initializeGitHubRepos,
getMyNotebooksContentRoot: () => _myNotebooksContentRoot,
};
}; };