mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Add telemetry for Notebooks Gallery and other updates (#413)
* Add telemetry for Notebooks Gallery * More changes * Address feedback and fix lint error * Fix margins for My published work
This commit is contained in:
@@ -79,12 +79,16 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||
|
||||
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
|
||||
<Text variant="small" nowrap>
|
||||
{this.props.data.tags?.map((tag, index, array) => (
|
||||
<span key={tag}>
|
||||
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
|
||||
{index === array.length - 1 ? <></> : ", "}
|
||||
</span>
|
||||
))}
|
||||
{this.props.data.tags ? (
|
||||
this.props.data.tags.map((tag, index, array) => (
|
||||
<span key={tag}>
|
||||
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
|
||||
{index === array.length - 1 ? <></> : ", "}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<br />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
|
||||
@@ -2,7 +2,9 @@ import * as React from "react";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
export interface CodeOfConductComponentProps {
|
||||
junoClient: JunoClient;
|
||||
@@ -14,11 +16,11 @@ interface CodeOfConductComponentState {
|
||||
}
|
||||
|
||||
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
|
||||
private viewCodeOfConductTraced: boolean;
|
||||
private descriptionPara1: string;
|
||||
private descriptionPara2: string;
|
||||
private descriptionPara3: string;
|
||||
private link1: { label: string; url: string };
|
||||
private link2: { label: string; url: string };
|
||||
|
||||
constructor(props: CodeOfConductComponentProps) {
|
||||
super(props);
|
||||
@@ -27,23 +29,34 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
||||
readCodeOfConduct: false,
|
||||
};
|
||||
|
||||
this.descriptionPara1 = "Azure CosmosDB Notebook Gallery - Code of Conduct and Privacy Statement";
|
||||
this.descriptionPara2 =
|
||||
"Azure Cosmos DB Notebook Public Gallery contains notebook samples shared by users of Cosmos DB.";
|
||||
this.descriptionPara3 = "In order to access Azure Cosmos DB Notebook Gallery resources, you must accept the ";
|
||||
this.link1 = { label: "code of conduct", url: CodeOfConductEndpoints.codeOfConduct };
|
||||
this.link2 = { label: "privacy statement", url: CodeOfConductEndpoints.privacyStatement };
|
||||
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
|
||||
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
|
||||
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
|
||||
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
|
||||
}
|
||||
|
||||
private async acceptCodeOfConduct(): Promise<void> {
|
||||
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
|
||||
|
||||
try {
|
||||
const response = await this.props.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);
|
||||
|
||||
this.props.onAcceptCodeOfConduct(response.data);
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryAcceptCodeOfConduct,
|
||||
{
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||
}
|
||||
}
|
||||
@@ -53,6 +66,11 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (!this.viewCodeOfConductTraced) {
|
||||
this.viewCodeOfConductTraced = true;
|
||||
trace(Action.NotebooksGalleryViewCodeOfConduct);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }}>
|
||||
<Stack.Item>
|
||||
@@ -69,10 +87,6 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
||||
<Link href={this.link1.url} target="_blank">
|
||||
{this.link1.label}
|
||||
</Link>
|
||||
{" and "}
|
||||
<Link href={this.link2.url} target="_blank">
|
||||
{this.link2.label}
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
|
||||
@@ -87,7 +101,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
||||
fontSize: 12,
|
||||
},
|
||||
}}
|
||||
label="I have read and accepted the code of conduct and privacy statement"
|
||||
label="I have read and accepted the code of conduct."
|
||||
onChange={this.onChangeCheckbox}
|
||||
/>
|
||||
</Stack.Item>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
IPivotProps,
|
||||
IRectangle,
|
||||
Label,
|
||||
Link,
|
||||
List,
|
||||
Overlay,
|
||||
Pivot,
|
||||
@@ -28,6 +29,8 @@ import Explorer from "../../Explorer";
|
||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
export interface GalleryViewerComponentProps {
|
||||
container?: Explorer;
|
||||
@@ -87,6 +90,12 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
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[];
|
||||
@@ -138,6 +147,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
this.traceViewGallery();
|
||||
|
||||
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
||||
|
||||
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||
@@ -185,11 +196,58 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
);
|
||||
}
|
||||
|
||||
private traceViewGallery = (): void => {
|
||||
if (!this.viewGalleryTraced) {
|
||||
this.viewGalleryTraced = true;
|
||||
trace(Action.NotebooksGalleryViewGallery);
|
||||
}
|
||||
|
||||
switch (this.state.selectedTab) {
|
||||
case GalleryTab.OfficialSamples:
|
||||
if (!this.viewOfficialSamplesTraced) {
|
||||
this.resetViewGalleryTabTracedFlags();
|
||||
this.viewOfficialSamplesTraced = true;
|
||||
trace(Action.NotebooksGalleryViewOfficialSamples);
|
||||
}
|
||||
break;
|
||||
case GalleryTab.PublicGallery:
|
||||
if (!this.viewPublicGalleryTraced) {
|
||||
this.resetViewGalleryTabTracedFlags();
|
||||
this.viewPublicGalleryTraced = true;
|
||||
trace(Action.NotebooksGalleryViewPublicGallery);
|
||||
}
|
||||
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: string, line2: string): JSX.Element => {
|
||||
private createEmptyTabContent = (iconName: string, line1: JSX.Element, line2: JSX.Element): JSX.Element => {
|
||||
return (
|
||||
<Stack horizontalAlign="center" tokens={{ childrenGap: 10 }}>
|
||||
<FontIcon iconName={iconName} style={{ fontSize: 100, color: "lightgray", marginTop: 20 }} />
|
||||
@@ -223,8 +281,12 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
content: this.isEmptyData(data)
|
||||
? this.createEmptyTabContent(
|
||||
"ContactHeart",
|
||||
"You have not favorited anything",
|
||||
"Favorite any notebook from Official samples or Public gallery"
|
||||
<>You don't have any favorites yet</>,
|
||||
<>
|
||||
Favorite any notebook from the{" "}
|
||||
<Link onClick={() => this.setState({ selectedTab: GalleryTab.OfficialSamples })}>official samples</Link>{" "}
|
||||
or <Link onClick={() => this.setState({ selectedTab: GalleryTab.PublicGallery })}>public gallery</Link>
|
||||
</>
|
||||
)
|
||||
: this.createSearchBarHeader(this.createCardsTabContent(data)),
|
||||
};
|
||||
@@ -236,8 +298,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
content: this.isEmptyData(data)
|
||||
? this.createEmptyTabContent(
|
||||
"Contact",
|
||||
"You have not published anything",
|
||||
"Publish your sample notebooks to share your published work with others"
|
||||
<>
|
||||
You have not published anything to the{" "}
|
||||
<Link onClick={() => this.setState({ selectedTab: GalleryTab.PublicGallery })}>public gallery</Link> yet
|
||||
</>,
|
||||
<>Publish your notebooks to share your work with other users</>
|
||||
)
|
||||
: this.createPublishedNotebooksTabContent(data),
|
||||
};
|
||||
@@ -250,7 +315,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
{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.",
|
||||
"You have successfully published and shared the following notebook(s) to the public gallery.",
|
||||
this.createCardsTabContent(published)
|
||||
)}
|
||||
{underReview?.length > 0 &&
|
||||
@@ -278,8 +343,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
|
||||
{description && <Text>{description}</Text>}
|
||||
{title && (
|
||||
<Text styles={{ root: { fontWeight: FontWeights.semibold, marginLeft: 10, marginRight: 10 } }}>{title}</Text>
|
||||
)}
|
||||
{description && <Text styles={{ root: { marginLeft: 10, marginRight: 10 } }}>{description}</Text>}
|
||||
{content}
|
||||
</Stack>
|
||||
);
|
||||
@@ -344,7 +411,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
|
||||
return (
|
||||
<table>
|
||||
<table style={{ margin: 10 }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
||||
@@ -17,35 +17,28 @@ exports[`CodeOfConductComponent renders 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Azure CosmosDB Notebook Gallery - Code of Conduct and Privacy Statement
|
||||
Azure Cosmos DB Notebook Gallery - Code of Conduct
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Text>
|
||||
Azure Cosmos DB Notebook Public Gallery contains notebook samples shared by users of Cosmos DB.
|
||||
The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Text>
|
||||
In order to access Azure Cosmos DB Notebook Gallery resources, you must accept the
|
||||
In order to view and publish your samples to the gallery, you must accept the
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/cosmos-code-of-conduct"
|
||||
target="_blank"
|
||||
>
|
||||
code of conduct
|
||||
</StyledLinkBase>
|
||||
and
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/ms-privacy-policy"
|
||||
target="_blank"
|
||||
>
|
||||
privacy statement
|
||||
code of conduct.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<StyledCheckboxBase
|
||||
label="I have read and accepted the code of conduct and privacy statement"
|
||||
label="I have read and accepted the code of conduct."
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
|
||||
@@ -18,7 +18,9 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
export interface NotebookViewerComponentProps {
|
||||
container?: Explorer;
|
||||
@@ -77,6 +79,11 @@ export class NotebookViewerComponent
|
||||
}
|
||||
|
||||
private async loadNotebookContent(): Promise<void> {
|
||||
const startKey = traceStart(Action.NotebooksGalleryViewNotebook, {
|
||||
notebookUrl: this.props.notebookUrl,
|
||||
notebookId: this.props.galleryItem?.id,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(this.props.notebookUrl);
|
||||
if (!response.ok) {
|
||||
@@ -84,6 +91,12 @@ export class NotebookViewerComponent
|
||||
throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`);
|
||||
}
|
||||
|
||||
traceSuccess(
|
||||
Action.NotebooksGalleryViewNotebook,
|
||||
{ notebookUrl: this.props.notebookUrl, notebookId: this.props.galleryItem?.id },
|
||||
startKey
|
||||
);
|
||||
|
||||
const notebook: Notebook = await response.json();
|
||||
this.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
|
||||
this.notebookComponentBootstrapper.setContent("json", notebook);
|
||||
@@ -98,6 +111,17 @@ export class NotebookViewerComponent
|
||||
SessionStorageUtility.setEntry(this.props.galleryItem?.id, "true");
|
||||
}
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.NotebooksGalleryViewNotebook,
|
||||
{
|
||||
notebookUrl: this.props.notebookUrl,
|
||||
notebookId: this.props.galleryItem?.id,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
this.setState({ showProgressBar: false });
|
||||
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user