From 59f3223f919fc8b2fed3ff96e1ad11b091ddf768 Mon Sep 17 00:00:00 2001 From: Jade Welton Date: Fri, 1 May 2026 14:32:33 -0700 Subject: [PATCH] Remove gallery.html and all associated gallery functionality Remove the standalone gallery.html entry point, the in-app gallery tab, the publish-to-gallery pane, and all gallery-related components, utilities, and API methods that are no longer needed. Deleted: - src/GalleryViewer/ (standalone entry point) - src/Explorer/Controls/NotebookGallery/ (gallery components) - src/Explorer/Controls/Header/GalleryHeaderComponent.tsx - src/Explorer/Tabs/GalleryTab.tsx - src/Explorer/Panes/PublishNotebookPane/ (publish pane) - src/Utils/GalleryUtils.ts and tests - images/GalleryIcon.svg Edited: - webpack.config.js (removed entry point and HTML plugin) - Explorer.tsx (removed openGallery, publishNotebook methods) - ResourceTreeAdapter.tsx (removed gallery tree node and publish menu) - NotebookV2Tab.ts (removed publish-to-gallery button) - NotebookManager.tsx (removed openPublishNotebookPane) - NotebookViewerComponent.tsx (stripped gallery actions) - JunoClient.ts (removed gallery interfaces and API methods) - TelemetryConstants.ts, Constants.ts, ViewModels.ts, extractFeatures.ts, useKnockoutExplorer.ts (removed gallery constants) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- images/GalleryIcon.svg | 10 - src/Common/Constants.ts | 7 - src/Contracts/ViewModels.ts | 2 +- .../Header/GalleryHeaderComponent.tsx | 81 -- .../Cards/GalleryCardComponent.test.tsx | 39 - .../Cards/GalleryCardComponent.tsx | 205 ----- .../GalleryCardComponent.test.tsx.snap | 256 ------ .../CodeOfConduct/CodeOfConduct.test.tsx | 34 - .../CodeOfConduct/CodeOfConduct.tsx | 110 --- .../__snapshots__/CodeOfConduct.test.tsx.snap | 68 -- .../GalleryAndNotebookViewerComponent.tsx | 114 --- .../GalleryViewerComponent.less | 21 - .../GalleryViewerComponent.test.tsx | 21 - .../GalleryViewerComponent.tsx | 760 ------------------ .../InfoComponent/InfoComponent.less | 26 - .../InfoComponent/InfoComponent.test.tsx | 10 - .../InfoComponent/InfoComponent.tsx | 51 -- .../__snapshots__/InfoComponent.test.tsx.snap | 35 - .../GalleryViewerComponent.test.tsx.snap | 98 --- .../NotebookMetadataComponent.test.tsx | 8 - .../NotebookMetadataComponent.tsx | 43 +- .../NotebookViewerComponent.tsx | 96 +-- .../NotebookMetadataComponent.test.tsx.snap | 40 +- src/Explorer/Explorer.tsx | 61 -- src/Explorer/Notebook/NotebookManager.tsx | 28 - .../PublishNotebookPane.test.tsx | 28 - .../PublishNotebookPane.tsx | 214 ----- .../PublishNotebookPaneComponent.tsx | 264 ------ .../PublishNotebookPane.test.tsx.snap | 116 --- .../Panes/PublishNotebookPane/styled.less | 6 - src/Explorer/Tabs/GalleryTab.tsx | 36 - src/Explorer/Tabs/NotebookV2Tab.ts | 52 +- src/Explorer/Tree/ResourceTreeAdapter.tsx | 24 +- src/GalleryViewer/GalleryViewer.less | 5 - src/GalleryViewer/GalleryViewer.tsx | 65 -- src/GalleryViewer/galleryViewer.html | 13 - src/Juno/JunoClient.test.ts | 299 +------ src/Juno/JunoClient.ts | 253 ------ src/Platform/Hosted/extractFeatures.ts | 1 - src/Shared/Telemetry/TelemetryConstants.ts | 21 - src/Utils/GalleryUtils.test.ts | 113 --- src/Utils/GalleryUtils.ts | 528 ------------ src/hooks/useKnockoutExplorer.ts | 3 - webpack.config.js | 6 - 44 files changed, 21 insertions(+), 4250 deletions(-) delete mode 100644 images/GalleryIcon.svg delete mode 100644 src/Explorer/Controls/Header/GalleryHeaderComponent.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap delete mode 100644 src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/CodeOfConduct/__snapshots__/CodeOfConduct.test.tsx.snap delete mode 100644 src/Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less delete mode 100644 src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.test.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.less delete mode 100644 src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.test.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx delete mode 100644 src/Explorer/Controls/NotebookGallery/InfoComponent/__snapshots__/InfoComponent.test.tsx.snap delete mode 100644 src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap delete mode 100644 src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.test.tsx delete mode 100644 src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx delete mode 100644 src/Explorer/Panes/PublishNotebookPane/PublishNotebookPaneComponent.tsx delete mode 100644 src/Explorer/Panes/PublishNotebookPane/__snapshots__/PublishNotebookPane.test.tsx.snap delete mode 100644 src/Explorer/Panes/PublishNotebookPane/styled.less delete mode 100644 src/Explorer/Tabs/GalleryTab.tsx delete mode 100644 src/GalleryViewer/GalleryViewer.less delete mode 100644 src/GalleryViewer/GalleryViewer.tsx delete mode 100644 src/GalleryViewer/galleryViewer.html delete mode 100644 src/Utils/GalleryUtils.test.ts delete mode 100644 src/Utils/GalleryUtils.ts diff --git a/images/GalleryIcon.svg b/images/GalleryIcon.svg deleted file mode 100644 index bf49a651d..000000000 --- a/images/GalleryIcon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 4ee38c6a0..eb1cb44c8 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -1,9 +1,3 @@ -export class CodeOfConductEndpoints { - public static privacyStatement: string = "https://aka.ms/ms-privacy-policy"; - public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct"; - public static termsOfUse: string = "https://aka.ms/ms-terms-of-use"; -} - export class EndpointsRegex { public static readonly cassandra = [ "AccountEndpoint=(.*).cassandra.cosmosdb.azure.com", @@ -118,7 +112,6 @@ export class Flights { public static readonly PhoenixNotebooks = "phoenixnotebooks"; public static readonly PhoenixFeatures = "phoenixfeatures"; public static readonly NotebooksDownBanner = "notebooksdownbanner"; - public static readonly PublicGallery = "publicgallery"; } export class AfecFeatures { diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index a84afe9c2..cfeaf1b95 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -393,7 +393,7 @@ export enum CollectionTabKind { Terminal = 14, NotebookV2 = 15, SparkMasterTab = 16 /* Deprecated */, - Gallery = 17, + Gallery = 17 /* Deprecated */, NotebookViewer = 18, Schema = 19, CollectionSettingsV2 = 20, diff --git a/src/Explorer/Controls/Header/GalleryHeaderComponent.tsx b/src/Explorer/Controls/Header/GalleryHeaderComponent.tsx deleted file mode 100644 index 6d3143169..000000000 --- a/src/Explorer/Controls/Header/GalleryHeaderComponent.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { CommandButton, FontIcon, FontWeights, ITextProps, Separator, Stack, Text } from "@fluentui/react"; -import * as React from "react"; - -export class GalleryHeaderComponent extends React.Component { - private static readonly azureText = "Microsoft Azure"; - private static readonly cosmosdbText = "Cosmos DB"; - private static readonly galleryText = "Gallery"; - private static readonly loginText = "Sign In"; - private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank"); - private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href); - private static readonly headerItemStyle: React.CSSProperties = { - color: "white", - }; - private static readonly mainHeaderTextProps: ITextProps = { - style: GalleryHeaderComponent.headerItemStyle, - variant: "mediumPlus", - styles: { - root: { - fontWeight: FontWeights.semibold, - }, - }, - }; - private static readonly headerItemTextProps: ITextProps = { style: GalleryHeaderComponent.headerItemStyle }; - - private renderHeaderItem = (text: string, onClick: () => void, textProps: ITextProps): JSX.Element => { - return ( - - {text} - - ); - }; - - public render(): JSX.Element { - return ( - - - {this.renderHeaderItem( - GalleryHeaderComponent.azureText, - GalleryHeaderComponent.openPortal, - GalleryHeaderComponent.mainHeaderTextProps, - )} - - - - - - {this.renderHeaderItem( - GalleryHeaderComponent.cosmosdbText, - GalleryHeaderComponent.openDataExplorer, - GalleryHeaderComponent.headerItemTextProps, - )} - - - - - - {this.renderHeaderItem( - GalleryHeaderComponent.galleryText, - () => "", - GalleryHeaderComponent.headerItemTextProps, - )} - - - <> - - - {this.renderHeaderItem( - GalleryHeaderComponent.loginText, - GalleryHeaderComponent.openDataExplorer, - GalleryHeaderComponent.headerItemTextProps, - )} - - - ); - } -} diff --git a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx deleted file mode 100644 index d96fda04b..000000000 --- a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { shallow } from "enzyme"; -import React from "react"; -import { GalleryCardComponent, GalleryCardComponentProps } from "./GalleryCardComponent"; - -describe("GalleryCardComponent", () => { - it("renders", () => { - const props: GalleryCardComponentProps = { - 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, - showDownload: true, - showDelete: true, - onClick: undefined, - onTagClick: undefined, - onFavoriteClick: undefined, - onUnfavoriteClick: undefined, - onDownloadClick: undefined, - onDeleteClick: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx deleted file mode 100644 index fc3a70d91..000000000 --- a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { - BaseButton, - Button, - DocumentCard, - DocumentCardActivity, - DocumentCardDetails, - DocumentCardPreview, - DocumentCardTitle, - Icon, - IconButton, - IDocumentCardPreviewProps, - IDocumentCardStyles, - ImageFit, - Link, - Separator, - Spinner, - SpinnerSize, - Text, - TooltipHost, -} from "@fluentui/react"; -import React, { FunctionComponent, useState } from "react"; -import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg"; -import { IGalleryItem } from "../../../../Juno/JunoClient"; -import * as FileSystemUtil from "../../../Notebook/FileSystemUtil"; - -export interface GalleryCardComponentProps { - data: IGalleryItem; - isFavorite: boolean; - showDownload: boolean; - showDelete: boolean; - onClick: () => void; - onTagClick: (tag: string) => void; - onFavoriteClick: () => void; - onUnfavoriteClick: () => void; - onDownloadClick: () => void; - onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void; -} - -export const GalleryCardComponent: FunctionComponent = ({ - data, - isFavorite, - showDownload, - showDelete, - onClick, - onTagClick, - onFavoriteClick, - onUnfavoriteClick, - onDownloadClick, - onDeleteClick, -}: GalleryCardComponentProps) => { - const CARD_WIDTH = 256; - const cardImageHeight = 144; - const cardDescriptionMaxChars = 80; - const cardItemGapSmall = 8; - const cardDeleteSpinnerHeight = 360; - const smallTextLineHeight = 18; - - const [isDeletingPublishedNotebook, setIsDeletingPublishedNotebook] = useState(false); - - const cardButtonsVisible = isFavorite !== undefined || showDownload || showDelete; - const options: Intl.DateTimeFormatOptions = { - year: "numeric", - month: "short", - day: "numeric", - }; - const dateString = new Date(data.created).toLocaleString("default", options); - const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb"); - - const renderTruncated = (text: string, totalLength: number): string => { - let truncatedDescription = text.substr(0, totalLength); - if (text.length > totalLength) { - truncatedDescription = `${truncatedDescription} ...`; - } - return truncatedDescription; - }; - - const generateIconText = (iconName: string, text: string): JSX.Element => { - return ( - - {text} - - ); - }; - - /* - * Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is - * to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button) - */ - const generateIconButtonWithTooltip = ( - iconName: string, - title: string, - horizontalAlign: "right" | "left", - activate: () => void, - ): JSX.Element => { - return ( - - handlerOnClick(event, activate)} - /> - - ); - }; - - const handlerOnClick = ( - event: - | React.MouseEvent - | React.MouseEvent< - HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement, - MouseEvent - >, - activate: () => void, - ): void => { - event.stopPropagation(); - event.preventDefault(); - activate(); - }; - const DocumentCardActivityPeople = [{ name: data.author, profileImageSrc: data.isSample && CosmosDBLogo }]; - const previewProps: IDocumentCardPreviewProps = { - previewImages: [ - { - previewImageSrc: data.thumbnailUrl, - imageFit: ImageFit.cover, - width: CARD_WIDTH, - height: cardImageHeight, - }, - ], - }; - const cardStyles: IDocumentCardStyles = { - root: { display: "inline-block", marginRight: 20, width: CARD_WIDTH }, - }; - return ( - - {isDeletingPublishedNotebook && ( - - )} - {!isDeletingPublishedNotebook && ( - <> - - - - - {data.tags ? ( - data.tags.map((tag, index, array) => ( - - handlerOnClick(event, () => onTagClick(tag))}>{tag} - {index === array.length - 1 ? <> : ", "} - - )) - ) : ( -
- )} -
- - - - {data.views !== undefined && generateIconText("RedEye", data.views.toString())} - {data.downloads !== undefined && generateIconText("Download", data.downloads.toString())} - {data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())} - -
- {cardButtonsVisible && ( - - - - - {isFavorite !== undefined && - generateIconButtonWithTooltip( - isFavorite ? "HeartFill" : "Heart", - isFavorite ? "Unfavorite" : "Favorite", - "left", - isFavorite ? onUnfavoriteClick : onFavoriteClick, - )} - - {showDownload && generateIconButtonWithTooltip("Download", "Download", "left", onDownloadClick)} - - {showDelete && - generateIconButtonWithTooltip("Delete", "Remove", "right", () => - onDeleteClick( - () => setIsDeletingPublishedNotebook(true), - () => setIsDeletingPublishedNotebook(false), - ), - )} - - - )} - - )} -
- ); -}; diff --git a/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap deleted file mode 100644 index ed4aaa8f1..000000000 --- a/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap +++ /dev/null @@ -1,256 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GalleryCardComponent renders 1`] = ` - - - - - - - - tag - - - - - - - - - - 0 - - - - - 0 - - - - - 0 - - - - - - - - - - - - - - - - - - -`; diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx deleted file mode 100644 index 0dd05974d..000000000 --- a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -jest.mock("../../../../Juno/JunoClient"); -import { shallow } from "enzyme"; -import React from "react"; -import { HttpStatusCodes } from "../../../../Common/Constants"; -import { JunoClient } from "../../../../Juno/JunoClient"; -import { CodeOfConduct, CodeOfConductProps } from "./CodeOfConduct"; - -describe("CodeOfConduct", () => { - let codeOfConductProps: CodeOfConductProps; - - beforeEach(() => { - const junoClient = new JunoClient(); - junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - data: true, - }); - codeOfConductProps = { - junoClient: junoClient, - onAcceptCodeOfConduct: jest.fn(), - }; - }); - - it("renders", () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it("onAcceptedCodeOfConductCalled", async () => { - const wrapper = shallow(); - wrapper.find(".genericPaneSubmitBtn").first().simulate("click"); - await Promise.resolve(); - expect(codeOfConductProps.onAcceptCodeOfConduct).toHaveBeenCalled(); - }); -}); diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx deleted file mode 100644 index 3c6a8c939..000000000 --- a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/CodeOfConduct.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Checkbox, Link, PrimaryButton, Stack, Text } from "@fluentui/react"; -import React, { FunctionComponent, useEffect, useState } from "react"; -import { CodeOfConductEndpoints, HttpStatusCodes } from "../../../../Common/Constants"; -import { getErrorMessage, getErrorStack, handleError } from "../../../../Common/ErrorHandlingUtils"; -import { JunoClient } from "../../../../Juno/JunoClient"; -import { Action } from "../../../../Shared/Telemetry/TelemetryConstants"; -import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor"; - -export interface CodeOfConductProps { - junoClient: JunoClient; - onAcceptCodeOfConduct: (result: boolean) => void; -} - -export const CodeOfConduct: FunctionComponent = ({ - junoClient, - onAcceptCodeOfConduct, -}: CodeOfConductProps) => { - const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct"; - const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB."; - const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the "; - const link1: { label: string; url: string } = { - label: "code of conduct.", - url: CodeOfConductEndpoints.codeOfConduct, - }; - - const [readCodeOfConduct, setReadCodeOfConduct] = useState(false); - - const acceptCodeOfConduct = async (): Promise => { - const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct); - - try { - const response = await junoClient.acceptCodeOfConduct(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when accepting code of conduct`); - } - - traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey); - - onAcceptCodeOfConduct(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryAcceptCodeOfConduct, - { - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - handleError(error, "CodeOfConduct/acceptCodeOfConduct", "Failed to accept code of conduct"); - } - }; - - const onChangeCheckbox = (): void => { - setReadCodeOfConduct(!readCodeOfConduct); - }; - - useEffect(() => { - trace(Action.NotebooksGalleryViewCodeOfConduct); - }, []); - - return ( - - - {descriptionPara1} - - - - {descriptionPara2} - - - - - {descriptionPara3} - - {link1.label} - - - - - - - - - - await acceptCodeOfConduct()} - tabIndex={0} - className="genericPaneSubmitBtn" - text="Continue" - disabled={!readCodeOfConduct} - /> - - - ); -}; diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/__snapshots__/CodeOfConduct.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/CodeOfConduct/__snapshots__/CodeOfConduct.test.tsx.snap deleted file mode 100644 index 871aac4cc..000000000 --- a/src/Explorer/Controls/NotebookGallery/CodeOfConduct/__snapshots__/CodeOfConduct.test.tsx.snap +++ /dev/null @@ -1,68 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CodeOfConduct renders 1`] = ` - - - - Azure Cosmos DB Notebook Gallery - Code of Conduct - - - - - The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB. - - - - - In order to view and publish your samples to the gallery, you must accept the - - code of conduct. - - - - - - - - - - -`; diff --git a/src/Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent.tsx deleted file mode 100644 index 3e2b5f605..000000000 --- a/src/Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from "react"; -import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient"; -import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent"; -import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent"; -import * as GalleryUtils from "../../../Utils/GalleryUtils"; -import Explorer from "../../Explorer"; - -export interface GalleryAndNotebookViewerComponentProps { - container?: Explorer; - junoClient: JunoClient; - notebookUrl?: string; - galleryItem?: IGalleryItem; - isFavorite?: boolean; - selectedTab: GalleryTab; - sortBy: SortBy; - searchText: string; -} - -interface GalleryAndNotebookViewerComponentState { - notebookUrl: string; - galleryItem: IGalleryItem; - isFavorite: boolean; - selectedTab: GalleryTab; - sortBy: SortBy; - searchText: string; -} - -export class GalleryAndNotebookViewerComponent extends React.Component< - GalleryAndNotebookViewerComponentProps, - GalleryAndNotebookViewerComponentState -> { - constructor(props: GalleryAndNotebookViewerComponentProps) { - super(props); - - this.state = { - notebookUrl: props.notebookUrl, - galleryItem: props.galleryItem, - isFavorite: props.isFavorite, - selectedTab: props.selectedTab, - sortBy: props.sortBy, - searchText: props.searchText, - }; - } - - public render(): JSX.Element { - if (this.state.notebookUrl) { - const props: NotebookViewerComponentProps = { - container: this.props.container, - junoClient: this.props.junoClient, - notebookUrl: this.state.notebookUrl, - galleryItem: this.state.galleryItem, - isFavorite: this.state.isFavorite, - backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab), - onBackClick: this.onBackClick, - onTagClick: this.loadTaggedItems, - }; - - return ; - } - - const props: GalleryViewerComponentProps = { - container: this.props.container, - junoClient: this.props.junoClient, - selectedTab: this.state.selectedTab, - sortBy: this.state.sortBy, - searchText: this.state.searchText, - openNotebook: this.openNotebook, - onSelectedTabChange: this.onSelectedTabChange, - onSortByChange: this.onSortByChange, - onSearchTextChange: this.onSearchTextChange, - }; - - return ; - } - - private onBackClick = (): void => { - this.setState({ - notebookUrl: undefined, - }); - }; - - private loadTaggedItems = (tag: string): void => { - this.setState({ - notebookUrl: undefined, - searchText: tag, - }); - }; - - private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => { - this.setState({ - notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id), - galleryItem: data, - isFavorite, - }); - }; - - private onSelectedTabChange = (selectedTab: GalleryTab): void => { - this.setState({ - selectedTab, - }); - }; - - private onSortByChange = (sortBy: SortBy): void => { - this.setState({ - sortBy, - }); - }; - - private onSearchTextChange = (searchText: string): void => { - this.setState({ - searchText, - }); - }; -} diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less deleted file mode 100644 index 53151cc28..000000000 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.less +++ /dev/null @@ -1,21 +0,0 @@ -@import "../../../../less/Common/Constants"; - -.galleryContainer { - padding: @LargeSpace @LargeSpace 30px @LargeSpace; - height: 100%; - overflow-y: auto; - width: 100%; - font-family: @DataExplorerFont; - background: @GalleryBackgroundColor; -} - -.publicGalleryTabContainer { - position: relative; - min-height: 100vh; -} - -.publicGalleryTabOverlayContent { - background: white; - padding: 20px; - margin: 10%; -} diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.test.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.test.tsx deleted file mode 100644 index d5e2adecf..000000000 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { shallow } from "enzyme"; -import React from "react"; -import { GalleryViewerComponent, GalleryViewerComponentProps, GalleryTab, SortBy } from "./GalleryViewerComponent"; - -describe("GalleryViewerComponent", () => { - it("renders", () => { - const props: GalleryViewerComponentProps = { - junoClient: undefined, - selectedTab: GalleryTab.OfficialSamples, - sortBy: SortBy.MostViewed, - searchText: undefined, - openNotebook: undefined, - onSelectedTabChange: undefined, - onSortByChange: undefined, - onSearchTextChange: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx deleted file mode 100644 index a43fc6c3b..000000000 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx +++ /dev/null @@ -1,760 +0,0 @@ -import { - Dropdown, - FocusZone, - FontIcon, - FontWeights, - IDropdownOption, - IPageSpecification, - IPivotItemProps, - IPivotProps, - IRectangle, - Label, - Link, - List, - Overlay, - Pivot, - PivotItem, - SearchBox, - Spinner, - SpinnerSize, - Stack, - Text, -} from "@fluentui/react"; -import * as React from "react"; -import { userContext } from "UserContext"; -import { HttpStatusCodes } from "../../../Common/Constants"; -import { handleError } from "../../../Common/ErrorHandlingUtils"; -import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient"; -import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; -import { trace } from "../../../Shared/Telemetry/TelemetryProcessor"; -import * as GalleryUtils from "../../../Utils/GalleryUtils"; -import Explorer from "../../Explorer"; -import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; -import { CodeOfConduct } from "./CodeOfConduct/CodeOfConduct"; -import "./GalleryViewerComponent.less"; -import { InfoComponent } from "./InfoComponent/InfoComponent"; - -export interface GalleryViewerComponentProps { - container?: Explorer; - junoClient: JunoClient; - selectedTab: GalleryTab; - sortBy: SortBy; - searchText: string; - openNotebook: (data: IGalleryItem, isFavorite: boolean) => void; - onSelectedTabChange: (newTab: GalleryTab) => void; - onSortByChange: (sortBy: SortBy) => void; - onSearchTextChange: (searchText: string) => void; -} - -export enum GalleryTab { - PublicGallery, - OfficialSamples, - Favorites, - Published, -} - -export enum SortBy { - MostViewed, - MostDownloaded, - MostFavorited, - MostRecent, -} - -interface GalleryViewerComponentState { - sampleNotebooks: IGalleryItem[]; - publicNotebooks: IGalleryItem[]; - favoriteNotebooks: IGalleryItem[]; - publishedNotebooks: IGalleryItem[]; - selectedTab: GalleryTab; - sortBy: SortBy; - searchText: string; - isCodeOfConductAccepted: boolean; - isFetchingPublishedNotebooks: boolean; - isFetchingFavouriteNotebooks: boolean; -} - -interface GalleryTabInfo { - tab: GalleryTab; - content: JSX.Element; -} - -export class GalleryViewerComponent extends React.Component { - public static readonly OfficialSamplesTitle = "Official samples"; - public static readonly PublicGalleryTitle = "Public gallery"; - public static readonly FavoritesTitle = "My favorites"; - public static readonly PublishedTitle = "My published work"; - - private static readonly rowsPerPage = 5; - private static readonly CARD_WIDTH = 256; - private static readonly mostViewedText = "Most viewed"; - private static readonly mostDownloadedText = "Most downloaded"; - private static readonly mostFavoritedText = "Most favorited"; - private static readonly mostRecentText = "Most recent"; - - private readonly sortingOptions: IDropdownOption[]; - - private viewGalleryTraced: boolean; - private viewOfficialSamplesTraced: boolean; - private viewPublicGalleryTraced: boolean; - private viewFavoritesTraced: boolean; - private viewPublishedNotebooksTraced: boolean; - - private sampleNotebooks: IGalleryItem[]; - private publicNotebooks: IGalleryItem[]; - private favoriteNotebooks: IGalleryItem[]; - private publishedNotebooks: IGalleryItem[]; - private isCodeOfConductAccepted: boolean; - private columnCount: number; - private rowCount: number; - - constructor(props: GalleryViewerComponentProps) { - super(props); - - this.state = { - sampleNotebooks: undefined, - publicNotebooks: undefined, - favoriteNotebooks: undefined, - publishedNotebooks: undefined, - selectedTab: props.selectedTab, - sortBy: props.sortBy, - searchText: props.searchText, - isCodeOfConductAccepted: undefined, - isFetchingFavouriteNotebooks: true, - isFetchingPublishedNotebooks: true, - }; - - this.sortingOptions = [ - { - key: SortBy.MostViewed, - text: GalleryViewerComponent.mostViewedText, - }, - { - key: SortBy.MostDownloaded, - text: GalleryViewerComponent.mostDownloadedText, - }, - { - key: SortBy.MostRecent, - text: GalleryViewerComponent.mostRecentText, - }, - { - key: SortBy.MostFavorited, - text: GalleryViewerComponent.mostFavoritedText, - }, - ]; - - this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false); - this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state - } - - public render(): JSX.Element { - this.traceViewGallery(); - - const tabs: GalleryTabInfo[] = []; - if (userContext.features.publicGallery) { - tabs.push( - this.createPublicGalleryTab( - GalleryTab.PublicGallery, - this.state.publicNotebooks, - this.state.isCodeOfConductAccepted, - ), - ); - } - tabs.push(this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)); - - if (this.props.container) { - tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks)); - if (userContext.features.publicGallery) { - tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks)); - } - } - - const pivotProps: IPivotProps = { - onLinkClick: this.onPivotChange, - selectedKey: GalleryTab[this.state.selectedTab], - }; - - const pivotItems = tabs.map((tab) => { - const pivotItemProps: IPivotItemProps = { - itemKey: GalleryTab[tab.tab], - style: { marginTop: 20 }, - headerText: GalleryUtils.getTabTitle(tab.tab), - }; - - return ( - - {tab.content} - - ); - }); - - return ( -
- {pivotItems} -
- ); - } - - private traceViewGallery = (): void => { - if (!this.viewGalleryTraced) { - this.viewGalleryTraced = true; - trace(Action.NotebooksGalleryViewGallery); - } - - switch (this.state.selectedTab) { - case GalleryTab.PublicGallery: - if (!this.viewPublicGalleryTraced) { - this.resetViewGalleryTabTracedFlags(); - this.viewPublicGalleryTraced = true; - trace(Action.NotebooksGalleryViewPublicGallery); - } - break; - case GalleryTab.OfficialSamples: - if (!this.viewOfficialSamplesTraced) { - this.resetViewGalleryTabTracedFlags(); - this.viewOfficialSamplesTraced = true; - trace(Action.NotebooksGalleryViewOfficialSamples); - } - break; - case GalleryTab.Favorites: - if (!this.viewFavoritesTraced) { - this.resetViewGalleryTabTracedFlags(); - this.viewFavoritesTraced = true; - trace(Action.NotebooksGalleryViewFavorites); - } - break; - case GalleryTab.Published: - if (!this.viewPublishedNotebooksTraced) { - this.resetViewGalleryTabTracedFlags(); - this.viewPublishedNotebooksTraced = true; - trace(Action.NotebooksGalleryViewPublishedNotebooks); - } - break; - default: - throw new Error(`Unknown selected tab ${this.state.selectedTab}`); - } - }; - - private resetViewGalleryTabTracedFlags = (): void => { - this.viewOfficialSamplesTraced = false; - this.viewPublicGalleryTraced = false; - this.viewFavoritesTraced = false; - this.viewPublishedNotebooksTraced = false; - }; - - private isEmptyData = (data: IGalleryItem[]): boolean => { - return !data || data.length === 0; - }; - - private createEmptyTabContent = (iconName: string, line1: JSX.Element, line2: JSX.Element): JSX.Element => { - return ( - - - {line1} - {line2} - - ); - }; - - private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => { - return { - tab, - content: this.createSearchBarHeader(this.createCardsTabContent(data)), - }; - }; - - private createPublicGalleryTab( - tab: GalleryTab, - data: IGalleryItem[], - acceptedCodeOfConduct: boolean, - ): GalleryTabInfo { - return { - tab, - content: this.createPublicGalleryTabContent(data, acceptedCodeOfConduct), - }; - } - - private getFavouriteNotebooksTabContent = (data: IGalleryItem[]) => { - if (this.isEmptyData(data)) { - if (this.state.isFetchingFavouriteNotebooks) { - return ; - } - return this.createEmptyTabContent( - "ContactHeart", - <>You don't have any favorites yet, - <> - Favorite any notebook from the{" "} - this.setState({ selectedTab: GalleryTab.OfficialSamples })}>official samples or{" "} - this.setState({ selectedTab: GalleryTab.PublicGallery })}>public gallery - , - ); - } - return this.createSearchBarHeader(this.createCardsTabContent(data)); - }; - - private createFavoritesTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo { - return { - tab, - content: this.getFavouriteNotebooksTabContent(data), - }; - } - - private getPublishedNotebooksTabContent = (data: IGalleryItem[]) => { - if (this.isEmptyData(data)) { - if (this.state.isFetchingPublishedNotebooks) { - return ; - } - return this.createEmptyTabContent( - "Contact", - <> - You have not published anything to the{" "} - this.setState({ selectedTab: GalleryTab.PublicGallery })}>public gallery yet - , - <>Publish your notebooks to share your work with other users, - ); - } - return this.createPublishedNotebooksTabContent(data); - }; - - private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => { - return { - tab, - content: this.getPublishedNotebooksTabContent(data), - }; - }; - - private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => { - const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data); - const content = ( - - {published?.length > 0 && - this.createPublishedNotebooksSectionContent( - undefined, - "You have successfully published and shared the following notebook(s) to the public gallery.", - this.createCardsTabContent(published), - )} - {underReview?.length > 0 && - this.createPublishedNotebooksSectionContent( - "Under Review", - "Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)", - this.createCardsTabContent(underReview), - )} - {removed?.length > 0 && - this.createPublishedNotebooksSectionContent( - "Removed", - "These notebooks were found to contain illegal content and has been taken down.", - this.createPolicyViolationsListContent(removed), - )} - - ); - - return this.createSearchBarHeader(content); - }; - - private createPublishedNotebooksSectionContent = ( - title: string, - description: string, - content: JSX.Element, - ): JSX.Element => { - return ( - - {title && ( - {title} - )} - {description && {description}} - {content} - - ); - }; - - private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element { - return ( -
- {this.createSearchBarHeader(this.createCardsTabContent(data))} - {acceptedCodeOfConduct === false && ( - -
- { - this.setState({ isCodeOfConductAccepted: result }); - }} - /> -
-
- )} -
- ); - } - - private createSearchBarHeader(content: JSX.Element): JSX.Element { - return ( - - - - - - - - - - - - - - - - {content} - - ); - } - - private createCardsTabContent(data: IGalleryItem[]): JSX.Element { - return data ? ( - - - - ) : ( - - ); - } - - private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element { - return ( - - - - - - - {data.map((item) => ( - - - - - ))} - -
NamePolicy violations
{item.name}{item.policyViolations.join(", ")}
- ); - } - - private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void { - switch (tab) { - case GalleryTab.PublicGallery: - this.loadPublicNotebooks(searchText, sortBy, offline); - break; - - case GalleryTab.OfficialSamples: - this.loadSampleNotebooks(searchText, sortBy, offline); - break; - - case GalleryTab.Favorites: - this.loadFavoriteNotebooks(searchText, sortBy, offline); - break; - - case GalleryTab.Published: - this.loadPublishedNotebooks(searchText, sortBy, offline); - break; - - default: - throw new Error(`Unknown tab ${tab}`); - } - } - - private async loadSampleNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise { - if (!offline) { - try { - const response = await this.props.junoClient.getSampleNotebooks(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when loading sample notebooks`); - } - - this.sampleNotebooks = response.data; - - trace(Action.NotebooksGalleryOfficialSamplesCount, ActionModifiers.Mark, { - count: this.sampleNotebooks?.length, - }); - } catch (error) { - handleError(error, "GalleryViewerComponent/loadSampleNotebooks", "Failed to load sample notebooks"); - } - } - - this.setState({ - sampleNotebooks: this.sampleNotebooks && [...this.sort(sortBy, this.search(searchText, this.sampleNotebooks))], - }); - } - - private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise { - if (!offline) { - try { - let response: IJunoResponse | IJunoResponse; - if (this.props.container) { - response = await this.props.junoClient.getPublicGalleryData(); - this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct; - this.publicNotebooks = response.data?.notebooksData; - } else { - response = await this.props.junoClient.getPublicNotebooks(); - this.publicNotebooks = response.data; - } - - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when loading public notebooks`); - } - - trace(Action.NotebooksGalleryPublicGalleryCount, ActionModifiers.Mark, { count: this.publicNotebooks?.length }); - } catch (error) { - handleError(error, "GalleryViewerComponent/loadPublicNotebooks", "Failed to load public notebooks"); - } - } - - this.setState({ - publicNotebooks: this.publicNotebooks && [...this.sort(sortBy, this.search(searchText, this.publicNotebooks))], - isCodeOfConductAccepted: this.isCodeOfConductAccepted, - }); - } - - private async loadFavoriteNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise { - if (!offline) { - try { - this.setState({ isFetchingFavouriteNotebooks: true }); - const response = await this.props.junoClient.getFavoriteNotebooks(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when loading favorite notebooks`); - } - - this.favoriteNotebooks = response.data; - - trace(Action.NotebooksGalleryFavoritesCount, ActionModifiers.Mark, { count: this.favoriteNotebooks?.length }); - } catch (error) { - handleError(error, "GalleryViewerComponent/loadFavoriteNotebooks", "Failed to load favorite notebooks"); - } finally { - this.setState({ isFetchingFavouriteNotebooks: false }); - } - } - - this.setState({ - favoriteNotebooks: this.favoriteNotebooks && [ - ...this.sort(sortBy, this.search(searchText, this.favoriteNotebooks)), - ], - }); - - // Refresh favorite button state - if (this.state.selectedTab !== GalleryTab.Favorites) { - this.refreshSelectedTab(); - } - } - - private async loadPublishedNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise { - if (!offline) { - try { - this.setState({ isFetchingPublishedNotebooks: true }); - const response = await this.props.junoClient.getPublishedNotebooks(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when loading published notebooks`); - } - - this.publishedNotebooks = response.data; - - const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(this.publishedNotebooks); - trace(Action.NotebooksGalleryPublishedCount, ActionModifiers.Mark, { - count: this.publishedNotebooks?.length, - publishedCount: published.length, - underReviewCount: underReview.length, - removedCount: removed.length, - }); - } catch (error) { - handleError(error, "GalleryViewerComponent/loadPublishedNotebooks", "Failed to load published notebooks"); - } finally { - this.setState({ isFetchingPublishedNotebooks: false }); - } - } - - this.setState({ - publishedNotebooks: this.publishedNotebooks && [ - ...this.sort(sortBy, this.search(searchText, this.publishedNotebooks)), - ], - }); - } - - private search(searchText: string, data: IGalleryItem[]): IGalleryItem[] { - if (searchText) { - return data?.filter((item) => this.isGalleryItemPresent(searchText, item)); - } - - return data; - } - - private isGalleryItemPresent(searchText: string, item: IGalleryItem): boolean { - const toSearch = searchText.trim().toUpperCase(); - const searchData: string[] = [item.author.toUpperCase(), item.description.toUpperCase(), item.name.toUpperCase()]; - - if (item.tags) { - searchData.push(...item.tags.map((tag) => tag.toUpperCase())); - } - - for (const data of searchData) { - if (data?.indexOf(toSearch) !== -1) { - return true; - } - } - return false; - } - - private sort(sortBy: SortBy, data: IGalleryItem[]): IGalleryItem[] { - return data?.sort((a, b) => { - switch (sortBy) { - case SortBy.MostViewed: - return b.views - a.views; - case SortBy.MostDownloaded: - return b.downloads - a.downloads; - case SortBy.MostFavorited: - return b.favorites - a.favorites; - case SortBy.MostRecent: - return Date.parse(b.created) - Date.parse(a.created); - default: - throw new Error(`Unknown sorting condition ${sortBy}`); - } - }); - } - - private refreshSelectedTab(item?: IGalleryItem): void { - if (item) { - this.updateGalleryItem(item); - } - this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, true); - } - - private updateGalleryItem(updatedItem: IGalleryItem): void { - this.replaceGalleryItem(updatedItem, this.sampleNotebooks); - this.replaceGalleryItem(updatedItem, this.publicNotebooks); - this.replaceGalleryItem(updatedItem, this.favoriteNotebooks); - this.replaceGalleryItem(updatedItem, this.publishedNotebooks); - } - - private replaceGalleryItem(item: IGalleryItem, items?: IGalleryItem[]): void { - const index = items?.findIndex((value) => value.id === item.id); - if (index !== -1) { - items?.splice(index, 1, item); - } - } - - private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => { - if (itemIndex === 0) { - this.columnCount = Math.floor(visibleRect.width / GalleryViewerComponent.CARD_WIDTH) || this.columnCount; - this.rowCount = GalleryViewerComponent.rowsPerPage; - } - - return { - height: visibleRect.height, - itemCount: this.columnCount * this.rowCount, - }; - }; - - private onRenderCell = (data?: IGalleryItem): JSX.Element => { - const isFavorite = - this.props.container && this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined; - const props: GalleryCardComponentProps = { - data, - isFavorite, - showDownload: !!this.props.container, - showDelete: this.state.selectedTab === GalleryTab.Published, - onClick: () => this.props.openNotebook(data, isFavorite), - onTagClick: this.loadTaggedItems, - onFavoriteClick: () => this.favoriteItem(data), - onUnfavoriteClick: () => this.unfavoriteItem(data), - onDownloadClick: () => this.downloadItem(data), - onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => - this.deleteItem(data, beforeDelete, afterDelete), - }; - - return ( -
- -
- ); - }; - - private loadTaggedItems = (tag: string): void => { - const searchText = tag; - this.setState({ - searchText, - }); - - this.loadTabContent(this.state.selectedTab, searchText, this.state.sortBy, true); - this.props.onSearchTextChange && this.props.onSearchTextChange(searchText); - }; - - private favoriteItem = async (data: IGalleryItem): Promise => { - GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, data, (item: IGalleryItem) => { - if (this.favoriteNotebooks) { - this.favoriteNotebooks.push(item); - } else { - this.favoriteNotebooks = [item]; - } - this.refreshSelectedTab(item); - }); - }; - - private unfavoriteItem = async (data: IGalleryItem): Promise => { - GalleryUtils.unfavoriteItem(this.props.container, this.props.junoClient, data, (item: IGalleryItem) => { - this.favoriteNotebooks = this.favoriteNotebooks?.filter((value) => value.id !== item.id); - this.refreshSelectedTab(item); - }); - }; - - private downloadItem = async (data: IGalleryItem): Promise => { - GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, (item) => - this.refreshSelectedTab(item), - ); - }; - - private deleteItem = async (data: IGalleryItem, beforeDelete: () => void, afterDelete: () => void): Promise => { - GalleryUtils.deleteItem( - this.props.container, - this.props.junoClient, - data, - (item) => { - this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id); - this.refreshSelectedTab(item); - }, - beforeDelete, - afterDelete, - ); - }; - - private onPivotChange = (item: PivotItem): void => { - const selectedTab = GalleryTab[item.props.itemKey as keyof typeof GalleryTab]; - const searchText: string = undefined; - this.setState({ - selectedTab, - searchText, - }); - - this.loadTabContent(selectedTab, searchText, this.state.sortBy, false); - this.props.onSelectedTabChange && this.props.onSelectedTabChange(selectedTab); - }; - - private onSearchBoxChange = (event?: React.ChangeEvent, newValue?: string): void => { - const searchText = newValue; - this.setState({ - searchText, - }); - - this.loadTabContent(this.state.selectedTab, searchText, this.state.sortBy, true); - this.props.onSearchTextChange && this.props.onSearchTextChange(searchText); - }; - - private onDropdownChange = (event: React.FormEvent, option?: IDropdownOption): void => { - const sortBy = option.key as SortBy; - this.setState({ - sortBy, - }); - - this.loadTabContent(this.state.selectedTab, this.state.searchText, sortBy, true); - this.props.onSortByChange && this.props.onSortByChange(sortBy); - }; -} diff --git a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.less b/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.less deleted file mode 100644 index 5ec067fad..000000000 --- a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.less +++ /dev/null @@ -1,26 +0,0 @@ -@import "../../../../../less/Common/Constants.less"; -.infoPanel, .infoPanelMain { - display: flex; - align-items: center; -} - -.infoPanel { - padding-left: 5px; - padding-right: 5px; -} - -.infoLabel, .infoLabelMain { - padding-left: 5px -} - -.infoLabel { - font-weight: 400 -} - -.infoIconMain { - color: @AccentMedium -} - -.infoIconMain:hover { - color: @BaseMedium -} \ No newline at end of file diff --git a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.test.tsx b/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.test.tsx deleted file mode 100644 index 07129e7c7..000000000 --- a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { shallow } from "enzyme"; -import React from "react"; -import { InfoComponent } from "./InfoComponent"; - -describe("InfoComponent", () => { - it("renders", () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx b/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx deleted file mode 100644 index 94515d46a..000000000 --- a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { HoverCard, HoverCardType, Icon, Label, Link, Stack } from "@fluentui/react"; -import * as React from "react"; -import { CodeOfConductEndpoints } from "../../../../Common/Constants"; -import "./InfoComponent.less"; - -export interface InfoComponentProps { - onReportAbuseClick?: () => void; -} - -export class InfoComponent extends React.Component { - private getInfoPanel = (iconName: string, labelText: string, url?: string, onClick?: () => void): JSX.Element => { - return ( - -
- - -
- - ); - }; - - private onHover = (): JSX.Element => { - return ( - - {this.getInfoPanel("Script", "Code of Conduct", CodeOfConductEndpoints.codeOfConduct)} - - {this.getInfoPanel("RedEye", "Privacy Statement", CodeOfConductEndpoints.privacyStatement)} - - - {this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)} - - {this.props.onReportAbuseClick && ( - - {this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())} - - )} - - ); - }; - - public render(): JSX.Element { - return ( - -
- - -
-
- ); - } -} diff --git a/src/Explorer/Controls/NotebookGallery/InfoComponent/__snapshots__/InfoComponent.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/InfoComponent/__snapshots__/InfoComponent.test.tsx.snap deleted file mode 100644 index 7311a01ca..000000000 --- a/src/Explorer/Controls/NotebookGallery/InfoComponent/__snapshots__/InfoComponent.test.tsx.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InfoComponent renders 1`] = ` - -
- - - Help - -
-
-`; diff --git a/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap deleted file mode 100644 index b7f1567b3..000000000 --- a/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap +++ /dev/null @@ -1,98 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GalleryViewerComponent renders 1`] = ` -
- - - - - - - - - - Sort by - - - - - - - - - - - - - - - -
-`; diff --git a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx index 000aef69e..5235b1d28 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx @@ -25,10 +25,6 @@ describe("NotebookMetadataComponent", () => { isFavorite: false, downloadButtonText: "Download", onTagClick: undefined, - onDownloadClick: undefined, - onFavoriteClick: undefined, - onUnfavoriteClick: undefined, - onReportAbuseClick: undefined, }; const wrapper = shallow(); @@ -57,10 +53,6 @@ describe("NotebookMetadataComponent", () => { isFavorite: true, downloadButtonText: "Download", onTagClick: undefined, - onDownloadClick: undefined, - onFavoriteClick: undefined, - onUnfavoriteClick: undefined, - onReportAbuseClick: undefined, }; const wrapper = shallow(); diff --git a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx index 2f9a812f8..248d8b550 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx @@ -1,46 +1,21 @@ /** * Wrapper around Notebook metadata */ -import { FontWeights, Icon, IconButton, Link, Persona, PersonaSize, PrimaryButton, Stack, Text } from "@fluentui/react"; +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"; -import { InfoComponent } from "../NotebookGallery/InfoComponent/InfoComponent"; export interface NotebookMetadataComponentProps { data: IGalleryItem; isFavorite: boolean; downloadButtonText?: string; onTagClick: (tag: string) => void; - onFavoriteClick: () => void; - onUnfavoriteClick: () => void; - onDownloadClick: () => void; - onReportAbuseClick: () => void; } export class NotebookMetadataComponent extends React.Component { - private renderFavouriteButton = (): JSX.Element => { - return ( - - {this.props.isFavorite !== undefined ? ( - <> - - {this.props.data.favorites} likes - - ) : ( - <> - {this.props.data.favorites} likes - - )} - - ); - }; - public render(): JSX.Element { const options: Intl.DateTimeFormatOptions = { year: "numeric", @@ -59,20 +34,10 @@ export class NotebookMetadataComponent extends React.Component - {this.renderFavouriteButton()} - - {this.props.downloadButtonText && ( - - - - )} - - - <> - - - + + {this.props.data.favorites} likes + diff --git a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx index cc068e170..a6a9cd16d 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx @@ -1,7 +1,7 @@ /** * Wrapper around Notebook Viewer Read only content */ -import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "@fluentui/react"; +import { Icon, Link, ProgressIndicator } from "@fluentui/react"; import { Notebook } from "@nteract/commutable"; import { createContentRef } from "@nteract/core"; import * as React from "react"; @@ -11,14 +11,11 @@ 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 * as GalleryUtils from "../../../Utils/GalleryUtils"; -import { DialogHost } from "../../../Utils/GalleryUtils"; 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 { Dialog, TextFieldProps, useDialog } from "../Dialog"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import "./NotebookViewerComponent.less"; @@ -42,10 +39,10 @@ interface NotebookViewerComponentState { showProgressBar: boolean; } -export class NotebookViewerComponent - extends React.Component - implements DialogHost -{ +export class NotebookViewerComponent extends React.Component< + NotebookViewerComponentProps, + NotebookViewerComponentState +> { private clientManager: NotebookClientV2; private notebookComponentBootstrapper: NotebookComponentBootstrapper; @@ -102,7 +99,6 @@ export class NotebookViewerComponent ); const notebook: Notebook = await response.json(); - GalleryUtils.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId); this.notebookComponentBootstrapper.setContent("json", notebook); this.setState({ content: notebook, showProgressBar: false }); @@ -150,10 +146,6 @@ export class NotebookViewerComponent isFavorite={this.state.isFavorite} downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`} onTagClick={this.props.onTagClick} - onFavoriteClick={this.favoriteItem} - onUnfavoriteClick={this.unfavoriteItem} - onDownloadClick={this.downloadItem} - onReportAbuseClick={this.state.galleryItem.isSample ? undefined : this.reportAbuse} /> ) : ( @@ -166,7 +158,6 @@ export class NotebookViewerComponent hideInputs: this.props.hideInputs, hidePrompts: this.props.hidePrompts, })} - ); } @@ -191,81 +182,4 @@ export class NotebookViewerComponent isFavorite, }; } - - showOkModalDialog( - title: string, - msg: string, - okLabel: string, - onOk: () => void, - progressIndicatorProps?: IProgressIndicatorProps, - ): void { - useDialog.getState().openDialog({ - isModal: true, - title, - subText: msg, - primaryButtonText: okLabel, - onPrimaryButtonClick: () => { - useDialog.getState().closeDialog(); - onOk && onOk(); - }, - secondaryButtonText: undefined, - onSecondaryButtonClick: undefined, - progressIndicatorProps, - }); - } - - showOkCancelModalDialog( - title: string, - msg: string, - okLabel: string, - onOk: () => void, - cancelLabel: string, - onCancel: () => void, - progressIndicatorProps?: IProgressIndicatorProps, - choiceGroupProps?: IChoiceGroupProps, - textFieldProps?: TextFieldProps, - primaryButtonDisabled?: boolean, - ): void { - useDialog.getState().openDialog({ - isModal: true, - title, - subText: msg, - primaryButtonText: okLabel, - secondaryButtonText: cancelLabel, - onPrimaryButtonClick: () => { - useDialog.getState().closeDialog(); - onOk && onOk(); - }, - onSecondaryButtonClick: () => { - useDialog.getState().closeDialog(); - onCancel && onCancel(); - }, - progressIndicatorProps, - choiceGroupProps, - textFieldProps, - primaryButtonDisabled, - }); - } - - private favoriteItem = async (): Promise => { - GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) => - this.setState({ galleryItem: item, isFavorite: true }), - ); - }; - - private unfavoriteItem = async (): Promise => { - GalleryUtils.unfavoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) => - this.setState({ galleryItem: item, isFavorite: false }), - ); - }; - - private downloadItem = async (): Promise => { - GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) => - this.setState({ galleryItem: item }), - ); - }; - - private reportAbuse = (): void => { - GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {}); - }; } diff --git a/src/Explorer/Controls/NotebookViewer/__snapshots__/NotebookMetadataComponent.test.tsx.snap b/src/Explorer/Controls/NotebookViewer/__snapshots__/NotebookMetadataComponent.test.tsx.snap index 716390c36..a8b87926c 100644 --- a/src/Explorer/Controls/NotebookViewer/__snapshots__/NotebookMetadataComponent.test.tsx.snap +++ b/src/Explorer/Controls/NotebookViewer/__snapshots__/NotebookMetadataComponent.test.tsx.snap @@ -27,28 +27,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = ` - + 0 likes - - - - - - - - + 0 likes - - - - - - - void, - onClosePanel?: () => void, - ): Promise { - if (this.notebookManager) { - await this.notebookManager.openPublishNotebookPane( - name, - content, - notebookContentRef, - onTakeSnapshot, - onClosePanel, - ); - } - } - public copyNotebook(name: string, content: string): void { this.notebookManager?.openCopyNotebookPane(name, content); } @@ -1045,45 +1023,6 @@ export default class Explorer { useTabs.getState().activateNewTab(newTab); } - public async openGallery( - selectedTab?: GalleryTabKind, - notebookUrl?: string, - galleryItem?: IGalleryItem, - isFavorite?: boolean, - ): Promise { - const title = "Gallery"; - const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default; - const galleryTab = useTabs - .getState() - .getTabs(ViewModels.CollectionTabKind.Gallery) - .find((tab) => tab.tabTitle() === title); - - if (galleryTab instanceof GalleryTab) { - useTabs.getState().activateTab(galleryTab); - } else { - useTabs.getState().activateNewTab( - new GalleryTab( - { - tabKind: ViewModels.CollectionTabKind.Gallery, - title, - tabPath: title, - onLoadStartKey: undefined, - isTabsContentExpanded: ko.observable(true), - }, - { - account: userContext.databaseAccount, - container: this, - junoClient: this.notebookManager?.junoClient, - selectedTab: selectedTab || GalleryTabKind.OfficialSamples, - notebookUrl, - galleryItem, - isFavorite, - }, - ), - ); - } - } - public async onNewCollectionClicked( options: { databaseId?: string; diff --git a/src/Explorer/Notebook/NotebookManager.tsx b/src/Explorer/Notebook/NotebookManager.tsx index 3ccbefcaf..a58272e70 100644 --- a/src/Explorer/Notebook/NotebookManager.tsx +++ b/src/Explorer/Notebook/NotebookManager.tsx @@ -17,16 +17,13 @@ import { JunoClient } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; -import { getFullName } from "../../Utils/UserUtils"; import { useDialog } from "../Controls/Dialog"; import Explorer from "../Explorer"; import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel"; -import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider"; import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider"; -import { SnapshotRequest } from "./NotebookComponent/types"; import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContentClient } from "./NotebookContentClient"; import { SchemaAnalyzerNotebook } from "./SchemaAnalyzer/SchemaAnalyzerUtils"; @@ -124,31 +121,6 @@ export default class NotebookManager { } } - public async openPublishNotebookPane( - name: string, - content: NotebookPaneContent, - notebookContentRef: string, - onTakeSnapshot: (request: SnapshotRequest) => void, - onClosePanel: () => void, - ): Promise { - useSidePanel - .getState() - .openSidePanel( - "Publish Notebook", - , - "440px", - onClosePanel, - ); - } - public openCopyNotebookPane(name: string, content: string): void { const { container } = this.params; useSidePanel diff --git a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.test.tsx b/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.test.tsx deleted file mode 100644 index 3ce42e2e4..000000000 --- a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { shallow } from "enzyme"; -import React from "react"; -import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent"; - -describe("PublishNotebookPaneComponent", () => { - it("renders", () => { - const props: PublishNotebookPaneProps = { - notebookName: "SampleNotebook.ipynb", - notebookDescription: "sample description", - notebookTags: "tag1, tag2", - imageSrc: "https://i.ytimg.com/vi/E_lByLdKeKY/maxresdefault.jpg", - notebookAuthor: "CosmosDB", - notebookCreatedDate: "2020-07-17T00:00:00Z", - notebookObject: undefined, - notebookContentRef: undefined, - setNotebookName: undefined, - setNotebookDescription: undefined, - setNotebookTags: undefined, - setImageSrc: undefined, - onError: undefined, - clearFormError: undefined, - onTakeSnapshot: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx b/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx deleted file mode 100644 index e11155f37..000000000 --- a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPane.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import { ImmutableNotebook, toJS } from "@nteract/commutable"; -import React, { FunctionComponent, useEffect, useState } from "react"; -import { HttpStatusCodes } from "../../../Common/Constants"; -import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils"; -import { useNotebookSnapshotStore } from "../../../hooks/useNotebookSnapshotStore"; -import { useSidePanel } from "../../../hooks/useSidePanel"; -import { JunoClient } from "../../../Juno/JunoClient"; -import { Keys, t } from "Localization"; -import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; -import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { CodeOfConduct } from "../../Controls/NotebookGallery/CodeOfConduct/CodeOfConduct"; -import { GalleryTab } from "../../Controls/NotebookGallery/GalleryViewerComponent"; -import Explorer from "../../Explorer"; -import * as FileSystemUtil from "../../Notebook/FileSystemUtil"; -import { SnapshotRequest } from "../../Notebook/NotebookComponent/types"; -import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; -import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent"; - -export interface PublishNotebookPaneAProps { - explorer: Explorer; - junoClient: JunoClient; - name: string; - author: string; - notebookContent: string | ImmutableNotebook; - notebookContentRef: string; - onTakeSnapshot: (request: SnapshotRequest) => void; -} -export const PublishNotebookPane: FunctionComponent = ({ - explorer: container, - junoClient, - name, - author, - notebookContent, - notebookContentRef, - onTakeSnapshot, -}: PublishNotebookPaneAProps): JSX.Element => { - const closeSidePanel = useSidePanel((state) => state.closeSidePanel); - - const [isCodeOfConductAccepted, setIsCodeOfConductAccepted] = useState(false); - const [content, setContent] = useState(""); - const [formError, setFormError] = useState(""); - const [formErrorDetail, setFormErrorDetail] = useState(""); - const [isExecuting, setIsExecuting] = useState(); - - const [notebookName, setNotebookName] = useState(name); - const [notebookDescription, setNotebookDescription] = useState(""); - const [notebookTags, setNotebookTags] = useState(""); - const [imageSrc, setImageSrc] = useState(); - const { snapshot: notebookSnapshot, error: notebookSnapshotError } = useNotebookSnapshotStore(); - - const CodeOfConductAccepted = async () => { - try { - const response = await junoClient.isCodeOfConductAccepted(); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - throw new Error(`Received HTTP ${response.status} when accepting code of conduct`); - } - setIsCodeOfConductAccepted(response.data); - } catch (error) { - handleError( - error, - "PublishNotebookPaneAdapter/isCodeOfConductAccepted", - "Failed to check if code of conduct was accepted", - ); - } - }; - const [notebookObject, setNotebookObject] = useState(); - useEffect(() => { - CodeOfConductAccepted(); - let newContent; - if (typeof notebookContent === "string") { - newContent = notebookContent as string; - } else { - newContent = JSON.stringify(toJS(notebookContent)); - setNotebookObject(notebookContent); - } - setContent(newContent); - }, []); - - useEffect(() => { - setImageSrc(notebookSnapshot); - }, [notebookSnapshot]); - - useEffect(() => { - setFormError(notebookSnapshotError); - }, [notebookSnapshotError]); - - const submit = async (): Promise => { - const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${name} to gallery`); - setIsExecuting(true); - - let startKey: number; - - if (!notebookName || !notebookDescription || !author || !imageSrc) { - setFormError(t(Keys.panes.publishNotebook.publishFailedError, { notebookName })); - setFormErrorDetail("Name, description, author and cover image are required"); - createFormError(formError, formErrorDetail, "PublishNotebookPaneAdapter/submit"); - setIsExecuting(false); - return; - } - - try { - startKey = traceStart(Action.NotebooksGalleryPublish, {}); - - const response = await junoClient.publishNotebook( - notebookName, - notebookDescription, - notebookTags?.split(","), - imageSrc, - content, - ); - - const data = response.data; - if (data) { - let isPublishPending = false; - - if (data.pendingScanJobIds?.length > 0) { - isPublishPending = true; - NotificationConsoleUtils.logConsoleInfo( - `Content of ${name} is currently being scanned for illegal content. It will not be available in the public gallery until the review is complete (may take a few days).`, - ); - } else { - NotificationConsoleUtils.logConsoleInfo(`Published ${notebookName} to gallery`); - container.openGallery(GalleryTab.Published); - } - - traceSuccess( - Action.NotebooksGalleryPublish, - { - notebookId: data.id, - isPublishPending, - }, - startKey, - ); - } - } catch (error) { - traceFailure( - Action.NotebooksGalleryPublish, - { - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - const errorMessage = getErrorMessage(error); - setFormError( - t(Keys.panes.publishNotebook.publishFailedError, { - notebookName: FileSystemUtil.stripExtension(notebookName, "ipynb"), - }), - ); - setFormErrorDetail(`${errorMessage}`); - handleError(errorMessage, "PublishNotebookPaneAdapter/submit", formError); - return; - } finally { - clearPublishingMessage(); - setIsExecuting(false); - } - closeSidePanel(); - }; - - const createFormError = (formError: string, formErrorDetail: string, area: string): void => { - setFormError(formError); - setFormErrorDetail(formErrorDetail); - handleError(formErrorDetail, area, formError); - }; - - const clearFormError = (): void => { - setFormError(""); - setFormErrorDetail(""); - }; - - const props: RightPaneFormProps = { - formError: formError, - isExecuting: isExecuting, - submitButtonText: "Publish", - onSubmit: () => submit(), - isSubmitButtonHidden: !isCodeOfConductAccepted, - }; - - const publishNotebookPaneProps: PublishNotebookPaneProps = { - notebookDescription, - notebookTags, - imageSrc, - notebookName, - notebookAuthor: author, - notebookCreatedDate: new Date().toISOString(), - notebookObject: notebookObject, - notebookContentRef, - onError: createFormError, - clearFormError: clearFormError, - setNotebookName, - setNotebookDescription, - setNotebookTags, - setImageSrc, - onTakeSnapshot, - }; - return ( - - {!isCodeOfConductAccepted ? ( -
- { - setIsCodeOfConductAccepted(isAccepted); - }} - /> -
- ) : ( - - )} -
- ); -}; diff --git a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPaneComponent.tsx b/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPaneComponent.tsx deleted file mode 100644 index ae5257008..000000000 --- a/src/Explorer/Panes/PublishNotebookPane/PublishNotebookPaneComponent.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { Dropdown, IDropdownProps, ITextFieldProps, Stack, Text, TextField } from "@fluentui/react"; -import { ImmutableNotebook } from "@nteract/commutable"; -import React, { FunctionComponent, useState } from "react"; -import { Keys, t } from "Localization"; -import { GalleryCardComponent } from "../../Controls/NotebookGallery/Cards/GalleryCardComponent"; -import * as FileSystemUtil from "../../Notebook/FileSystemUtil"; -import { SnapshotRequest } from "../../Notebook/NotebookComponent/types"; -import { NotebookUtil } from "../../Notebook/NotebookUtil"; -import "./styled.less"; - -export interface PublishNotebookPaneProps { - notebookName: string; - notebookAuthor: string; - notebookTags: string; - notebookDescription: string; - notebookCreatedDate: string; - notebookObject: ImmutableNotebook; - notebookContentRef: string; - imageSrc: string; - - onError: (formError: string, formErrorDetail: string, area: string) => void; - clearFormError: () => void; - setNotebookName: (newValue: string) => void; - setNotebookDescription: (newValue: string) => void; - setNotebookTags: (newValue: string) => void; - setImageSrc: (newValue: string) => void; - onTakeSnapshot: (request: SnapshotRequest) => void; -} - -enum ImageTypes { - Url = "URL", - CustomImage = "Custom Image", - TakeScreenshot = "Take Screenshot", - UseFirstDisplayOutput = "Use First Display Output", -} - -export const PublishNotebookPaneComponent: FunctionComponent = ({ - notebookName, - notebookTags, - notebookDescription, - notebookAuthor, - notebookCreatedDate, - notebookObject, - notebookContentRef, - imageSrc, - onError, - clearFormError, - setNotebookName, - setNotebookDescription, - setNotebookTags, - setImageSrc, - onTakeSnapshot, -}: PublishNotebookPaneProps) => { - const [type, setType] = useState(ImageTypes.CustomImage); - const CARD_WIDTH = 256; - const cardImageHeight = 144; - const cardHeightToWidthRatio = cardImageHeight / CARD_WIDTH; - - const maxImageSizeInMib = 1.5; - - const descriptionPara1 = t(Keys.panes.publishNotebook.publishDescription); - - const descriptionPara2 = t(Keys.panes.publishNotebook.publishPrompt, { - name: FileSystemUtil.stripExtension(notebookName, "ipynb"), - }); - - const options: ImageTypes[] = [ImageTypes.CustomImage, ImageTypes.Url]; - if (onTakeSnapshot) { - options.push(ImageTypes.TakeScreenshot); - if (notebookObject) { - options.push(ImageTypes.UseFirstDisplayOutput); - } - } - - const thumbnailSelectorProps: IDropdownProps = { - label: t(Keys.panes.publishNotebook.coverImage), - selectedKey: type, - ariaLabel: t(Keys.panes.publishNotebook.coverImage), - options: options.map((value: string) => ({ text: value, key: value })), - onChange: async (event, options) => { - setImageSrc(""); - clearFormError(); - if (options.text === ImageTypes.TakeScreenshot) { - onTakeSnapshot({ - aspectRatio: cardHeightToWidthRatio, - requestId: new Date().getTime().toString(), - type: "notebook", - notebookContentRef, - }); - } else if (options.text === ImageTypes.UseFirstDisplayOutput) { - const cellIds = NotebookUtil.findCodeCellWithDisplay(notebookObject); - if (cellIds.length > 0) { - onTakeSnapshot({ - aspectRatio: cardHeightToWidthRatio, - requestId: new Date().getTime().toString(), - type: "celloutput", - cellId: cellIds[0], - notebookContentRef, - }); - } else { - firstOutputErrorHandler(new Error(t(Keys.panes.publishNotebook.outputDoesNotExist))); - } - } - setType(options.text); - }, - }; - - const thumbnailUrlProps: ITextFieldProps = { - label: t(Keys.panes.publishNotebook.coverImageUrl), - ariaLabel: t(Keys.panes.publishNotebook.coverImageUrl), - required: true, - onChange: (event, newValue) => { - setImageSrc(newValue); - }, - }; - - const firstOutputErrorHandler = (error: Error) => { - const formError = t(Keys.panes.publishNotebook.failedToCaptureOutput); - const formErrorDetail = `${error}`; - const area = "PublishNotebookPaneComponent/UseFirstOutput"; - onError(formError, formErrorDetail, area); - }; - - const imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - updateImageSrc(reader.result.toString()); - }; - - reader.onerror = (error) => { - const formError = t(Keys.panes.publishNotebook.failedToConvertError, { fileName: file.name }); - const formErrorDetail = `${error}`; - const area = "PublishNotebookPaneComponent/selectImageFile"; - onError(formError, formErrorDetail, area); - }; - }; - - const renderThumbnailSelectors = (type: string) => { - switch (type) { - case ImageTypes.Url: - return ; - case ImageTypes.CustomImage: - return ( - { - const file = event.target.files[0]; - if (file.size / 1024 ** 2 > maxImageSizeInMib) { - event.target.value = ""; - const formError = t(Keys.panes.publishNotebook.failedToUploadError, { fileName: file.name }); - const formErrorDetail = `Image is larger than ${maxImageSizeInMib} MiB. Please Choose a different image.`; - const area = "PublishNotebookPaneComponent/selectImageFile"; - - onError(formError, formErrorDetail, area); - setImageSrc(""); - return; - } else { - clearFormError(); - } - imageToBase64(file, (result: string) => { - setImageSrc(result); - }); - }} - /> - ); - default: - return <>; - } - }; - - return ( -
- - - {descriptionPara1} - - - - {descriptionPara2} - - - - { - const notebookName = newValue + ".ipynb"; - setNotebookName(notebookName); - }} - /> - - - - { - setNotebookDescription(newValue); - }} - /> - - - - { - setNotebookTags(newValue); - }} - /> - - - - - - - {renderThumbnailSelectors(type)} - - - {t(Keys.panes.publishNotebook.preview)} - - - undefined} - onTagClick={undefined} - onFavoriteClick={undefined} - onUnfavoriteClick={undefined} - onDownloadClick={undefined} - onDeleteClick={undefined} - /> - - -
- ); -}; diff --git a/src/Explorer/Panes/PublishNotebookPane/__snapshots__/PublishNotebookPane.test.tsx.snap b/src/Explorer/Panes/PublishNotebookPane/__snapshots__/PublishNotebookPane.test.tsx.snap deleted file mode 100644 index 23af68f23..000000000 --- a/src/Explorer/Panes/PublishNotebookPane/__snapshots__/PublishNotebookPane.test.tsx.snap +++ /dev/null @@ -1,116 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PublishNotebookPaneComponent renders 1`] = ` -
- - - - When published, this notebook will appear in the Azure Cosmos DB notebooks public gallery. Make sure you have removed any sensitive data or output before publishing. - - - - - Would you like to publish and share "SampleNotebook" to the gallery? - - - - - - - - - - - - - - - - - - - - Preview - - - - - - -
-`; diff --git a/src/Explorer/Panes/PublishNotebookPane/styled.less b/src/Explorer/Panes/PublishNotebookPane/styled.less deleted file mode 100644 index e1170b670..000000000 --- a/src/Explorer/Panes/PublishNotebookPane/styled.less +++ /dev/null @@ -1,6 +0,0 @@ -.publishNotebookPanelContent { - display: flex; - flex-direction: column; - flex: 1; - overflow-y: auto; -} \ No newline at end of file diff --git a/src/Explorer/Tabs/GalleryTab.tsx b/src/Explorer/Tabs/GalleryTab.tsx deleted file mode 100644 index 7932f8301..000000000 --- a/src/Explorer/Tabs/GalleryTab.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import type { DatabaseAccount } from "../../Contracts/DataModels"; -import type { TabOptions } from "../../Contracts/ViewModels"; -import type { IGalleryItem, JunoClient } from "../../Juno/JunoClient"; -import { GalleryAndNotebookViewerComponent as GalleryViewer } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponent"; -import type { GalleryTab as GalleryViewerTab } from "../Controls/NotebookGallery/GalleryViewerComponent"; -import { SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent"; -import type Explorer from "../Explorer"; -import TabsBase from "./TabsBase"; - -interface Props { - account: DatabaseAccount; - container: Explorer; - junoClient: JunoClient; - selectedTab: GalleryViewerTab; - notebookUrl?: string; - galleryItem?: IGalleryItem; - isFavorite?: boolean; -} - -export default class GalleryTab extends TabsBase { - constructor( - options: TabOptions, - private props: Props, - ) { - super(options); - } - - public render() { - return ; - } - - public getContainer(): Explorer { - return this.props.container; - } -} diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index 92e0e6958..b54efc00e 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -1,7 +1,6 @@ import { stringifyNotebook, toJS } from "@nteract/commutable"; import * as ko from "knockout"; import * as Q from "q"; -import { userContext } from "UserContext"; import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg"; import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; import CutIcon from "../../../images/notebook/Notebook-cut.svg"; @@ -12,8 +11,7 @@ import RunAllIcon from "../../../images/notebook/Notebook-run-all.svg"; import RunIcon from "../../../images/notebook/Notebook-run.svg"; import { default as InterruptKernelIcon, default as KillKernelIcon } from "../../../images/notebook/Notebook-stop.svg"; import SaveIcon from "../../../images/save-cosmos.svg"; -import { useNotebookSnapshotStore } from "../../hooks/useNotebookSnapshotStore"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; +import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; @@ -21,9 +19,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu import { useDialog } from "../Controls/Dialog"; import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2"; -import * as CdbActions from "../Notebook/NotebookComponent/actions"; import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; -import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types"; import { NotebookContentItem } from "../Notebook/NotebookContentItem"; import { NotebookUtil } from "../Notebook/NotebookUtil"; import { useNotebook } from "../Notebook/useNotebook"; @@ -97,7 +93,6 @@ export default class NotebookTabV2 extends NotebookTabBase { const saveLabel = "Save"; const copyToLabel = "Copy to ..."; - const publishLabel = "Publish to gallery"; const kernelLabel = "No Kernel"; const runLabel = "Run"; const runActiveCellLabel = "Run Active Cell"; @@ -130,17 +125,6 @@ export default class NotebookTabV2 extends NotebookTabBase { }); } - if (userContext.features.publicGallery) { - saveButtonChildren.push({ - iconName: "PublishContent", - onCommandClick: async () => await this.publishToGallery(), - commandButtonLabel: publishLabel, - hasPopup: false, - disabled: false, - ariaLabel: publishLabel, - }); - } - let buttons: CommandButtonComponentProps[] = [ { iconSrc: SaveIcon, @@ -383,40 +367,6 @@ export default class NotebookTabV2 extends NotebookTabBase { ); } - private publishToGallery = async () => { - TelemetryProcessor.trace(Action.NotebooksGalleryClickPublishToGallery, ActionModifiers.Mark, { - source: Source.CommandBarMenu, - }); - - const notebookReduxStore = NotebookTabV2.clientManager.getStore(); - const unsubscribe = notebookReduxStore.subscribe(() => { - const cdbState = (notebookReduxStore.getState() as CdbAppState).cdb; - useNotebookSnapshotStore.setState({ - snapshot: cdbState.notebookSnapshot?.imageSrc, - error: cdbState.notebookSnapshotError, - }); - }); - - const notebookContent = this.notebookComponentAdapter.getContent(); - const notebookContentRef = this.notebookComponentAdapter.contentRef; - const onPanelClose = (): void => { - unsubscribe(); - useNotebookSnapshotStore.setState({ - snapshot: undefined, - error: undefined, - }); - notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(undefined)); - }; - - await this.container.publishNotebook( - notebookContent.name, - notebookContent.content, - notebookContentRef, - (request: SnapshotRequest) => notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(request)), - onPanelClose, - ); - }; - private copyNotebook = () => { const notebookContent = this.notebookComponentAdapter.getContent(); let content: string; diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index 819c73d6c..2d19bc8e9 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -10,7 +10,6 @@ import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg"; import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; import FileIcon from "../../../images/notebook/file-cosmos.svg"; -import PublishIcon from "../../../images/notebook/publish_content.svg"; import RefreshIcon from "../../../images/refresh-cosmos.svg"; import CollectionIcon from "../../../images/tree-collection.svg"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; @@ -18,7 +17,7 @@ import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtili import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; import { IPinnedRepo } from "../../Juno/JunoClient"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; +import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { isServerlessAccount } from "../../Utils/CapabilityUtils"; @@ -49,7 +48,6 @@ export class ResourceTreeAdapter implements ReactAdapter { public parameters: ko.Observable; - public galleryContentRoot: NotebookContentItem; public myNotebooksContentRoot: NotebookContentItem; public gitHubNotebooksContentRoot: NotebookContentItem; @@ -102,11 +100,6 @@ export class ResourceTreeAdapter implements ReactAdapter { public async initialize(): Promise { const refreshTasks: Promise[] = []; - this.galleryContentRoot = { - name: "Gallery", - path: "Gallery", - type: NotebookContentItemType.File, - }; this.myNotebooksContentRoot = { name: useNotebook.getState().notebookFolderName, path: useNotebook.getState().notebookBasePath, @@ -538,20 +531,7 @@ export class ResourceTreeAdapter implements ReactAdapter { ]; if (item.type === NotebookContentItemType.Notebook) { - items.push({ - label: "Publish to gallery", - iconSrc: PublishIcon, - onClick: async () => { - TelemetryProcessor.trace(Action.NotebooksGalleryClickPublishToGallery, ActionModifiers.Mark, { - source: Source.ResourceTreeMenu, - }); - - const content = await this.container.readFile(item); - if (content) { - await this.container.publishNotebook(item.name, content); - } - }, - }); + // Additional notebook-specific context menu items can be added here } // "Copy to ..." isn't needed if github locations are not available diff --git a/src/GalleryViewer/GalleryViewer.less b/src/GalleryViewer/GalleryViewer.less deleted file mode 100644 index 2eab230e9..000000000 --- a/src/GalleryViewer/GalleryViewer.less +++ /dev/null @@ -1,5 +0,0 @@ -@import "../../less/Common/Constants"; - -.standalone-gallery-root { - background: @GalleryBackgroundColor; -} \ No newline at end of file diff --git a/src/GalleryViewer/GalleryViewer.tsx b/src/GalleryViewer/GalleryViewer.tsx deleted file mode 100644 index 2026415af..000000000 --- a/src/GalleryViewer/GalleryViewer.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { initializeIcons, Link, Text } from "@fluentui/react"; -import "bootstrap/dist/css/bootstrap.css"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import { userContext } from "UserContext"; -import { initializeConfiguration } from "../ConfigContext"; -import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent"; -import { - GalleryAndNotebookViewerComponent, - GalleryAndNotebookViewerComponentProps, -} from "../Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent"; -import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; -import { JunoClient } from "../Juno/JunoClient"; -import * as GalleryUtils from "../Utils/GalleryUtils"; -import "./GalleryViewer.less"; - -const enableNotebooksUrl = "https://aka.ms/cosmos-enable-notebooks"; -const createAccountUrl = "https://aka.ms/cosmos-create-account-portal"; - -const onInit = async () => { - const dataExplorerUrl = new URL("./", window.location.href).href; - - initializeIcons(); - await initializeConfiguration(); - const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search); - - const props: GalleryAndNotebookViewerComponentProps = { - junoClient: new JunoClient(), - selectedTab: - galleryViewerProps.selectedTab || - (userContext.features.publicGallery ? GalleryTab.PublicGallery : GalleryTab.OfficialSamples), - sortBy: galleryViewerProps.sortBy || SortBy.MostRecent, - searchText: galleryViewerProps.searchText, - }; - - const element = ( -
-
- -
-
-
- - Welcome to the Azure Cosmos DB notebooks gallery! View the sample notebooks to learn about use cases, best - practices, and how to get started with Azure Cosmos DB. - - - If {`you'd`} like to run or edit the notebook in your own Azure Cosmos DB account,{" "} - sign in and select an account with{" "} - notebooks enabled. From there, you can download the sample to your - account. If you {`don't`} have an account yet, you can{" "} - create one from the Azure portal. - -
- - -
-
- ); - - ReactDOM.render(element, document.getElementById("galleryContent")); -}; - -// Entry point -window.addEventListener("load", onInit); diff --git a/src/GalleryViewer/galleryViewer.html b/src/GalleryViewer/galleryViewer.html deleted file mode 100644 index 41ca38498..000000000 --- a/src/GalleryViewer/galleryViewer.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Gallery Viewer - - - - -
- - diff --git a/src/Juno/JunoClient.test.ts b/src/Juno/JunoClient.test.ts index 771354f3e..ae7542926 100644 --- a/src/Juno/JunoClient.test.ts +++ b/src/Juno/JunoClient.test.ts @@ -1,24 +1,5 @@ -import { HttpHeaders, HttpStatusCodes } from "../Common/Constants"; -import { DatabaseAccount } from "../Contracts/DataModels"; -import { updateUserContext, userContext } from "../UserContext"; -import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; -import { IPinnedRepo, IPublishNotebookRequest, JunoClient } from "./JunoClient"; - -const sampleSubscriptionId = "subscriptionId"; - -const sampleDatabaseAccount: DatabaseAccount = { - id: "id", - name: "name", - location: "location", - type: "type", - kind: "kind", - properties: { - documentEndpoint: "documentEndpoint", - gremlinEndpoint: "gremlinEndpoint", - tableEndpoint: "tableEndpoint", - cassandraEndpoint: "cassandraEndpoint", - }, -}; +import { HttpStatusCodes } from "../Common/Constants"; +import { IPinnedRepo, JunoClient } from "./JunoClient"; const samplePinnedRepos: IPinnedRepo[] = [ { @@ -130,279 +111,3 @@ describe("GitHub", () => { expect(fetchUrlParams.get("client_id")).toBeDefined(); }); }); - -describe("Gallery", () => { - const junoClient = new JunoClient(); - const originalSubscriptionId = userContext.subscriptionId; - - beforeAll(() => { - updateUserContext({ - databaseAccount: { - name: "name", - } as DatabaseAccount, - subscriptionId: sampleSubscriptionId, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - afterAll(() => { - updateUserContext({ subscriptionId: originalSubscriptionId }); - }); - - it("getSampleNotebooks", async () => { - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.getSampleNotebooks(); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/samples`, - undefined, - ); - }); - - it("getPublicNotebooks", async () => { - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.getPublicNotebooks(); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/public`, - undefined, - ); - }); - - it("getNotebook", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.getNotebookInfo(id); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}`); - }); - - it("getNotebookContent", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - text: () => undefined as undefined, - }); - - const response = await junoClient.getNotebookContent(id); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/content`); - }); - - it("increaseNotebookViews", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - const response = await junoClient.increaseNotebookViews(id); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/views`, { - method: "PATCH", - }); - }); - - it("increaseNotebookDownloadCount", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.increaseNotebookDownloadCount(id); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/${id}/downloads`, - { - method: "PATCH", - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("favoriteNotebook", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.favoriteNotebook(id); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/${id}/favorite`, - { - method: "PATCH", - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("unfavoriteNotebook", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.unfavoriteNotebook(id); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/${id}/unfavorite`, - { - method: "PATCH", - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("getFavoriteNotebooks", async () => { - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.getFavoriteNotebooks(); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/favorites`, - { - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("getPublishedNotebooks", async () => { - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.getPublishedNotebooks(); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/published`, - { - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("deleteNotebook", async () => { - const id = "id"; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.deleteNotebook(id); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery/${id}`, - { - method: "DELETE", - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - }, - ); - }); - - it("publishNotebook", async () => { - const name = "name"; - const description = "description"; - const tags = ["tag"]; - const thumbnailUrl = "thumbnailUrl"; - const content = `{ "key": "value" }`; - const addLinkToNotebookViewer = true; - window.fetch = jest.fn().mockReturnValue({ - status: HttpStatusCodes.OK, - json: () => undefined as undefined, - }); - - const response = await junoClient.publishNotebook(name, description, tags, thumbnailUrl, content); - - const authorizationHeader = getAuthorizationHeader(); - expect(response.status).toBe(HttpStatusCodes.OK); - expect(window.fetch).toHaveBeenCalledWith( - `${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${ - sampleDatabaseAccount.name - }/gallery`, - { - method: "PUT", - headers: { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }, - body: JSON.stringify({ - name, - description, - tags, - thumbnailUrl, - content: JSON.parse(content), - addLinkToNotebookViewer, - } as IPublishNotebookRequest), - }, - ); - }); -}); diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 0b0618e72..77ba61764 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -44,30 +44,6 @@ export interface IGalleryItem { pendingScanJobIds: string[]; } -export interface IPublicGalleryData { - metadata: IPublicGalleryMetaData; - notebooksData: IGalleryItem[]; -} - -export interface IPublicGalleryMetaData { - acceptedCodeOfConduct: boolean; -} - -export interface IUserGallery { - favorites: string[]; - published: string[]; -} - -// Only exported for unit test -export interface IPublishNotebookRequest { - name: string; - description: string; - tags: string[]; - thumbnailUrl: string; - content: unknown; - addLinkToNotebookViewer: boolean; -} - export class JunoClient { private cachedPinnedRepos: ko.Observable; @@ -176,90 +152,6 @@ export class JunoClient { }; } - public async getSampleNotebooks(): Promise> { - return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/samples`); - } - - public async getPublicNotebooks(): Promise> { - return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`); - } - - public async getPublicGalleryData(): Promise> { - const url = `${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/public`; - const response = await window.fetch(url, { headers: JunoClient.getHeaders() }); - - let data: IPublicGalleryData; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async acceptCodeOfConduct(): Promise> { - const url = `${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/acceptCodeOfConduct`; - const response = await window.fetch(url, { - method: "PATCH", - headers: JunoClient.getHeaders(), - }); - - let data: boolean; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async isCodeOfConductAccepted(): Promise> { - const url = `${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/isCodeOfConductAccepted`; - const response = await window.fetch(url, { headers: JunoClient.getHeaders() }); - - let data: boolean; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async getNotebookInfo(id: string): Promise> { - const response = await window.fetch(this.getNotebookInfoUrl(id)); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async getNotebookContent(id: string): Promise> { - const response = await window.fetch(this.getNotebookContentUrl(id)); - - let data: string; - if (response.status === HttpStatusCodes.OK) { - data = await response.text(); - } - - return { - status: response.status, - data, - }; - } - public async increaseNotebookViews(id: string): Promise> { const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/${id}/views`, { method: "PATCH", @@ -276,151 +168,6 @@ export class JunoClient { }; } - public async increaseNotebookDownloadCount(id: string): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/${id}/downloads`, { - method: "PATCH", - headers: JunoClient.getHeaders(), - }); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async favoriteNotebook(id: string): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/${id}/favorite`, { - method: "PATCH", - headers: JunoClient.getHeaders(), - }); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async unfavoriteNotebook(id: string): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/${id}/unfavorite`, { - method: "PATCH", - headers: JunoClient.getHeaders(), - }); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async getFavoriteNotebooks(): Promise> { - return await this.getNotebooks(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/favorites`, { - headers: JunoClient.getHeaders(), - }); - } - - public async getPublishedNotebooks(): Promise> { - return await this.getNotebooks(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/published`, { - headers: JunoClient.getHeaders(), - }); - } - - public async deleteNotebook(id: string): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery/${id}`, { - method: "DELETE", - headers: JunoClient.getHeaders(), - }); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - - public async publishNotebook( - name: string, - description: string, - tags: string[], - thumbnailUrl: string, - content: string, - ): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/gallery`, { - method: "PUT", - headers: JunoClient.getHeaders(), - body: JSON.stringify({ - name, - description, - tags, - thumbnailUrl, - content: JSON.parse(content), - addLinkToNotebookViewer: true, - } as IPublishNotebookRequest), - }); - - let data: IGalleryItem; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } else { - throw new Error(`HTTP status ${response.status} thrown. ${(await response.json()).Message}`); - } - - return { - status: response.status, - data, - }; - } - - public getNotebookContentUrl(id: string): string { - return `${this.getNotebooksUrl()}/gallery/${id}/content`; - } - - public getNotebookInfoUrl(id: string): string { - return `${this.getNotebooksUrl()}/gallery/${id}`; - } - - public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise> { - const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/reportAbuse`, { - method: "POST", - body: JSON.stringify({ - notebookId, - abuseCategory, - notes, - }), - headers: { - [HttpHeaders.contentType]: "application/json", - }, - }); - - let data: boolean; - if (response.status === HttpStatusCodes.OK) { - data = await response.json(); - } - - return { - status: response.status, - data, - }; - } - public async requestSchema( schemaRequest: DataModels.ISchemaRequest, ): Promise> { diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 6ca5d569c..ee1855818 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -44,7 +44,6 @@ export type Features = { phoenixNotebooks?: boolean; phoenixFeatures?: boolean; notebooksDownBanner: boolean; - publicGallery?: boolean; }; export function extractFeatures(given = new URLSearchParams(window.location.search)): Features { diff --git a/src/Shared/Telemetry/TelemetryConstants.ts b/src/Shared/Telemetry/TelemetryConstants.ts index 5270b4e24..507706a71 100644 --- a/src/Shared/Telemetry/TelemetryConstants.ts +++ b/src/Shared/Telemetry/TelemetryConstants.ts @@ -99,28 +99,7 @@ export enum Action { SettingsV2Updated, SettingsV2Discarded, MongoIndexUpdated, - NotebooksGalleryPublish, - NotebooksGalleryReportAbuse, - NotebooksGalleryClickReportAbuse, - NotebooksGalleryViewCodeOfConduct, - NotebooksGalleryAcceptCodeOfConduct, - NotebooksGalleryFavorite, - NotebooksGalleryUnfavorite, - NotebooksGalleryClickDelete, - NotebooksGalleryDelete, - NotebooksGalleryClickDownload, - NotebooksGalleryDownload, NotebooksGalleryViewNotebook, - NotebooksGalleryViewGallery, - NotebooksGalleryViewOfficialSamples, - NotebooksGalleryViewPublicGallery, - NotebooksGalleryViewFavorites, - NotebooksGalleryViewPublishedNotebooks, - NotebooksGalleryClickPublishToGallery, - NotebooksGalleryOfficialSamplesCount, - NotebooksGalleryPublicGalleryCount, - NotebooksGalleryFavoritesCount, - NotebooksGalleryPublishedCount, SelfServe, ExpandAddCollectionPaneAdvancedSection, ExpandAddGlobalSecondaryIndexPaneAdvancedSection, diff --git a/src/Utils/GalleryUtils.test.ts b/src/Utils/GalleryUtils.test.ts deleted file mode 100644 index 4fdbcfb35..000000000 --- a/src/Utils/GalleryUtils.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { HttpStatusCodes } from "../Common/Constants"; -import { useDialog } from "../Explorer/Controls/Dialog"; -import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; -import Explorer from "../Explorer/Explorer"; -import { useNotebook } from "../Explorer/Notebook/useNotebook"; -import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; -import * as GalleryUtils from "./GalleryUtils"; - -const galleryItem: IGalleryItem = { - id: "id", - name: "name", - description: "description", - gitSha: "gitSha", - tags: ["tag1"], - author: "author", - thumbnailUrl: "thumbnailUrl", - created: "created", - isSample: false, - downloads: 0, - favorites: 0, - views: 0, - newCellId: undefined, - policyViolations: undefined, - pendingScanJobIds: undefined, -}; - -describe("GalleryUtils", () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it("downloadItem shows dialog in data explorer", () => { - const container = new Explorer(); - GalleryUtils.downloadItem(container, undefined, galleryItem, undefined); - - expect(useDialog.getState().visible).toBe(true); - expect(useDialog.getState().dialogProps).toBeDefined(); - expect(useDialog.getState().dialogProps.title).toBe(`Download to ${useNotebook.getState().notebookFolderName}`); - }); - - it("favoriteItem favorites item", async () => { - const container = {} as Explorer; - const junoClient = new JunoClient(); - junoClient.favoriteNotebook = jest - .fn() - .mockReturnValue(Promise.resolve({ status: HttpStatusCodes.OK, data: galleryItem })); - const onComplete = jest.fn().mockImplementation(); - - await GalleryUtils.favoriteItem(container, junoClient, galleryItem, onComplete); - - expect(junoClient.favoriteNotebook).toHaveBeenCalledWith(galleryItem.id); - expect(onComplete).toHaveBeenCalledWith(galleryItem); - }); - - it("unfavoriteItem unfavorites item", async () => { - const container = {} as Explorer; - const junoClient = new JunoClient(); - junoClient.unfavoriteNotebook = jest - .fn() - .mockReturnValue(Promise.resolve({ status: HttpStatusCodes.OK, data: galleryItem })); - const onComplete = jest.fn().mockImplementation(); - - await GalleryUtils.unfavoriteItem(container, junoClient, galleryItem, onComplete); - - expect(junoClient.unfavoriteNotebook).toHaveBeenCalledWith(galleryItem.id); - expect(onComplete).toHaveBeenCalledWith(galleryItem); - }); - - it("deleteItem shows dialog in data explorer", () => { - const container = {} as Explorer; - GalleryUtils.deleteItem(container, undefined, galleryItem, undefined); - - expect(useDialog.getState().visible).toBe(true); - expect(useDialog.getState().dialogProps).toBeDefined(); - expect(useDialog.getState().dialogProps.title).toBe("Remove published notebook"); - }); - - it("getGalleryViewerProps gets gallery viewer props correctly", () => { - const selectedTab: GalleryTab = GalleryTab.OfficialSamples; - const sortBy: SortBy = SortBy.MostDownloaded; - const searchText = "my-complicated%20search%20query!!!"; - - const response = GalleryUtils.getGalleryViewerProps( - `?${GalleryUtils.GalleryViewerParams.SelectedTab}=${GalleryTab[selectedTab]}&${GalleryUtils.GalleryViewerParams.SortBy}=${SortBy[sortBy]}&${GalleryUtils.GalleryViewerParams.SearchText}=${searchText}`, - ); - - expect(response).toEqual({ - selectedTab, - sortBy, - searchText: decodeURIComponent(searchText), - } as GalleryUtils.GalleryViewerProps); - }); - - it("getNotebookViewerProps gets notebook viewer props correctly", () => { - const notebookUrl = "https%3A%2F%2Fnotebook.url"; - const galleryItemId = "1234-abcd-efgh"; - const hideInputs = "true"; - - const response = GalleryUtils.getNotebookViewerProps( - `?${GalleryUtils.NotebookViewerParams.NotebookUrl}=${notebookUrl}&${GalleryUtils.NotebookViewerParams.GalleryItemId}=${galleryItemId}&${GalleryUtils.NotebookViewerParams.HideInputs}=${hideInputs}`, - ); - - expect(response).toEqual({ - notebookUrl: decodeURIComponent(notebookUrl), - galleryItemId, - hideInputs: true, - } as GalleryUtils.NotebookViewerProps); - }); - - it("getTabTitle returns correct title for official samples", () => { - expect(GalleryUtils.getTabTitle(GalleryTab.OfficialSamples)).toBe("Official samples"); - }); -}); diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts deleted file mode 100644 index 8dc1896a1..000000000 --- a/src/Utils/GalleryUtils.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { IChoiceGroupOption, IChoiceGroupProps, IProgressIndicatorProps } from "@fluentui/react"; -import { Notebook } from "@nteract/commutable"; -import { NotebookV4 } from "@nteract/commutable/lib/v4"; -import { HttpStatusCodes } from "../Common/Constants"; -import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; -import { TextFieldProps, useDialog } from "../Explorer/Controls/Dialog"; -import { - GalleryTab, - GalleryViewerComponent, - SortBy, -} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; -import Explorer from "../Explorer/Explorer"; -import { useNotebook } from "../Explorer/Notebook/useNotebook"; -import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; -import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; -import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor"; -import { logConsoleInfo, logConsoleProgress } from "./NotificationConsoleUtils"; - -const defaultSelectedAbuseCategory = "Other"; -const abuseCategories: IChoiceGroupOption[] = [ - { - key: "ChildEndangermentExploitation", - text: "Child endangerment or exploitation", - }, - { - key: "ContentInfringement", - text: "Content infringement", - }, - { - key: "OffensiveContent", - text: "Offensive content", - }, - { - key: "Terrorism", - text: "Terrorism", - }, - { - key: "ThreatsCyberbullyingHarassment", - text: "Threats, cyber bullying or harassment", - }, - { - key: "VirusSpywareMalware", - text: "Virus, spyware or malware", - }, - { - key: "Fraud", - text: "Fraud", - }, - { - key: "HateSpeech", - text: "Hate speech", - }, - { - key: "ImminentHarmToPersonsOrProperty", - text: "Imminent harm to persons or property", - }, - { - key: "Other", - text: "Other", - }, -]; - -export enum NotebookViewerParams { - NotebookUrl = "notebookUrl", - GalleryItemId = "galleryItemId", - HideInputs = "hideInputs", -} - -export interface NotebookViewerProps { - notebookUrl: string; - galleryItemId: string; - hideInputs: boolean; -} - -export enum GalleryViewerParams { - SelectedTab = "tab", - SortBy = "sort", - SearchText = "q", -} - -export interface GalleryViewerProps { - selectedTab: GalleryTab; - sortBy: SortBy; - searchText: string; -} - -export interface DialogHost { - showOkModalDialog( - title: string, - msg: string, - okLabel: string, - onOk: () => void, - progressIndicatorProps?: IProgressIndicatorProps, - ): void; - - showOkCancelModalDialog( - title: string, - msg: string, - okLabel: string, - onOk: () => void, - cancelLabel: string, - onCancel: () => void, - progressIndicatorProps?: IProgressIndicatorProps, - choiceGroupProps?: IChoiceGroupProps, - textFieldProps?: TextFieldProps, - primaryButtonDisabled?: boolean, - ): void; -} - -export function reportAbuse( - junoClient: JunoClient, - data: IGalleryItem, - dialogHost: DialogHost, - onComplete: (success: boolean) => void, -): void { - trace(Action.NotebooksGalleryClickReportAbuse, ActionModifiers.Mark, { notebookId: data.id }); - - const notebookId = data.id; - let abuseCategory = defaultSelectedAbuseCategory; - let additionalDetails: string; - - dialogHost.showOkCancelModalDialog( - "Report Abuse", - undefined, - "Report Abuse", - async () => { - dialogHost.showOkCancelModalDialog( - "Report Abuse", - `Submitting your report on ${data.name} violating code of conduct`, - "Reporting...", - undefined, - "Cancel", - undefined, - {}, - undefined, - undefined, - true, - ); - - const startKey = traceStart(Action.NotebooksGalleryReportAbuse, { notebookId: data.id, abuseCategory }); - - try { - const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails); - if (response.status !== HttpStatusCodes.Accepted) { - throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`); - } - - dialogHost.showOkModalDialog( - "Report Abuse", - `Your report on ${data.name} has been submitted. Thank you for reporting the violation.`, - "OK", - undefined, - { - percentComplete: 1, - }, - ); - - traceSuccess(Action.NotebooksGalleryReportAbuse, { notebookId: data.id, abuseCategory }, startKey); - - onComplete(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryReportAbuse, - { - notebookId: data.id, - abuseCategory, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - handleError( - error, - "GalleryUtils/reportAbuse", - `Failed to submit report on ${data.name} violating code of conduct`, - ); - - dialogHost.showOkModalDialog( - "Report Abuse", - `Failed to submit report on ${data.name} violating code of conduct`, - "OK", - undefined, - { - percentComplete: 1, - }, - ); - } - }, - "Cancel", - undefined, - undefined, - { - label: "How does this content violate the code of conduct?", - options: abuseCategories, - defaultSelectedKey: defaultSelectedAbuseCategory, - onChange: (_event?: React.FormEvent, option?: IChoiceGroupOption) => { - abuseCategory = option?.key; - }, - }, - { - label: "You can also include additional relevant details on the offensive content", - multiline: true, - rows: 3, - autoAdjustHeight: false, - onChange: (_event: React.FormEvent, newValue?: string) => { - additionalDetails = newValue; - }, - }, - ); -} - -export function downloadItem( - container: Explorer, - junoClient: JunoClient, - data: IGalleryItem, - onComplete: (item: IGalleryItem) => void, -): void { - trace(Action.NotebooksGalleryClickDownload, ActionModifiers.Mark, { - notebookId: data.id, - downloadCount: data.downloads, - isSample: data.isSample, - }); - - const name = data.name; - useDialog.getState().showOkCancelModalDialog( - `Download to ${useNotebook.getState().notebookFolderName}`, - undefined, - "Download", - async () => { - if (useNotebook.getState().isPhoenixNotebooks) { - await container.allocateContainer(); - } - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) { - downloadNotebookItem(name, data, junoClient, container, onComplete); - } else { - useDialog - .getState() - .showOkModalDialog( - "Failed to connect", - "Failed to connect to temporary workspace. Please refresh the page and try again.", - ); - } - }, - "Cancel", - undefined, - container.getDownloadModalContent(name), - ); -} -export async function downloadNotebookItem( - fileName: string, - data: IGalleryItem, - junoClient: JunoClient, - container: Explorer, - onComplete: (item: IGalleryItem) => void, -) { - const clearInProgressMessage = logConsoleProgress( - `Downloading ${fileName} to ${useNotebook.getState().notebookFolderName}`, - ); - const startKey = traceStart(Action.NotebooksGalleryDownload, { - notebookId: data.id, - downloadCount: data.downloads, - isSample: data.isSample, - }); - - try { - const response = await junoClient.getNotebookContent(data.id); - if (!response.data) { - throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`); - } - - const notebook = JSON.parse(response.data) as Notebook; - removeNotebookViewerLink(notebook, data.newCellId); - - if (!data.isSample) { - const metadata = notebook.metadata as { [name: string]: unknown }; - metadata.untrusted = true; - } - - await container.importAndOpenContent(data.name, JSON.stringify(notebook)); - logConsoleInfo(`Successfully downloaded ${data.name} to ${useNotebook.getState().notebookFolderName}`); - - const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id); - if (increaseDownloadResponse.data) { - traceSuccess( - Action.NotebooksGalleryDownload, - { notebookId: data.id, downloadCount: increaseDownloadResponse.data.downloads, isSample: data.isSample }, - startKey, - ); - onComplete(increaseDownloadResponse.data); - } - } catch (error) { - traceFailure( - Action.NotebooksGalleryDownload, - { - notebookId: data.id, - downloadCount: data.downloads, - isSample: data.isSample, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`); - } - - clearInProgressMessage(); -} -export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => { - if (!newCellId) { - return; - } - const notebookV4 = notebook as NotebookV4; - if (notebookV4?.cells[0]?.source[0]?.search(newCellId)) { - notebookV4.cells.splice(0, 1); - notebook = notebookV4; - } -}; - -export async function favoriteItem( - container: Explorer, - junoClient: JunoClient, - data: IGalleryItem, - onComplete: (item: IGalleryItem) => void, -): Promise { - if (container) { - const startKey = traceStart(Action.NotebooksGalleryFavorite, { - notebookId: data.id, - isSample: data.isSample, - favoriteCount: data.favorites, - }); - - try { - const response = await junoClient.favoriteNotebook(data.id); - if (!response.data) { - throw new Error(`Received HTTP ${response.status} when favoriting ${data.name}`); - } - - traceSuccess( - Action.NotebooksGalleryFavorite, - { notebookId: data.id, isSample: data.isSample, favoriteCount: response.data.favorites }, - startKey, - ); - - onComplete(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryFavorite, - { - notebookId: data.id, - isSample: data.isSample, - favoriteCount: data.favorites, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - handleError(error, "GalleryUtils/favoriteItem", `Failed to favorite ${data.name}`); - } - } -} - -export async function unfavoriteItem( - container: Explorer, - junoClient: JunoClient, - data: IGalleryItem, - onComplete: (item: IGalleryItem) => void, -): Promise { - if (container) { - const startKey = traceStart(Action.NotebooksGalleryUnfavorite, { - notebookId: data.id, - isSample: data.isSample, - favoriteCount: data.favorites, - }); - - try { - const response = await junoClient.unfavoriteNotebook(data.id); - if (!response.data) { - throw new Error(`Received HTTP ${response.status} when unfavoriting ${data.name}`); - } - - traceSuccess( - Action.NotebooksGalleryUnfavorite, - { notebookId: data.id, isSample: data.isSample, favoriteCount: response.data.favorites }, - startKey, - ); - - onComplete(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryUnfavorite, - { - notebookId: data.id, - isSample: data.isSample, - favoriteCount: data.favorites, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }, - startKey, - ); - - handleError(error, "GalleryUtils/unfavoriteItem", `Failed to unfavorite ${data.name}`); - } - } -} - -export function deleteItem( - container: Explorer, - junoClient: JunoClient, - data: IGalleryItem, - onComplete: (item: IGalleryItem) => void, - beforeDelete?: () => void, - afterDelete?: () => void, -): void { - if (container) { - trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id }); - - useDialog.getState().showOkCancelModalDialog( - "Remove published notebook", - `Would you like to remove ${data.name} from the gallery?`, - "Remove", - async () => { - if (beforeDelete) { - beforeDelete(); - } - const name = data.name; - const clearInProgressMessage = logConsoleProgress(`Removing ${name} from gallery`); - const startKey = traceStart(Action.NotebooksGalleryDelete, { notebookId: data.id }); - - try { - const response = await junoClient.deleteNotebook(data.id); - if (!response.data) { - throw new Error(`Received HTTP ${response.status} while removing ${name}`); - } - - traceSuccess(Action.NotebooksGalleryDelete, { notebookId: data.id }, startKey); - - logConsoleInfo(`Successfully removed ${name} from gallery`); - onComplete(response.data); - } catch (error) { - traceFailure( - Action.NotebooksGalleryDelete, - { notebookId: data.id, error: getErrorMessage(error), errorStack: getErrorStack(error) }, - startKey, - ); - - handleError(error, "GalleryUtils/deleteItem", `Failed to remove ${name} from gallery`); - } finally { - if (afterDelete) { - afterDelete(); - } - } - - clearInProgressMessage(); - }, - "Cancel", - undefined, - ); - } -} - -export function getGalleryViewerProps(search: string): GalleryViewerProps { - const params = new URLSearchParams(search); - let selectedTab: GalleryTab; - if (params.has(GalleryViewerParams.SelectedTab)) { - selectedTab = GalleryTab[params.get(GalleryViewerParams.SelectedTab) as keyof typeof GalleryTab]; - } - - let sortBy: SortBy; - if (params.has(GalleryViewerParams.SortBy)) { - sortBy = SortBy[params.get(GalleryViewerParams.SortBy) as keyof typeof SortBy]; - } - - return { - selectedTab, - sortBy, - searchText: params.get(GalleryViewerParams.SearchText), - }; -} - -export function getNotebookViewerProps(search: string): NotebookViewerProps { - const params = new URLSearchParams(search); - return { - notebookUrl: params.get(NotebookViewerParams.NotebookUrl), - galleryItemId: params.get(NotebookViewerParams.GalleryItemId), - hideInputs: JSON.parse(params.get(NotebookViewerParams.HideInputs)), - }; -} - -export function getTabTitle(tab: GalleryTab): string { - switch (tab) { - case GalleryTab.PublicGallery: - return GalleryViewerComponent.PublicGalleryTitle; - case GalleryTab.OfficialSamples: - return GalleryViewerComponent.OfficialSamplesTitle; - case GalleryTab.Favorites: - return GalleryViewerComponent.FavoritesTitle; - case GalleryTab.Published: - return GalleryViewerComponent.PublishedTitle; - default: - throw new Error(`Unknown tab ${tab}`); - } -} - -export function filterPublishedNotebooks(items: IGalleryItem[]): { - published: IGalleryItem[]; - underReview: IGalleryItem[]; - removed: IGalleryItem[]; -} { - const underReview: IGalleryItem[] = []; - const removed: IGalleryItem[] = []; - const published: IGalleryItem[] = []; - - items?.forEach((item) => { - if (item.policyViolations?.length > 0) { - removed.push(item); - } else if (item.pendingScanJobIds?.length > 0) { - underReview.push(item); - } else { - published.push(item); - } - }); - - return { published, underReview, removed }; -} diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index bc4184cf1..d4a4f9555 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -1008,9 +1008,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) { userContext.features.notebooksDownBanner = true; } - if (inputs.flights.indexOf(Flights.PublicGallery) !== -1) { - userContext.features.publicGallery = true; - } } // Handle initial theme from portal diff --git a/webpack.config.js b/webpack.config.js index 3f1056546..19f90200a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -113,7 +113,6 @@ module.exports = function (_env = {}, argv = {}) { hostedExplorer: "./src/HostedExplorer.tsx", terminal: "./src/Terminal/index.ts", cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx", - galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx", selfServe: "./src/SelfServe/SelfServe.tsx", connectToGitHub: "./src/GitHub/GitHubConnector.ts", redirectBridge: "./src/redirectBridge.ts", @@ -154,11 +153,6 @@ module.exports = function (_env = {}, argv = {}) { template: "src/CellOutputViewer/cellOutputViewer.html", chunks: ["cellOutputViewer"], }), - new HtmlWebpackPlugin({ - filename: "gallery.html", - template: "src/GalleryViewer/galleryViewer.html", - chunks: ["galleryViewer"], - }), new HtmlWebpackPlugin({ filename: "connectToGitHub.html", template: "src/connectToGitHub.html",