Remove Phoenix & Notebooks - Phase 2: Remove in-app notebook authoring & rendering (#2515)

Delete the nteract rendering engine, notebook tabs, panes, the read-only viewer, Schema Analyzer (pulled forward from Phase 3), and all UI entry points that open notebooks. Decouple surviving files (Explorer, NotebookManager, useNotebook, ResourceTreeAdapter) with minimal edits, keeping GitHub/Juno/Phoenix wiring for later phases.

Removed 22 zero-importer notebook-only npm deps; re-added phantom transitively-hoisted deps still used by surviving code: xterm, xterm-addon-fit (CloudShell), d3-collection (Graph), @nteract/myths (@nteract/core).

Verified: compile, compile:strict, lint (0 errors), format:check, test (1945 passing), build:ci all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
jawelton74
2026-06-22 09:36:47 -07:00
committed by GitHub
parent 0e175c8a9c
commit 8e90672ff5
109 changed files with 142 additions and 14512 deletions
@@ -1,11 +0,0 @@
.notebookViewerMetadataContainer {
margin: 0px 10px;
.title, .decoration, .persona {
display: inline-block;
}
.extras {
margin-top: 5px;
}
}
@@ -1,63 +0,0 @@
import { shallow } from "enzyme";
import React from "react";
import { NotebookMetadataComponent, NotebookMetadataComponentProps } from "./NotebookMetadataComponent";
describe("NotebookMetadataComponent", () => {
it("renders un-liked notebook", () => {
const props: NotebookMetadataComponentProps = {
data: {
id: "id",
name: "name",
description: "description",
author: "author",
thumbnailUrl: "thumbnailUrl",
created: "created",
gitSha: "gitSha",
tags: ["tag"],
isSample: false,
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined,
},
isFavorite: false,
downloadButtonText: "Download",
onTagClick: undefined,
};
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders liked notebook", () => {
const props: NotebookMetadataComponentProps = {
data: {
id: "id",
name: "name",
description: "description",
author: "author",
thumbnailUrl: "thumbnailUrl",
created: "created",
gitSha: "gitSha",
tags: ["tag"],
isSample: false,
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined,
},
isFavorite: true,
downloadButtonText: "Download",
onTagClick: undefined,
};
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
// TODO Add test for metadata display
});
@@ -1,77 +0,0 @@
/**
* Wrapper around Notebook metadata
*/
import { FontWeights, Icon, Link, Persona, PersonaSize, Stack, Text } from "@fluentui/react";
import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
export interface NotebookMetadataComponentProps {
data: IGalleryItem;
isFavorite: boolean;
downloadButtonText?: string;
onTagClick: (tag: string) => void;
}
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
public render(): JSX.Element {
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "short",
day: "numeric",
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
return (
<Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
<Stack.Item>
<Text variant="xxLarge" nowrap>
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
</Text>
</Stack.Item>
<Stack.Item>
<Text>
<Icon iconName="Heart" /> {this.props.data.favorites} likes
</Text>
</Stack.Item>
</Stack>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
size={PersonaSize.size32}
/>
<Text>{dateString}</Text>
<Text>
<Icon iconName="RedEye" /> {this.props.data.views}
</Text>
<Text>
<Icon iconName="Download" />
{this.props.data.downloads}
</Text>
</Stack>
<Text nowrap>
{this.props.data.tags?.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(): void => this.props.onTagClick(tag)}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))}
</Text>
<Text variant="large" styles={{ root: { fontWeight: FontWeights.semibold } }}>
Description
</Text>
<Text>{this.props.data.description}</Text>
</Stack>
);
}
}
@@ -1,8 +0,0 @@
@import "../../../../less/Common/Constants";
.notebookViewerContainer {
padding: 30px;
height: 100%;
width: 100%;
overflow-y: auto;
}
@@ -1,185 +0,0 @@
/**
* Wrapper around Notebook Viewer Read only content
*/
import { Icon, Link, ProgressIndicator } from "@fluentui/react";
import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core";
import * as React from "react";
import { contents } from "rx-jupyter";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../../Explorer";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { useNotebook } from "../../Notebook/useNotebook";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less";
export interface NotebookViewerComponentProps {
container?: Explorer;
junoClient?: JunoClient;
notebookUrl: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
backNavigationText: string;
hideInputs?: boolean;
hidePrompts?: boolean;
onBackClick: () => void;
onTagClick: (tag: string) => void;
}
interface NotebookViewerComponentState {
content: Notebook;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
showProgressBar: boolean;
}
export class NotebookViewerComponent extends React.Component<
NotebookViewerComponentProps,
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
constructor(props: NotebookViewerComponentProps) {
super(props);
this.clientManager = new NotebookClientV2({
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
databaseAccountName: undefined,
defaultExperience: "NotebookViewer",
isReadOnly: true,
cellEditorType: "codemirror",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
});
this.notebookComponentBootstrapper = new NotebookComponentBootstrapper({
notebookClient: this.clientManager,
contentRef: createContentRef(),
});
this.state = {
content: undefined,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
showProgressBar: true,
};
this.loadNotebookContent();
}
private async loadNotebookContent(): Promise<void> {
const startKey = traceStart(Action.NotebooksGalleryViewNotebook, {
notebookUrl: this.props.notebookUrl,
notebookId: this.props.galleryItem?.id,
isSample: this.props.galleryItem?.isSample,
});
try {
const response = await fetch(this.props.notebookUrl);
if (!response.ok) {
this.setState({ showProgressBar: false });
throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`);
}
traceSuccess(
Action.NotebooksGalleryViewNotebook,
{
notebookUrl: this.props.notebookUrl,
notebookId: this.props.galleryItem?.id,
isSample: this.props.galleryItem?.isSample,
},
startKey,
);
const notebook: Notebook = await response.json();
this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook, showProgressBar: false });
if (this.props.galleryItem && !SessionStorageUtility.getEntry(this.props.galleryItem.id)) {
const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id);
if (!response.data) {
throw new Error(`Received HTTP ${response.status} while increasing notebook views`);
}
this.setState({ galleryItem: response.data });
SessionStorageUtility.setEntry(this.props.galleryItem?.id, "true");
}
} catch (error) {
traceFailure(
Action.NotebooksGalleryViewNotebook,
{
notebookUrl: this.props.notebookUrl,
notebookId: this.props.galleryItem?.id,
isSample: this.props.galleryItem?.isSample,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey,
);
this.setState({ showProgressBar: false });
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
}
}
public render(): JSX.Element {
return (
<div className="notebookViewerContainer">
{this.props.backNavigationText !== undefined ? (
<Link onClick={this.props.onBackClick}>
<Icon iconName="Back" /> {this.props.backNavigationText}
</Link>
) : (
<></>
)}
{this.state.galleryItem ? (
<div style={{ margin: 10 }}>
<NotebookMetadataComponent
data={this.state.galleryItem}
isFavorite={this.state.isFavorite}
downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`}
onTagClick={this.props.onTagClick}
/>
</div>
) : (
<></>
)}
{this.state.showProgressBar && <ProgressIndicator />}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs,
hidePrompts: this.props.hidePrompts,
})}
</div>
);
}
public static getDerivedStateFromProps(
props: NotebookViewerComponentProps,
state: NotebookViewerComponentState,
): Partial<NotebookViewerComponentState> {
let galleryItem = props.galleryItem;
let isFavorite = props.isFavorite;
if (state.galleryItem !== undefined) {
galleryItem = state.galleryItem;
}
if (state.isFavorite !== undefined) {
isFavorite = state.isFavorite;
}
return {
galleryItem,
isFavorite,
};
}
}
@@ -1,197 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 30,
}
}
verticalAlign="center"
>
<StackItem>
<Text
nowrap={true}
variant="xxLarge"
>
name
</Text>
</StackItem>
<StackItem>
<Text>
<Icon
iconName="Heart"
/>
0
likes
</Text>
</StackItem>
</Stack>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 10,
}
}
verticalAlign="center"
>
<StyledPersonaBase
imageUrl={false}
size={11}
text="author"
/>
<Text>
Invalid Date
</Text>
<Text>
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<Icon
iconName="Download"
/>
0
</Text>
</Stack>
<Text
nowrap={true}
>
<span
key="tag"
>
<StyledLinkBase
onClick={[Function]}
>
tag
</StyledLinkBase>
</span>
</Text>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
variant="large"
>
Description
</Text>
<Text>
description
</Text>
</Stack>
`;
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 30,
}
}
verticalAlign="center"
>
<StackItem>
<Text
nowrap={true}
variant="xxLarge"
>
name
</Text>
</StackItem>
<StackItem>
<Text>
<Icon
iconName="Heart"
/>
0
likes
</Text>
</StackItem>
</Stack>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 10,
}
}
verticalAlign="center"
>
<StyledPersonaBase
imageUrl={false}
size={11}
text="author"
/>
<Text>
Invalid Date
</Text>
<Text>
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<Icon
iconName="Download"
/>
0
</Text>
</Stack>
<Text
nowrap={true}
>
<span
key="tag"
>
<StyledLinkBase
onClick={[Function]}
>
tag
</StyledLinkBase>
</span>
</Text>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
variant="large"
>
Description
</Text>
<Text>
description
</Text>
</Stack>
`;
@@ -130,7 +130,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
},
@@ -252,7 +251,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
},
@@ -479,7 +477,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
},
@@ -551,7 +548,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
}
@@ -720,7 +716,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
},
@@ -792,7 +787,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
}