mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-28 16:36:46 +00:00
Add Report Abuse dialog for public gallery notebooks (#265)
![image](https://user-images.githubusercontent.com/693092/95408825-3975a680-08d5-11eb-812b-80f922ab9fc8.png)
This commit is contained in:
parent
daba1c4ed4
commit
3b64d75322
@ -3,7 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
|
|||||||
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
||||||
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import { Link } from "office-ui-fabric-react/lib/Link";
|
import { Link } from "office-ui-fabric-react/lib/Link";
|
||||||
import { FontIcon } from "office-ui-fabric-react";
|
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -24,6 +24,7 @@ export interface DialogProps {
|
|||||||
subText: string;
|
subText: string;
|
||||||
isModal: boolean;
|
isModal: boolean;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
choiceGroupProps?: IChoiceGroupProps;
|
||||||
textFieldProps?: TextFieldProps;
|
textFieldProps?: TextFieldProps;
|
||||||
linkProps?: LinkProps;
|
linkProps?: LinkProps;
|
||||||
primaryButtonText: string;
|
primaryButtonText: string;
|
||||||
@ -65,6 +66,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
minWidth: DIALOG_MIN_WIDTH,
|
minWidth: DIALOG_MIN_WIDTH,
|
||||||
maxWidth: DIALOG_MAX_WIDTH
|
maxWidth: DIALOG_MAX_WIDTH
|
||||||
};
|
};
|
||||||
|
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
|
||||||
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
||||||
const linkProps: LinkProps = this.props.linkProps;
|
const linkProps: LinkProps = this.props.linkProps;
|
||||||
const primaryButtonProps: IButtonProps = {
|
const primaryButtonProps: IButtonProps = {
|
||||||
@ -82,6 +84,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps}>
|
<Dialog {...dialogProps}>
|
||||||
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
{textFieldProps && <TextField {...textFieldProps} />}
|
{textFieldProps && <TextField {...textFieldProps} />}
|
||||||
{linkProps && (
|
{linkProps && (
|
||||||
<Link href={linkProps.linkUrl} target="_blank">
|
<Link href={linkProps.linkUrl} target="_blank">
|
||||||
|
@ -227,7 +227,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
||||||
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
|
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
{this.props.container?.isGalleryPublishEnabled() && (
|
{(!this.props.container || this.props.container.isGalleryPublishEnabled()) && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
@ -3,10 +3,14 @@ import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "office-ui-fa
|
|||||||
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
|
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
|
||||||
import "./InfoComponent.less";
|
import "./InfoComponent.less";
|
||||||
|
|
||||||
export class InfoComponent extends React.Component {
|
export interface InfoComponentProps {
|
||||||
private getInfoPanel = (iconName: string, labelText: string, url: string): JSX.Element => {
|
onReportAbuseClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InfoComponent extends React.Component<InfoComponentProps> {
|
||||||
|
private getInfoPanel = (iconName: string, labelText: string, url?: string, onClick?: () => void): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Link href={url} target="_blank">
|
<Link href={url} target={url && "_blank"} onClick={onClick}>
|
||||||
<div className="infoPanel">
|
<div className="infoPanel">
|
||||||
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
|
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
|
||||||
<Label className="infoLabel">{labelText}</Label>
|
<Label className="infoLabel">{labelText}</Label>
|
||||||
@ -25,6 +29,11 @@ export class InfoComponent extends React.Component {
|
|||||||
<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 && (
|
||||||
|
<Stack.Item>
|
||||||
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -77,6 +77,9 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
selectedKey={0}
|
selectedKey={0}
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
|
@ -25,7 +25,8 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
onTagClick: undefined,
|
onTagClick: undefined,
|
||||||
onDownloadClick: undefined,
|
onDownloadClick: undefined,
|
||||||
onFavoriteClick: undefined,
|
onFavoriteClick: undefined,
|
||||||
onUnfavoriteClick: undefined
|
onUnfavoriteClick: undefined,
|
||||||
|
onReportAbuseClick: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||||
@ -54,7 +55,8 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
onTagClick: undefined,
|
onTagClick: undefined,
|
||||||
onDownloadClick: undefined,
|
onDownloadClick: undefined,
|
||||||
onFavoriteClick: undefined,
|
onFavoriteClick: undefined,
|
||||||
onUnfavoriteClick: undefined
|
onUnfavoriteClick: undefined,
|
||||||
|
onReportAbuseClick: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||||
|
@ -17,6 +17,7 @@ import { IGalleryItem } from "../../../Juno/JunoClient";
|
|||||||
import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
|
import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
|
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
|
||||||
|
import { InfoComponent } from "../NotebookGallery/InfoComponent/InfoComponent";
|
||||||
|
|
||||||
export interface NotebookMetadataComponentProps {
|
export interface NotebookMetadataComponentProps {
|
||||||
data: IGalleryItem;
|
data: IGalleryItem;
|
||||||
@ -26,6 +27,7 @@ export interface NotebookMetadataComponentProps {
|
|||||||
onFavoriteClick: () => void;
|
onFavoriteClick: () => void;
|
||||||
onUnfavoriteClick: () => void;
|
onUnfavoriteClick: () => void;
|
||||||
onDownloadClick: () => void;
|
onDownloadClick: () => void;
|
||||||
|
onReportAbuseClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
|
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
|
||||||
@ -41,24 +43,39 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
|
|||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
||||||
<Text variant="xxLarge" nowrap>
|
<Stack.Item>
|
||||||
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
<Text variant="xxLarge" nowrap>
|
||||||
</Text>
|
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
||||||
<Text>
|
</Text>
|
||||||
{this.props.isFavorite !== undefined && (
|
</Stack.Item>
|
||||||
<>
|
|
||||||
<IconButton
|
<Stack.Item>
|
||||||
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
<Text>
|
||||||
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
{this.props.isFavorite !== undefined && (
|
||||||
/>
|
<>
|
||||||
{this.props.data.favorites} likes
|
<IconButton
|
||||||
</>
|
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
||||||
)}
|
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
||||||
</Text>
|
/>
|
||||||
|
{this.props.data.favorites} likes
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
{this.props.downloadButtonText && (
|
{this.props.downloadButtonText && (
|
||||||
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
<Stack.Item>
|
||||||
|
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
||||||
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Stack.Item grow>
|
||||||
|
<></>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<InfoComponent onReportAbuseClick={this.props.onReportAbuseClick} />
|
||||||
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
import { Notebook } from "@nteract/commutable";
|
import { Notebook } from "@nteract/commutable";
|
||||||
import { createContentRef } from "@nteract/core";
|
import { createContentRef } from "@nteract/core";
|
||||||
import { Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
|
import { IChoiceGroupProps, Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { contents } from "rx-jupyter";
|
import { contents } from "rx-jupyter";
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
@ -15,12 +14,13 @@ import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationCon
|
|||||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||||
|
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||||
|
|
||||||
export interface NotebookViewerComponentProps {
|
export interface NotebookViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@ -43,10 +43,8 @@ interface NotebookViewerComponentState {
|
|||||||
showProgressBar: boolean;
|
showProgressBar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookViewerComponent extends React.Component<
|
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
|
||||||
NotebookViewerComponentProps,
|
implements DialogHost {
|
||||||
NotebookViewerComponentState
|
|
||||||
> {
|
|
||||||
private clientManager: NotebookClientV2;
|
private clientManager: NotebookClientV2;
|
||||||
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
||||||
|
|
||||||
@ -140,6 +138,7 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
onFavoriteClick={this.favoriteItem}
|
onFavoriteClick={this.favoriteItem}
|
||||||
onUnfavoriteClick={this.unfavoriteItem}
|
onUnfavoriteClick={this.unfavoriteItem}
|
||||||
onDownloadClick={this.downloadItem}
|
onDownloadClick={this.downloadItem}
|
||||||
|
onReportAbuseClick={this.state.galleryItem.isSample ? undefined : this.reportAbuse}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -179,6 +178,39 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialogHost
|
||||||
|
showOkCancelModalDialog(
|
||||||
|
title: string,
|
||||||
|
msg: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps
|
||||||
|
): void {
|
||||||
|
this.setState({
|
||||||
|
dialogProps: {
|
||||||
|
isModal: true,
|
||||||
|
visible: true,
|
||||||
|
title,
|
||||||
|
subText: msg,
|
||||||
|
primaryButtonText: okLabel,
|
||||||
|
secondaryButtonText: cancelLabel,
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
this.setState({ dialogProps: undefined });
|
||||||
|
onOk && onOk();
|
||||||
|
},
|
||||||
|
onSecondaryButtonClick: () => {
|
||||||
|
this.setState({ dialogProps: undefined });
|
||||||
|
onCancel && onCancel();
|
||||||
|
},
|
||||||
|
choiceGroupProps,
|
||||||
|
textFieldProps
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private favoriteItem = async (): Promise<void> => {
|
private favoriteItem = async (): Promise<void> => {
|
||||||
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
|
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
|
||||||
this.setState({ galleryItem: item, isFavorite: true })
|
this.setState({ galleryItem: item, isFavorite: true })
|
||||||
@ -196,4 +228,8 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
this.setState({ galleryItem: item })
|
this.setState({ galleryItem: item })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private reportAbuse = (): void => {
|
||||||
|
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,26 +17,38 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<Text
|
<StackItem>
|
||||||
nowrap={true}
|
<Text
|
||||||
variant="xxLarge"
|
nowrap={true}
|
||||||
>
|
variant="xxLarge"
|
||||||
name
|
>
|
||||||
</Text>
|
name
|
||||||
<Text>
|
</Text>
|
||||||
<CustomizedIconButton
|
</StackItem>
|
||||||
iconProps={
|
<StackItem>
|
||||||
Object {
|
<Text>
|
||||||
"iconName": "HeartFill",
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "HeartFill",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
|
0
|
||||||
|
likes
|
||||||
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<CustomizedPrimaryButton
|
||||||
|
text="Download"
|
||||||
/>
|
/>
|
||||||
0
|
</StackItem>
|
||||||
likes
|
<StackItem
|
||||||
</Text>
|
grow={true}
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
@ -117,26 +129,38 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<Text
|
<StackItem>
|
||||||
nowrap={true}
|
<Text
|
||||||
variant="xxLarge"
|
nowrap={true}
|
||||||
>
|
variant="xxLarge"
|
||||||
name
|
>
|
||||||
</Text>
|
name
|
||||||
<Text>
|
</Text>
|
||||||
<CustomizedIconButton
|
</StackItem>
|
||||||
iconProps={
|
<StackItem>
|
||||||
Object {
|
<Text>
|
||||||
"iconName": "Heart",
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Heart",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
|
0
|
||||||
|
likes
|
||||||
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<CustomizedPrimaryButton
|
||||||
|
text="Download"
|
||||||
/>
|
/>
|
||||||
0
|
</StackItem>
|
||||||
likes
|
<StackItem
|
||||||
</Text>
|
grow={true}
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
|
@ -88,6 +88,7 @@ import TabsBase from "./Tabs/TabsBase";
|
|||||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
|
import { IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
@ -2385,42 +2386,16 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public showOkCancelModalDialog(
|
public showOkCancelModalDialog(
|
||||||
title: string,
|
|
||||||
msg: string,
|
|
||||||
okLabel: string,
|
|
||||||
onOk: () => void,
|
|
||||||
cancelLabel: string,
|
|
||||||
onCancel: () => void
|
|
||||||
): void {
|
|
||||||
this._dialogProps({
|
|
||||||
isModal: true,
|
|
||||||
visible: true,
|
|
||||||
title,
|
|
||||||
subText: msg,
|
|
||||||
primaryButtonText: okLabel,
|
|
||||||
secondaryButtonText: cancelLabel,
|
|
||||||
onPrimaryButtonClick: () => {
|
|
||||||
this._closeModalDialog();
|
|
||||||
onOk && onOk();
|
|
||||||
},
|
|
||||||
onSecondaryButtonClick: () => {
|
|
||||||
this._closeModalDialog();
|
|
||||||
onCancel && onCancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public showOkCancelTextFieldModalDialog(
|
|
||||||
title: string,
|
title: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
okLabel: string,
|
okLabel: string,
|
||||||
onOk: () => void,
|
onOk: () => void,
|
||||||
cancelLabel: string,
|
cancelLabel: string,
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
textFieldProps: TextFieldProps,
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps,
|
||||||
isPrimaryButtonDisabled?: boolean
|
isPrimaryButtonDisabled?: boolean
|
||||||
): void {
|
): void {
|
||||||
let textFieldValue: string = null;
|
|
||||||
this._dialogProps({
|
this._dialogProps({
|
||||||
isModal: true,
|
isModal: true,
|
||||||
visible: true,
|
visible: true,
|
||||||
@ -2436,8 +2411,9 @@ export default class Explorer {
|
|||||||
this._closeModalDialog();
|
this._closeModalDialog();
|
||||||
onCancel && onCancel();
|
onCancel && onCancel();
|
||||||
},
|
},
|
||||||
primaryButtonDisabled: isPrimaryButtonDisabled,
|
choiceGroupProps,
|
||||||
textFieldProps
|
textFieldProps,
|
||||||
|
primaryButtonDisabled: isPrimaryButtonDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ export default class NotebookManager {
|
|||||||
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
|
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
|
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
|
||||||
this.params.container.showOkCancelTextFieldModalDialog(
|
this.params.container.showOkCancelModalDialog(
|
||||||
title || "Commit",
|
title || "Commit",
|
||||||
undefined,
|
undefined,
|
||||||
primaryButtonLabel || "Commit",
|
primaryButtonLabel || "Commit",
|
||||||
@ -181,6 +181,7 @@ export default class NotebookManager {
|
|||||||
},
|
},
|
||||||
"Cancel",
|
"Cancel",
|
||||||
() => reject(new Error("Commit dialog canceled")),
|
() => reject(new Error("Commit dialog canceled")),
|
||||||
|
undefined,
|
||||||
{
|
{
|
||||||
label: "Commit message",
|
label: "Commit message",
|
||||||
autoAdjustHeight: true,
|
autoAdjustHeight: true,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { IPinnedRepo, JunoClient, IGalleryItem } from "./JunoClient";
|
import { IPinnedRepo, JunoClient, IGalleryItem } from "./JunoClient";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
@ -237,7 +236,7 @@ describe("Gallery", () => {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -260,7 +259,7 @@ describe("Gallery", () => {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -281,7 +280,7 @@ describe("Gallery", () => {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -299,7 +298,7 @@ describe("Gallery", () => {
|
|||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/favorites`, {
|
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/favorites`, {
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -317,7 +316,7 @@ describe("Gallery", () => {
|
|||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/published`, {
|
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/published`, {
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -337,7 +336,7 @@ describe("Gallery", () => {
|
|||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -364,7 +363,7 @@ describe("Gallery", () => {
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name,
|
name,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
||||||
@ -404,6 +404,30 @@ export class JunoClient {
|
|||||||
return `${this.getNotebooksUrl()}/gallery/${id}`;
|
return `${this.getNotebooksUrl()}/gallery/${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
|
||||||
|
const response = await window.fetch(`${this.getNotebooksUrl()}/avert/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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async getNotebooks(input: RequestInfo, init?: RequestInit): Promise<IJunoResponse<IGalleryItem[]>> {
|
private async getNotebooks(input: RequestInfo, init?: RequestInit): Promise<IJunoResponse<IGalleryItem[]>> {
|
||||||
const response = await window.fetch(input, init);
|
const response = await window.fetch(input, init);
|
||||||
|
|
||||||
@ -430,7 +454,7 @@ export class JunoClient {
|
|||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
return {
|
return {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
"content-type": "application/json"
|
[HttpHeaders.contentType]: "application/json"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,52 @@ import {
|
|||||||
GalleryViewerComponent
|
GalleryViewerComponent
|
||||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
|
||||||
|
|
||||||
|
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 {
|
export enum NotebookViewerParams {
|
||||||
NotebookUrl = "notebookUrl",
|
NotebookUrl = "notebookUrl",
|
||||||
@ -33,6 +79,78 @@ export interface GalleryViewerProps {
|
|||||||
searchText: string;
|
searchText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DialogHost {
|
||||||
|
showOkCancelModalDialog(
|
||||||
|
title: string,
|
||||||
|
msg: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reportAbuse(
|
||||||
|
junoClient: JunoClient,
|
||||||
|
data: IGalleryItem,
|
||||||
|
dialogHost: DialogHost,
|
||||||
|
onComplete: (success: boolean) => void
|
||||||
|
): void {
|
||||||
|
const notebookId = data.id;
|
||||||
|
let abuseCategory = defaultSelectedAbuseCategory;
|
||||||
|
let additionalDetails: string;
|
||||||
|
|
||||||
|
dialogHost.showOkCancelModalDialog(
|
||||||
|
"Report Abuse",
|
||||||
|
undefined,
|
||||||
|
"Report Abuse",
|
||||||
|
async () => {
|
||||||
|
const clearSubmitReportNotification = NotificationConsoleUtils.logConsoleProgress(
|
||||||
|
`Submitting your report on ${data.name} violating code of conduct`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails);
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(
|
||||||
|
`Your report on ${data.name} has been submitted. Thank you for reporting the violation.`
|
||||||
|
);
|
||||||
|
onComplete(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
const message = `Failed to submit report on ${data.name} violating code of conduct: ${error}`;
|
||||||
|
Logger.logError(message, "GalleryUtils/reportAbuse");
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSubmitReportNotification();
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
label: "How does this content violate the code of conduct?",
|
||||||
|
options: abuseCategories,
|
||||||
|
defaultSelectedKey: defaultSelectedAbuseCategory,
|
||||||
|
onChange: (_event?: React.FormEvent<HTMLElement | HTMLInputElement>, 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<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
|
||||||
|
additionalDetails = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function downloadItem(
|
export function downloadItem(
|
||||||
container: Explorer,
|
container: Explorer,
|
||||||
junoClient: JunoClient,
|
junoClient: JunoClient,
|
||||||
|
Loading…
Reference in New Issue
Block a user