From bd00e5eb9b444e04053610e22fd905c815a8f45f Mon Sep 17 00:00:00 2001 From: Tanuj Mittal Date: Wed, 14 Oct 2020 20:49:18 -0700 Subject: [PATCH] Support async notebook publishing (#275) Handle cases for async notebook publishing. Now `Your published work` tab shows 3 sections - published, under review, and removed notebooks. Note: The text labels are design placeholders ![image](https://user-images.githubusercontent.com/693092/95799994-3b5fb100-0cab-11eb-86fc-4ded0aeeddb1.png) --- .../Cards/GalleryCardComponent.test.tsx | 4 +- .../GalleryViewerComponent.tsx | 82 +++++++++++++++++-- .../InfoComponent/InfoComponent.tsx | 2 +- .../GalleryViewerComponent.test.tsx.snap | 15 ++++ .../NotebookMetadataComponent.test.tsx | 8 +- .../Panes/PublishNotebookPaneAdapter.tsx | 21 +++-- .../Panes/PublishNotebookPaneComponent.tsx | 4 +- ...PublishNotebookPaneComponent.test.tsx.snap | 2 + src/Juno/JunoClient.test.ts | 20 +---- src/Juno/JunoClient.ts | 2 + src/Utils/GalleryUtils.test.ts | 4 +- src/Utils/GalleryUtils.ts | 24 ++++++ 12 files changed, 149 insertions(+), 39 deletions(-) diff --git a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx index d8e6b95bd..31b77de10 100644 --- a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx +++ b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.test.tsx @@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => { downloads: 0, favorites: 0, views: 0, - newCellId: undefined + newCellId: undefined, + policyViolations: undefined, + pendingScanJobIds: undefined }, isFavorite: false, showDownload: true, diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx index 2ba19a736..29a9d7116 100644 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx @@ -1,6 +1,7 @@ import { Dropdown, FocusZone, + FontWeights, IDropdownOption, IPageSpecification, IPivotItemProps, @@ -11,7 +12,8 @@ import { Pivot, PivotItem, SearchBox, - Stack + Stack, + Text } from "office-ui-fabric-react"; import * as React from "react"; import * as Logger from "../../../Common/Logger"; @@ -151,7 +153,7 @@ export class GalleryViewerComponent extends React.Component { + return { + tab, + content: this.createPublishedNotebooksTabContent(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 the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.", + 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 acceptedCodeOfConduct === false ? ( ) : ( - this.createTabContent(data) + this.createSearchBarHeader(this.createCardsTabContent(data)) ); } - private createTabContent(data: IGalleryItem[]): JSX.Element { + private createSearchBarHeader(content: JSX.Element): JSX.Element { return ( @@ -233,7 +284,7 @@ export class GalleryViewerComponent extends React.Component )} - {data && this.createCardsTabContent(data)} + {content} ); } @@ -251,6 +302,25 @@ export class GalleryViewerComponent extends React.Component + + + Name + Policy violations + + {data.map(item => ( + + {item.name} + {item.policyViolations.join(", ")} + + ))} + + + ); + } + private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void { switch (tab) { case GalleryTab.OfficialSamples: diff --git a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx b/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx index 1d10a49a7..50ac0b4e4 100644 --- a/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/InfoComponent/InfoComponent.tsx @@ -29,7 +29,7 @@ export class InfoComponent extends React.Component { {this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)} - {this.props.onReportAbuseClick !== undefined && ( + {this.props.onReportAbuseClick && ( {this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())} diff --git a/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap index 969fa1676..17ae29657 100644 --- a/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap +++ b/src/Explorer/Controls/NotebookGallery/__snapshots__/GalleryViewerComponent.test.tsx.snap @@ -81,6 +81,21 @@ exports[`GalleryViewerComponent renders 1`] = ` + + + + + diff --git a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx index 2de6e4ded..5f288bda5 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.test.tsx @@ -18,7 +18,9 @@ describe("NotebookMetadataComponent", () => { downloads: 0, favorites: 0, views: 0, - newCellId: undefined + newCellId: undefined, + policyViolations: undefined, + pendingScanJobIds: undefined }, isFavorite: false, downloadButtonText: "Download", @@ -48,7 +50,9 @@ describe("NotebookMetadataComponent", () => { downloads: 0, favorites: 0, views: 0, - newCellId: undefined + newCellId: undefined, + policyViolations: undefined, + pendingScanJobIds: undefined }, isFavorite: true, downloadButtonText: "Download", diff --git a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx index 224b43c67..c8f94f7d2 100644 --- a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx @@ -140,10 +140,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter { } public async submit(): Promise { - const notificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Publishing ${this.name} to gallery` - ); + const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${this.name} to gallery`); this.isExecuting = true; this.triggerRender(); @@ -161,8 +158,16 @@ export class PublishNotebookPaneAdapter implements ReactAdapter { this.content, this.isLinkInjectionEnabled ); - if (response.data) { - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Published ${name} to gallery`); + + const data = response.data; + if (data) { + if (data.pendingScanJobIds?.length > 0) { + NotificationConsoleUtils.logConsoleInfo( + `Content of ${this.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 ${this.name} to gallery`); + } } } catch (error) { this.formError = `Failed to publish ${this.name} to gallery`; @@ -170,10 +175,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter { const message = `${this.formError}: ${this.formErrorDetail}`; Logger.logError(message, "PublishNotebookPaneAdapter/submit"); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message); + NotificationConsoleUtils.logConsoleError(message); return; } finally { - NotificationConsoleUtils.clearInProgressMessageWithId(notificationId); + clearPublishingMessage(); this.isExecuting = false; this.triggerRender(); } diff --git a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx index 1980e7a77..a0261cbf5 100644 --- a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx @@ -296,7 +296,9 @@ export class PublishNotebookPaneComponent extends React.Component { const junoClient = new JunoClient(ko.observable(sampleDatabaseAccount)); diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 987740e52..e0fc26884 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -37,6 +37,8 @@ export interface IGalleryItem { favorites: number; views: number; newCellId: string; + policyViolations: string[]; + pendingScanJobIds: string[]; } export interface IPublicGalleryData { diff --git a/src/Utils/GalleryUtils.test.ts b/src/Utils/GalleryUtils.test.ts index 95365af40..6cf7332df 100644 --- a/src/Utils/GalleryUtils.test.ts +++ b/src/Utils/GalleryUtils.test.ts @@ -17,7 +17,9 @@ const galleryItem: IGalleryItem = { downloads: 0, favorites: 0, views: 0, - newCellId: undefined + newCellId: undefined, + policyViolations: undefined, + pendingScanJobIds: undefined }; describe("GalleryUtils", () => { diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts index 31b2d2a99..723b04144 100644 --- a/src/Utils/GalleryUtils.ts +++ b/src/Utils/GalleryUtils.ts @@ -323,3 +323,27 @@ export function getTabTitle(tab: GalleryTab): string { 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 }; +}