mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-08-10 16:47:13 +01:00
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:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
-197
@@ -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],
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user