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)
This commit is contained in:
parent
821f665e78
commit
bd00e5eb9b
|
@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => {
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
showDownload: true,
|
showDownload: true,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
IPivotItemProps,
|
IPivotItemProps,
|
||||||
|
@ -11,7 +12,8 @@ import {
|
||||||
Pivot,
|
Pivot,
|
||||||
PivotItem,
|
PivotItem,
|
||||||
SearchBox,
|
SearchBox,
|
||||||
Stack
|
Stack,
|
||||||
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
|
@ -151,7 +153,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
if (this.state.isCodeOfConductAccepted !== false) {
|
if (this.state.isCodeOfConductAccepted !== false) {
|
||||||
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,10 +199,59 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createTabContent(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
content: this.createPublishedNotebooksTabContent(data)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
|
||||||
|
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
|
||||||
|
const content = (
|
||||||
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
|
{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)
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.createSearchBarHeader(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksSectionContent = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
content: JSX.Element
|
||||||
|
): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
|
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
|
||||||
|
{description && <Text>{description}</Text>}
|
||||||
|
{content}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
||||||
return acceptedCodeOfConduct === false ? (
|
return acceptedCodeOfConduct === false ? (
|
||||||
<CodeOfConductComponent
|
<CodeOfConductComponent
|
||||||
|
@ -210,11 +261,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
this.createTabContent(data)
|
this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTabContent(data: IGalleryItem[]): JSX.Element {
|
private createSearchBarHeader(content: JSX.Element): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
||||||
|
@ -233,7 +284,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{data && this.createCardsTabContent(data)}
|
<Stack.Item>{content}</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -251,6 +302,25 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Policy violations</th>
|
||||||
|
</tr>
|
||||||
|
{data.map(item => (
|
||||||
|
<tr key={`policy-violations-tr-${item.id}`}>
|
||||||
|
<td>{item.name}</td>
|
||||||
|
<td>{item.policyViolations.join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case GalleryTab.OfficialSamples:
|
case GalleryTab.OfficialSamples:
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
{this.props.onReportAbuseClick !== undefined && (
|
{this.props.onReportAbuseClick && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|
|
@ -81,6 +81,21 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</StackItem>
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<StackItem>
|
||||||
|
<FocusZone
|
||||||
|
direction={2}
|
||||||
|
isCircularNavigation={false}
|
||||||
|
shouldRaiseClicks={true}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
getPageSpecification={[Function]}
|
||||||
|
onRenderCell={[Function]}
|
||||||
|
renderedWindowsAhead={3}
|
||||||
|
renderedWindowsBehind={2}
|
||||||
|
startIndex={0}
|
||||||
|
/>
|
||||||
|
</FocusZone>
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</StyledPivotBase>
|
</StyledPivotBase>
|
||||||
|
|
|
@ -18,7 +18,9 @@ describe("NotebookMetadataComponent", () => {
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
|
@ -48,7 +50,9 @@ describe("NotebookMetadataComponent", () => {
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
|
|
|
@ -140,10 +140,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async submit(): Promise<void> {
|
public async submit(): Promise<void> {
|
||||||
const notificationId = NotificationConsoleUtils.logConsoleMessage(
|
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${this.name} to gallery`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Publishing ${this.name} to gallery`
|
|
||||||
);
|
|
||||||
this.isExecuting = true;
|
this.isExecuting = true;
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
|
|
||||||
|
@ -161,8 +158,16 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||||
this.content,
|
this.content,
|
||||||
this.isLinkInjectionEnabled
|
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) {
|
} catch (error) {
|
||||||
this.formError = `Failed to publish ${this.name} to gallery`;
|
this.formError = `Failed to publish ${this.name} to gallery`;
|
||||||
|
@ -170,10 +175,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||||
|
|
||||||
const message = `${this.formError}: ${this.formErrorDetail}`;
|
const message = `${this.formError}: ${this.formErrorDetail}`;
|
||||||
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
|
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
NotificationConsoleUtils.logConsoleError(message);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
clearPublishingMessage();
|
||||||
this.isExecuting = false;
|
this.isExecuting = false;
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,7 +296,9 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
}}
|
}}
|
||||||
isFavorite={false}
|
isFavorite={false}
|
||||||
showDownload={true}
|
showDownload={true}
|
||||||
|
|
|
@ -103,6 +103,8 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
|
||||||
"isSample": false,
|
"isSample": false,
|
||||||
"name": "SampleNotebook.ipynb",
|
"name": "SampleNotebook.ipynb",
|
||||||
"newCellId": undefined,
|
"newCellId": undefined,
|
||||||
|
"pendingScanJobIds": undefined,
|
||||||
|
"policyViolations": undefined,
|
||||||
"tags": Array [
|
"tags": Array [
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { IPinnedRepo, JunoClient, IGalleryItem } from "./JunoClient";
|
import { IPinnedRepo, JunoClient } from "./JunoClient";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
|
@ -33,24 +33,6 @@ const samplePinnedRepos: IPinnedRepo[] = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const sampleGalleryItems: 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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
describe("Pinned repos", () => {
|
describe("Pinned repos", () => {
|
||||||
const junoClient = new JunoClient(ko.observable<DatabaseAccount>(sampleDatabaseAccount));
|
const junoClient = new JunoClient(ko.observable<DatabaseAccount>(sampleDatabaseAccount));
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ export interface IGalleryItem {
|
||||||
favorites: number;
|
favorites: number;
|
||||||
views: number;
|
views: number;
|
||||||
newCellId: string;
|
newCellId: string;
|
||||||
|
policyViolations: string[];
|
||||||
|
pendingScanJobIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPublicGalleryData {
|
export interface IPublicGalleryData {
|
||||||
|
|
|
@ -17,7 +17,9 @@ const galleryItem: IGalleryItem = {
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("GalleryUtils", () => {
|
describe("GalleryUtils", () => {
|
||||||
|
|
|
@ -323,3 +323,27 @@ export function getTabTitle(tab: GalleryTab): string {
|
||||||
throw new Error(`Unknown tab ${tab}`);
|
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 };
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue