Remove ResourceTreeAdapter

This commit is contained in:
Laurent Nguyen 2021-03-08 14:20:27 +01:00
parent 56b5a9861b
commit 4480a7250d
11 changed files with 155 additions and 82 deletions

View File

@ -392,6 +392,9 @@ export class Notebook {
public static readonly kernelRestartInitialDelayMs = 1000; public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000; public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000; public static readonly autoSaveIntervalMs = 120000;
public static readonly MyNotebooksTitle = "My Notebooks";
public static readonly GitHubReposTitle = "GitHub repos";
} }
export class SparkLibrary { export class SparkLibrary {

View File

@ -56,7 +56,6 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane"; import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory"; import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken"; import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
import { RouteHandler } from "../RouteHandlers/RouteHandler"; import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane";
@ -102,6 +101,8 @@ export interface ExplorerParams {
closeSidePanel: () => void; closeSidePanel: () => void;
closeDialog: () => void; closeDialog: () => void;
openDialog: (props: DialogProps) => void; openDialog: (props: DialogProps) => void;
onRefreshNotebookList: () => void;
} }
export default class Explorer { export default class Explorer {
@ -160,7 +161,6 @@ export default class Explorer {
public isLeftPaneExpanded: ko.Observable<boolean>; public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>; public selectedNode: ko.Observable<ViewModels.TreeNode>;
public isRefreshingExplorer: ko.Observable<boolean>; public isRefreshingExplorer: ko.Observable<boolean>;
private resourceTree: ResourceTreeAdapter;
private selfServeComponentAdapter: SelfServeComponentAdapter; private selfServeComponentAdapter: SelfServeComponentAdapter;
// Resource Token // Resource Token
@ -249,7 +249,7 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5; private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(params?: ExplorerParams) { constructor(private 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;
@ -859,7 +859,6 @@ export default class Explorer {
this.notebookManager.initialize({ this.notebookManager.initialize({
container: this, container: this,
notebookBasePath: this.notebookBasePath, notebookBasePath: this.notebookBasePath,
resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(), refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
refreshNotebookList: () => this.refreshNotebookList(), refreshNotebookList: () => this.refreshNotebookList(),
}); });
@ -874,7 +873,6 @@ export default class Explorer {
this.isSparkEnabled = ko.observable(false); this.isSparkEnabled = ko.observable(false);
this.isSparkEnabled.subscribe((isEnabled: boolean) => this.refreshCommandBarButtons()); this.isSparkEnabled.subscribe((isEnabled: boolean) => this.refreshCommandBarButtons());
this.resourceTree = new ResourceTreeAdapter(this);
this.resourceTreeForResourceToken = new ResourceTreeAdapterForResourceToken(this); this.resourceTreeForResourceToken = new ResourceTreeAdapterForResourceToken(this);
this.notebookServerInfo = ko.observable<DataModels.NotebookWorkspaceConnectionInfo>({ this.notebookServerInfo = ko.observable<DataModels.NotebookWorkspaceConnectionInfo>({
notebookServerEndpoint: undefined, notebookServerEndpoint: undefined,
@ -2109,12 +2107,16 @@ export default class Explorer {
return false; return false;
} }
}; };
private refreshNotebookList = async (): Promise<void> => { private refreshNotebookList = async (): Promise<void> => {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
return; return;
} }
await this.resourceTree.initialize(); console.log("=======> refreshNotebookList");
// await this.resourceTree.initialize();
this.params?.onRefreshNotebookList();
this.notebookManager?.refreshPinnedRepos(); this.notebookManager?.refreshPinnedRepos();
if (this.notebookToImport) { if (this.notebookToImport) {
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content); this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
@ -2250,7 +2252,7 @@ export default class Explorer {
}); });
} }
public refreshContentItem(item: NotebookContentItem): Promise<void> { public refreshContentItem(item: NotebookContentItem): Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to refresh notebook list, but notebook is not enabled"; const error = "Attempt to refresh notebook list, but notebook is not enabled";
handleError(error, "Explorer/refreshContentItem"); handleError(error, "Explorer/refreshContentItem");

View File

@ -18,11 +18,13 @@ export class NotebookContentClient {
/** /**
* This updates the item and points all the children's parent to this item * This updates the item and points all the children's parent to this item
* @param item * @param item
* @return updated item
*/ */
public updateItemChildren(item: NotebookContentItem): Promise<void> { public updateItemChildren(item: NotebookContentItem): Promise<NotebookContentItem> {
return this.fetchNotebookFiles(item.path).then((subItems) => { return this.fetchNotebookFiles(item.path).then((subItems) => {
item.children = subItems; item.children = subItems;
subItems.forEach((subItem) => (subItem.parent = item)); subItems.forEach((subItem) => (subItem.parent = item));
return item;
}); });
} }

View File

@ -18,7 +18,6 @@ import { contents } from "rx-jupyter";
import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContainerClient } from "./NotebookContainerClient";
import { MemoryUsageInfo } from "../../Contracts/DataModels"; import { MemoryUsageInfo } from "../../Contracts/DataModels";
import { NotebookContentClient } from "./NotebookContentClient"; import { NotebookContentClient } from "./NotebookContentClient";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter"; import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
import { getFullName } from "../../Utils/UserUtils"; import { getFullName } from "../../Utils/UserUtils";
import { ImmutableNotebook } from "@nteract/commutable"; import { ImmutableNotebook } from "@nteract/commutable";
@ -30,7 +29,7 @@ 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; // resourceTree: ResourceTreeAdapter;
refreshCommandBarButtons: () => void; refreshCommandBarButtons: () => void;
refreshNotebookList: () => void; refreshNotebookList: () => void;
} }

View File

@ -8,10 +8,9 @@ import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRight
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
import { IDropdownOption } from "office-ui-fabric-react"; import { IDropdownOption } from "office-ui-fabric-react";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
import { HttpStatusCodes } from "../../Common/Constants"; import { HttpStatusCodes, Notebook } from "../../Common/Constants";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem"; import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils"; import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
interface Location { interface Location {
@ -151,7 +150,7 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
switch (location.type) { switch (location.type) {
case "MyNotebooks": case "MyNotebooks":
parent = { parent = {
name: ResourceTreeAdapter.MyNotebooksTitle, name: Notebook.MyNotebooksTitle,
path: this.container.getNotebookBasePath(), path: this.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory, type: NotebookContentItemType.Directory,
}; };
@ -159,7 +158,7 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
case "GitHub": case "GitHub":
parent = { parent = {
name: ResourceTreeAdapter.GitHubReposTitle, name: Notebook.GitHubReposTitle,
path: GitHubUtils.toContentUri( path: GitHubUtils.toContentUri(
this.selectedLocation.owner, this.selectedLocation.owner,
this.selectedLocation.repo, this.selectedLocation.repo,

View File

@ -1,7 +1,6 @@
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as React from "react"; import * as React from "react";
import { IPinnedRepo } from "../../Juno/JunoClient"; import { IPinnedRepo } from "../../Juno/JunoClient";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { import {
Stack, Stack,
Label, Label,
@ -13,6 +12,7 @@ import {
IRenderFunction, IRenderFunction,
ISelectableOption, ISelectableOption,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { Notebook } from "../../Common/Constants";
interface Location { interface Location {
type: "MyNotebooks" | "GitHub"; type: "MyNotebooks" | "GitHub";
@ -70,8 +70,8 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
options.push({ options.push({
key: "MyNotebooks-Item", key: "MyNotebooks-Item",
text: ResourceTreeAdapter.MyNotebooksTitle, text: Notebook.MyNotebooksTitle,
title: ResourceTreeAdapter.MyNotebooksTitle, title: Notebook.MyNotebooksTitle,
data: { data: {
type: "MyNotebooks", type: "MyNotebooks",
} as Location, } as Location,
@ -86,7 +86,7 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
options.push({ options.push({
key: "GitHub-Header", key: "GitHub-Header",
text: ResourceTreeAdapter.GitHubReposTitle, text: Notebook.GitHubReposTitle,
itemType: SelectableOptionMenuItemType.Header, itemType: SelectableOptionMenuItemType.Header,
}); });

View File

@ -1,8 +1,7 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent"; import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem, TreeNodeComponent } from "../Controls/TreeComponent/TreeComponent"; import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory"; import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
@ -18,11 +17,10 @@ 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 _ from "underscore";
import { IPinnedRepo } from "../../Juno/JunoClient"; 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 } from "../../Common/Constants"; import { Areas, Notebook } 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";
@ -35,30 +33,44 @@ 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";
export class ResourceTreeAdapter implements ReactAdapter { export interface ResourceTreeProps {
public static readonly MyNotebooksTitle = "My Notebooks"; // TODO remove eventually
public static readonly GitHubReposTitle = "GitHub repos"; explorer: Explorer;
lastRefreshedTime: number;
}
interface ResourceTreeState {
galleryContentRoot: NotebookContentItem;
myNotebooksContentRoot: NotebookContentItem;
gitHubNotebooksContentRoot: NotebookContentItem;
}
export class ResourceTree extends React.Component<ResourceTreeProps, ResourceTreeState> {
private static readonly DataTitle = "DATA"; private static readonly DataTitle = "DATA";
private static readonly NotebooksTitle = "NOTEBOOKS"; private static readonly NotebooksTitle = "NOTEBOOKS";
private static readonly PseudoDirPath = "PsuedoDir"; private static readonly PseudoDirPath = "PseudoDir";
public parameters: ko.Observable<number>;
public galleryContentRoot: NotebookContentItem;
public myNotebooksContentRoot: NotebookContentItem;
public gitHubNotebooksContentRoot: NotebookContentItem;
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
public constructor(private container: Explorer) { private readonly container: Explorer;
this.parameters = ko.observable(Date.now());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); constructor(props: ResourceTreeProps) {
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender()); super(props);
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender()); this.state = {
galleryContentRoot: undefined,
myNotebooksContentRoot: undefined,
gitHubNotebooksContentRoot: undefined
};
this.container = props.explorer;
this.container.selectedNode.subscribe(() => this.triggerRender());
this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
this.container.isNotebookEnabled.subscribe(() => this.triggerRender());
this.koSubsDatabaseIdMap = new ArrayHashMap(); this.koSubsDatabaseIdMap = new ArrayHashMap();
this.koSubsCollectionIdMap = new ArrayHashMap(); this.koSubsCollectionIdMap = new ArrayHashMap();
@ -77,7 +89,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
private traceMyNotebookTreeInfo() { private traceMyNotebookTreeInfo() {
const myNotebooksTree = this.myNotebooksContentRoot; const myNotebooksTree = this.state.myNotebooksContentRoot;
if (myNotebooksTree.children) { if (myNotebooksTree.children) {
// Count 1st generation children (tree is lazy-loaded) // Count 1st generation children (tree is lazy-loaded)
const nodeCounts = { files: 0, notebooks: 0, directories: 0 }; const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
@ -100,7 +112,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
} }
public renderComponent(): JSX.Element { render(): JSX.Element {
const dataRootNode = this.buildDataTree(); const dataRootNode = this.buildDataTree();
const notebooksRootNode = this.buildNotebooksTrees(); const notebooksRootNode = this.buildNotebooksTrees();
@ -108,15 +120,15 @@ export class ResourceTreeAdapter implements ReactAdapter {
return ( return (
<> <>
<AccordionComponent> <AccordionComponent>
<AccordionItemComponent title={ResourceTreeAdapter.DataTitle} isExpanded={!this.gitHubNotebooksContentRoot}> <AccordionItemComponent title={ResourceTree.DataTitle} isExpanded={!this.state.gitHubNotebooksContentRoot}>
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} /> <TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
<AccordionItemComponent title={ResourceTreeAdapter.NotebooksTitle}> <AccordionItemComponent title={ResourceTree.NotebooksTitle}>
<TreeComponent className="notebookResourceTree" rootNode={notebooksRootNode} /> <TreeComponent className="notebookResourceTree" rootNode={notebooksRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
</AccordionComponent> </AccordionComponent>
{this.galleryContentRoot && this.buildGalleryCallout()} {this.state.galleryContentRoot && this.buildGalleryCallout()}
</> </>
); );
} else { } else {
@ -124,52 +136,71 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
} }
public async initialize(): Promise<void[]> { 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>[] = []; const refreshTasks: Promise<void>[] = [];
this.galleryContentRoot = { this.setState({
name: "Gallery", galleryContentRoot: {
path: "Gallery", name: "Gallery",
type: NotebookContentItemType.File, path: "Gallery",
}; type: NotebookContentItemType.File,
},
this.myNotebooksContentRoot = { myNotebooksContentRoot: {
name: ResourceTreeAdapter.MyNotebooksTitle, name: Notebook.MyNotebooksTitle,
path: this.container.getNotebookBasePath(), path: this.container.getNotebookBasePath(),
type: NotebookContentItemType.Directory, type: NotebookContentItemType.Directory,
}; }
});
console.log("====> componentDidUpdate");
// Only if notebook server is available we can refresh // Only if notebook server is available we can refresh
if (this.container.notebookServerInfo().notebookServerEndpoint) { if (this.container.notebookServerInfo().notebookServerEndpoint) {
refreshTasks.push( refreshTasks.push(
this.container.refreshContentItem(this.myNotebooksContentRoot).then(() => { this.container.refreshContentItem(this.state.myNotebooksContentRoot).then(root => {
this.triggerRender(); this.setState({ myNotebooksContentRoot: root });
// this.triggerRender();
this.traceMyNotebookTreeInfo(); this.traceMyNotebookTreeInfo();
}) })
); );
} }
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
this.gitHubNotebooksContentRoot = { this.setState({
name: ResourceTreeAdapter.GitHubReposTitle, gitHubNotebooksContentRoot: {
path: ResourceTreeAdapter.PseudoDirPath, name: Notebook.GitHubReposTitle,
type: NotebookContentItemType.Directory, path: ResourceTree.PseudoDirPath,
}; type: NotebookContentItemType.Directory,
}
});
} else { } else {
this.gitHubNotebooksContentRoot = undefined; this.setState({
gitHubNotebooksContentRoot: undefined
})
} }
return Promise.all(refreshTasks); return Promise.all(refreshTasks);
} }
public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void { public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void {
if (this.gitHubNotebooksContentRoot) { if (this.state.gitHubNotebooksContentRoot) {
this.gitHubNotebooksContentRoot.children = []; const { gitHubNotebooksContentRoot } = this.state;
gitHubNotebooksContentRoot.children = [];
this.setState({ gitHubNotebooksContentRoot });
pinnedRepos?.forEach((pinnedRepo) => { pinnedRepos?.forEach((pinnedRepo) => {
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name); const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
const repoTreeItem: NotebookContentItem = { const repoTreeItem: NotebookContentItem = {
name: repoFullName, name: repoFullName,
path: ResourceTreeAdapter.PseudoDirPath, path: ResourceTree.PseudoDirPath,
type: NotebookContentItemType.Directory, type: NotebookContentItemType.Directory,
children: [], children: [],
}; };
@ -182,7 +213,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
}); });
}); });
this.gitHubNotebooksContentRoot.children.push(repoTreeItem); this.state.gitHubNotebooksContentRoot.children.push(repoTreeItem);
}); });
this.triggerRender(); this.triggerRender();
@ -296,7 +327,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
children.push(schemaNode); children.push(schemaNode);
} }
if (ResourceTreeAdapter.showScriptNodes(this.container)) { if (ResourceTree.showScriptNodes(this.container)) {
children.push(this.buildStoredProcedureNode(collection)); children.push(this.buildStoredProcedureNode(collection));
children.push(this.buildUserDefinedFunctionsNode(collection)); children.push(this.buildUserDefinedFunctionsNode(collection));
children.push(this.buildTriggerNode(collection)); children.push(this.buildTriggerNode(collection));
@ -337,7 +368,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
); );
}, },
onExpanded: () => { onExpanded: () => {
if (ResourceTreeAdapter.showScriptNodes(this.container)) { if (ResourceTree.showScriptNodes(this.container)) {
collection.loadStoredProcedures(); collection.loadStoredProcedures();
collection.loadUserDefinedFunctions(); collection.loadUserDefinedFunctions();
collection.loadTriggers(); collection.loadTriggers();
@ -416,7 +447,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
public buildSchemaNode(collection: ViewModels.Collection): TreeNode { public buildSchemaNode(collection: ViewModels.Collection): TreeNode {
if (collection.analyticalStorageTtl() == undefined) { if (collection.analyticalStorageTtl() === undefined) {
return undefined; return undefined;
} }
@ -437,13 +468,15 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
private getSchemaNodes(fields: DataModels.IDataField[]): TreeNode[] { private getSchemaNodes(fields: DataModels.IDataField[]): TreeNode[] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const schema: any = {}; const schema: any = {};
//unflatten //unflatten
fields.forEach((field: DataModels.IDataField, fieldIndex: number) => { fields.forEach((field: DataModels.IDataField) => {
const path: string[] = field.path.split("."); const path: string[] = field.path.split(".");
const fieldProperties = [field.dataType.name, `HasNulls: ${field.hasNulls}`]; const fieldProperties = [field.dataType.name, `HasNulls: ${field.hasNulls}`];
let current: any = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
let current: any = {};
path.forEach((name: string, pathIndex: number) => { path.forEach((name: string, pathIndex: number) => {
if (pathIndex === 0) { if (pathIndex === 0) {
if (schema[name] === undefined) { if (schema[name] === undefined) {
@ -467,9 +500,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
}); });
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const traverse = (obj: any): TreeNode[] => { const traverse = (obj: any): TreeNode[] => {
const children: TreeNode[] = []; const children: TreeNode[] = [];
// eslint-disable-next-line no-null/no-null
if (obj !== null && !Array.isArray(obj) && typeof obj === "object") { if (obj !== null && !Array.isArray(obj) && typeof obj === "object") {
Object.entries(obj).forEach(([key, value]) => { Object.entries(obj).forEach(([key, value]) => {
children.push({ label: key, children: traverse(value) }); children.push({ label: key, children: traverse(value) });
@ -485,21 +520,21 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
private buildNotebooksTrees(): TreeNode { private buildNotebooksTrees(): TreeNode {
let notebooksTree: TreeNode = { const notebooksTree: TreeNode = {
label: undefined, label: undefined,
isExpanded: true, isExpanded: true,
children: [], children: [],
}; };
if (this.galleryContentRoot) { if (this.state.galleryContentRoot) {
notebooksTree.children.push(this.buildGalleryNotebooksTree()); notebooksTree.children.push(this.buildGalleryNotebooksTree());
} }
if (this.myNotebooksContentRoot) { if (this.state.myNotebooksContentRoot) {
notebooksTree.children.push(this.buildMyNotebooksTree()); notebooksTree.children.push(this.buildMyNotebooksTree());
} }
if (this.gitHubNotebooksContentRoot) { if (this.state.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());
@ -569,7 +604,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
private buildMyNotebooksTree(): TreeNode { private buildMyNotebooksTree(): TreeNode {
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.myNotebooksContentRoot, this.state.myNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
@ -590,7 +625,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
private buildGitHubNotebooksTree(): TreeNode { private buildGitHubNotebooksTree(): TreeNode {
const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
this.gitHubNotebooksContentRoot, this.state.gitHubNotebooksContentRoot,
(item: NotebookContentItem) => { (item: NotebookContentItem) => {
this.container.openNotebook(item).then((hasOpened) => { this.container.openNotebook(item).then((hasOpened) => {
if (hasOpened) { if (hasOpened) {
@ -674,6 +709,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
/* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab.
NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. NotebookV2Tab could be dynamically imported, but not worth it to just get this type right.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(activeTab as any).notebookPath() === item.path (activeTab as any).notebookPath() === item.path
); );
}, },
@ -829,11 +865,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
/* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab.
NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. NotebookV2Tab could be dynamically imported, but not worth it to just get this type right.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(activeTab as any).notebookPath() === item.path (activeTab as any).notebookPath() === item.path
); );
}, },
contextMenu: contextMenu:
createDirectoryContextMenu && item.path !== ResourceTreeAdapter.PseudoDirPath createDirectoryContextMenu && item.path !== ResourceTree.PseudoDirPath
? this.createDirectoryContextMenu(item) ? this.createDirectoryContextMenu(item)
: undefined, : undefined,
data: item, data: item,
@ -841,8 +878,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
}; };
} }
public triggerRender() { private triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now())); this.setState({});
} }
/** /**

View File

@ -1,6 +1,6 @@
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import * as ko from "knockout"; import * as ko from "knockout";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter"; import { ResourceTreeAdapter } from "./ResourceTree";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";

View File

@ -2,7 +2,7 @@ import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import React from "react"; import React from "react";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter"; import { ResourceTreeAdapter } from "./ResourceTree";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { TreeComponent, TreeNode, TreeComponentProps } from "../Controls/TreeComponent/TreeComponent"; import { TreeComponent, TreeNode, TreeComponentProps } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";

View File

@ -69,6 +69,8 @@ import { NotificationConsoleComponent } from "./Explorer/Menus/NotificationConso
import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent"; import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent";
import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen"; import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen";
import { Dialog, DialogProps } from "./Explorer/Controls/Dialog"; import { Dialog, DialogProps } from "./Explorer/Controls/Dialog";
import { ResourceTree } from "./Explorer/Tree/ResourceTree";
import { useNotebooks } from "./hooks/useNotebooks";
initializeIcons(); initializeIcons();
@ -90,6 +92,7 @@ const App: React.FunctionComponent = () => {
}; };
const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel(); const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel();
const {lastRefreshTime, refreshList} = useNotebooks();
const explorerParams: ExplorerParams = { const explorerParams: ExplorerParams = {
setIsNotificationConsoleExpanded, setIsNotificationConsoleExpanded,
@ -99,10 +102,20 @@ const App: React.FunctionComponent = () => {
closeSidePanel, closeSidePanel,
openDialog, openDialog,
closeDialog, closeDialog,
onRefreshNotebookList: refreshList
}; };
const config = useConfig(); const config = useConfig();
const explorer = useKnockoutExplorer(config?.platform, explorerParams); const explorer = useKnockoutExplorer(config?.platform, explorerParams);
// const [databases, setDatabases] = useState();
// useEffect(() => {
// fetchDatabases().then((dbs) => {
// setDatabases(dbs)
// explorer.databases(dbs)
// });
// const databases = useDatabases(explorer)
return ( return (
<div className="flexContainer"> <div className="flexContainer">
<div <div
@ -167,7 +180,9 @@ const App: React.FunctionComponent = () => {
style={{ overflowY: "auto" }} style={{ overflowY: "auto" }}
data-bind="if: isAuthWithResourceToken(), react:resourceTreeForResourceToken" data-bind="if: isAuthWithResourceToken(), react:resourceTreeForResourceToken"
/> />
<div style={{ overflowY: "auto" }} data-bind="if: !isAuthWithResourceToken(), react:resourceTree" /> <div style={{ overflowY: "auto" }} data-bind="if: !isAuthWithResourceToken()">
<ResourceTree explorer={explorer} lastRefreshedTime={lastRefreshTime} />
</div>
</div> </div>
{/* Collections Window - End */} {/* Collections Window - End */}
</div> </div>

16
src/hooks/useNotebooks.ts Normal file
View File

@ -0,0 +1,16 @@
import { useState } from "react";
export interface NotebookHooks {
lastRefreshTime: number;
refreshList: () => void;
}
export const useNotebooks = (): NotebookHooks => {
const [lastRefreshTime, setLastRefreshTime] = useState<number>(undefined);
const refreshList = (): void => {
setLastRefreshTime(new Date().getTime());
}
return { lastRefreshTime, refreshList };
};