mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 15:06:55 +00:00
Added support for notebook viewer link injection (#124)
* Added support for notebook viewer link injection * updated tests
This commit is contained in:
parent
455a6ac81b
commit
95f1efc03f
@ -116,6 +116,7 @@ export class Features {
|
||||
public static readonly enableTtl = "enablettl";
|
||||
public static readonly enableNotebooks = "enablenotebooks";
|
||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||
public static readonly enableLinkInjection = "enablelinkinjection";
|
||||
public static readonly enableSpark = "enablespark";
|
||||
public static readonly livyEndpoint = "livyendpoint";
|
||||
public static readonly notebookServerUrl = "notebookserverurl";
|
||||
|
@ -49,6 +49,11 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
||||
{
|
||||
key: "feature.enableLinkInjection",
|
||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||
value: "true"
|
||||
},
|
||||
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
||||
{
|
||||
key: "feature.enablefixedcollectionwithsharedthroughput",
|
||||
|
@ -163,8 +163,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.canexceedmaximumvalue"
|
||||
label="Can exceed max value"
|
||||
key="feature.enableLinkInjection"
|
||||
label="Enable Injecting Notebook Viewer Link into the first cell"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
@ -172,6 +172,12 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
className="checkboxRow"
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.canexceedmaximumvalue"
|
||||
label="Can exceed max value"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablefixedcollectionwithsharedthroughput"
|
||||
|
@ -17,7 +17,8 @@ describe("GalleryCardComponent", () => {
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
},
|
||||
isFavorite: false,
|
||||
showDownload: true,
|
||||
|
@ -17,7 +17,8 @@ describe("NotebookMetadataComponent", () => {
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
},
|
||||
isFavorite: false,
|
||||
downloadButtonText: "Download",
|
||||
@ -45,7 +46,8 @@ describe("NotebookMetadataComponent", () => {
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
},
|
||||
isFavorite: true,
|
||||
downloadButtonText: "Download",
|
||||
|
@ -19,6 +19,7 @@ import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComp
|
||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import "./NotebookViewerComponent.less";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||
|
||||
export interface NotebookViewerComponentProps {
|
||||
@ -86,6 +87,7 @@ export class NotebookViewerComponent extends React.Component<
|
||||
}
|
||||
|
||||
const notebook: Notebook = await response.json();
|
||||
this.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
|
||||
this.notebookComponentBootstrapper.setContent("json", notebook);
|
||||
this.setState({ content: notebook, showProgressBar: false });
|
||||
|
||||
@ -105,10 +107,21 @@ export class NotebookViewerComponent extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
private removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
|
||||
if (!newCellId) {
|
||||
return;
|
||||
}
|
||||
const notebookV4 = notebook as NotebookV4;
|
||||
if (notebookV4 && notebookV4.cells[0].source[0].search(newCellId)) {
|
||||
delete notebookV4.cells[0];
|
||||
notebook = notebookV4;
|
||||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="notebookViewerContainer">
|
||||
{this.props.backNavigationText ? (
|
||||
{this.props.backNavigationText !== undefined ? (
|
||||
<Link onClick={this.props.onBackClick}>
|
||||
<Icon iconName="Back" /> {this.props.backNavigationText}
|
||||
</Link>
|
||||
|
@ -206,6 +206,7 @@ export default class Explorer {
|
||||
|
||||
// features
|
||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
@ -408,6 +409,9 @@ export default class Explorer {
|
||||
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
||||
);
|
||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||
);
|
||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
|
||||
@ -2349,7 +2353,7 @@ export default class Explorer {
|
||||
|
||||
public publishNotebook(name: string, content: string | unknown, parentDomElement: HTMLElement): void {
|
||||
if (this.notebookManager) {
|
||||
this.notebookManager.openPublishNotebookPane(name, content, parentDomElement);
|
||||
this.notebookManager.openPublishNotebookPane(name, content, parentDomElement, this.isLinkInjectionEnabled());
|
||||
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
||||
this.isPublishNotebookPaneEnabled(true);
|
||||
}
|
||||
|
@ -111,9 +111,10 @@ export default class NotebookManager {
|
||||
public openPublishNotebookPane(
|
||||
name: string,
|
||||
content: string | ImmutableNotebook,
|
||||
parentDomElement: HTMLElement
|
||||
parentDomElement: HTMLElement,
|
||||
isLinkInjectionEnabled: boolean
|
||||
): void {
|
||||
this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement);
|
||||
this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement, isLinkInjectionEnabled);
|
||||
}
|
||||
|
||||
// Octokit's error handler uses any
|
||||
|
@ -26,6 +26,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
private imageSrc: string;
|
||||
private notebookObject: ImmutableNotebook;
|
||||
private parentDomElement: HTMLElement;
|
||||
private isLinkInjectionEnabled: boolean;
|
||||
|
||||
constructor(private container: Explorer, private junoClient: JunoClient) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
@ -62,19 +63,21 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
name: string,
|
||||
author: string,
|
||||
notebookContent: string | ImmutableNotebook,
|
||||
parentDomElement: HTMLElement
|
||||
parentDomElement: HTMLElement,
|
||||
isLinkInjectionEnabled: boolean
|
||||
): void {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
if (typeof notebookContent === "string") {
|
||||
this.content = notebookContent as string;
|
||||
} else {
|
||||
this.content = JSON.stringify(toJS(notebookContent as ImmutableNotebook));
|
||||
this.content = JSON.stringify(toJS(notebookContent));
|
||||
this.notebookObject = notebookContent;
|
||||
}
|
||||
this.parentDomElement = parentDomElement;
|
||||
|
||||
this.isOpened = true;
|
||||
this.isLinkInjectionEnabled = isLinkInjectionEnabled;
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
@ -102,7 +105,8 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
this.tags?.split(","),
|
||||
this.author,
|
||||
this.imageSrc,
|
||||
this.content
|
||||
this.content,
|
||||
this.isLinkInjectionEnabled
|
||||
);
|
||||
if (!response.data) {
|
||||
throw new Error(`Received HTTP ${response.status} when publishing ${name} to gallery`);
|
||||
|
@ -276,7 +276,8 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
}}
|
||||
isFavorite={false}
|
||||
showDownload={true}
|
||||
|
@ -93,6 +93,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
||||
"id": undefined,
|
||||
"isSample": false,
|
||||
"name": "SampleNotebook.ipynb",
|
||||
"newCellId": undefined,
|
||||
"tags": Array [
|
||||
"",
|
||||
],
|
||||
|
@ -47,7 +47,8 @@ const sampleGalleryItems: IGalleryItem[] = [
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
}
|
||||
];
|
||||
|
||||
@ -185,7 +186,7 @@ describe("Gallery", () => {
|
||||
json: () => undefined as any
|
||||
});
|
||||
|
||||
const response = await junoClient.getNotebook(id);
|
||||
const response = await junoClient.getNotebookInfo(id);
|
||||
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}`);
|
||||
@ -353,7 +354,7 @@ describe("Gallery", () => {
|
||||
json: () => undefined as any
|
||||
});
|
||||
|
||||
const response = await junoClient.publishNotebook(name, description, tags, author, thumbnailUrl, content);
|
||||
const response = await junoClient.publishNotebook(name, description, tags, author, thumbnailUrl, content, false);
|
||||
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
|
@ -36,6 +36,7 @@ export interface IGalleryItem {
|
||||
downloads: number;
|
||||
favorites: number;
|
||||
views: number;
|
||||
newCellId: string;
|
||||
}
|
||||
|
||||
export interface IUserGallery {
|
||||
@ -162,7 +163,7 @@ export class JunoClient {
|
||||
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
|
||||
}
|
||||
|
||||
public async getNotebook(id: string): Promise<IJunoResponse<IGalleryItem>> {
|
||||
public async getNotebookInfo(id: string): Promise<IJunoResponse<IGalleryItem>> {
|
||||
const response = await window.fetch(this.getNotebookInfoUrl(id));
|
||||
|
||||
let data: IGalleryItem;
|
||||
@ -292,19 +293,31 @@ export class JunoClient {
|
||||
tags: string[],
|
||||
author: string,
|
||||
thumbnailUrl: string,
|
||||
content: string
|
||||
content: string,
|
||||
isLinkInjectionEnabled: boolean
|
||||
): Promise<IJunoResponse<IGalleryItem>> {
|
||||
const response = await window.fetch(`${this.getNotebooksAccountUrl()}/gallery`, {
|
||||
method: "PUT",
|
||||
headers: JunoClient.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
author,
|
||||
thumbnailUrl,
|
||||
content: JSON.parse(content)
|
||||
} as IPublishNotebookRequest)
|
||||
|
||||
body: isLinkInjectionEnabled
|
||||
? JSON.stringify({
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
author,
|
||||
thumbnailUrl,
|
||||
content: JSON.parse(content),
|
||||
addLinkToNotebookViewer: isLinkInjectionEnabled
|
||||
} as IPublishNotebookRequest)
|
||||
: JSON.stringify({
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
author,
|
||||
thumbnailUrl,
|
||||
content: JSON.parse(content)
|
||||
} as IPublishNotebookRequest)
|
||||
});
|
||||
|
||||
let data: IGalleryItem;
|
||||
|
@ -11,34 +11,48 @@ import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||
import * as GalleryUtils from "../Utils/GalleryUtils";
|
||||
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
|
||||
import { FileSystemUtil } from "../Explorer/Notebook/FileSystemUtil";
|
||||
import { config } from "../Config";
|
||||
|
||||
const onInit = async () => {
|
||||
initializeIcons();
|
||||
await initializeConfiguration();
|
||||
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
|
||||
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
|
||||
const backNavigationText = galleryViewerProps.selectedTab && GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
|
||||
let backNavigationText: string;
|
||||
let onBackClick: () => void;
|
||||
if (galleryViewerProps.selectedTab !== undefined) {
|
||||
backNavigationText = GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
|
||||
onBackClick = () => (window.location.href = `${config.hostedExplorerURL}gallery.html`);
|
||||
}
|
||||
const hideInputs = notebookViewerProps.hideInputs;
|
||||
|
||||
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
|
||||
render(notebookUrl, backNavigationText, hideInputs);
|
||||
|
||||
const galleryItemId = notebookViewerProps.galleryItemId;
|
||||
let galleryItem: IGalleryItem;
|
||||
|
||||
if (galleryItemId) {
|
||||
const junoClient = new JunoClient();
|
||||
const notebook = await junoClient.getNotebook(galleryItemId);
|
||||
render(notebookUrl, backNavigationText, hideInputs, notebook.data);
|
||||
const galleryItemJunoResponse = await junoClient.getNotebookInfo(galleryItemId);
|
||||
galleryItem = galleryItemJunoResponse.data;
|
||||
}
|
||||
render(notebookUrl, backNavigationText, hideInputs, galleryItem, onBackClick);
|
||||
};
|
||||
|
||||
const render = (notebookUrl: string, backNavigationText: string, hideInputs: boolean, galleryItem?: IGalleryItem) => {
|
||||
const render = (
|
||||
notebookUrl: string,
|
||||
backNavigationText: string,
|
||||
hideInputs: boolean,
|
||||
galleryItem?: IGalleryItem,
|
||||
onBackClick?: () => void
|
||||
) => {
|
||||
const props: NotebookViewerComponentProps = {
|
||||
junoClient: galleryItem ? new JunoClient() : undefined,
|
||||
notebookUrl,
|
||||
galleryItem,
|
||||
backNavigationText,
|
||||
hideInputs,
|
||||
onBackClick: undefined,
|
||||
onBackClick: onBackClick,
|
||||
onTagClick: undefined
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,8 @@ const galleryItem: IGalleryItem = {
|
||||
isSample: false,
|
||||
downloads: 0,
|
||||
favorites: 0,
|
||||
views: 0
|
||||
views: 0,
|
||||
newCellId: undefined
|
||||
};
|
||||
|
||||
describe("GalleryUtils", () => {
|
||||
|
Loading…
Reference in New Issue
Block a user