Remove enableGallery feature flag (#68)
* Remove enableGallery feature flag * Fix bugs * Add tests to increase coverage * Move favorites functionality behind feature.enableGalleryPublish flag * Show code cells in NotebookViewer * Use cosmos db logo as persona image for sample notebook gallery cards * Update gallery card snapshot to fix test
This commit is contained in:
parent
27024ef75c
commit
84ea3796ec
|
@ -0,0 +1,22 @@
|
||||||
|
<svg id="b089cfca-0de1-451c-a1ca-6680ea50cb4f" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="b25d0836-964a-4c84-8c20-855f66e8345e" cx="-105.006" cy="-10.409" r="5.954" gradientTransform="translate(117.739 19.644) scale(1.036 1.027)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.183" stop-color="#5ea0ef"/>
|
||||||
|
<stop offset="1" stop-color="#0078d4"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="b36c7f5d-2ef1-4760-8a25-eeb9661f4e47">
|
||||||
|
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="none"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<title>Icon-databases-121</title>
|
||||||
|
<path d="M2.954,5.266a.175.175,0,0,1-.176-.176h0A2.012,2.012,0,0,0,.769,3.081a.176.176,0,0,1-.176-.175h0a.176.176,0,0,1,.176-.176A2.012,2.012,0,0,0,2.778.72.175.175,0,0,1,2.954.544h0A.175.175,0,0,1,3.13.72h0A2.012,2.012,0,0,0,5.139,2.729a.175.175,0,0,1,.176.176h0a.175.175,0,0,1-.176.176h0A2.011,2.011,0,0,0,3.13,5.09.177.177,0,0,1,2.954,5.266Z" fill="#50e6ff"/>
|
||||||
|
<path d="M15.611,17.456a.141.141,0,0,1-.141-.141h0a1.609,1.609,0,0,0-1.607-1.607.141.141,0,0,1-.141-.14h0a.141.141,0,0,1,.141-.141h0a1.608,1.608,0,0,0,1.607-1.607.141.141,0,0,1,.141-.141h0a.141.141,0,0,1,.141.141h0a1.608,1.608,0,0,0,1.607,1.607.141.141,0,1,1,0,.282h0a1.609,1.609,0,0,0-1.607,1.607A.141.141,0,0,1,15.611,17.456Z" fill="#50e6ff"/>
|
||||||
|
<g>
|
||||||
|
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="url(#b25d0836-964a-4c84-8c20-855f66e8345e)"/>
|
||||||
|
<g clip-path="url(#b36c7f5d-2ef1-4760-8a25-eeb9661f4e47)">
|
||||||
|
<path d="M5.709,13.115A1.638,1.638,0,1,0,5.714,9.84,1.307,1.307,0,0,0,5.721,9.7,1.651,1.651,0,0,0,4.06,8.064H2.832a6.251,6.251,0,0,0,1.595,5.051Z" fill="#f2f2f2"/>
|
||||||
|
<path d="M15.045,7.815c0-.015,0-.03-.007-.044a5.978,5.978,0,0,0-1.406-2.88,1.825,1.825,0,0,0-.289-.09,1.806,1.806,0,0,0-2.3,1.663,2,2,0,0,0-.2-.013,1.737,1.737,0,0,0-.581,3.374,1.451,1.451,0,0,0,.541.1h2.03A13.453,13.453,0,0,0,15.045,7.815Z" fill="#f2f2f2"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path d="M17.191,3.832c-.629-1.047-2.1-1.455-4.155-1.149a14.606,14.606,0,0,0-2.082.452,6.456,6.456,0,0,1,1.528.767c.241-.053.483-.116.715-.151A7.49,7.49,0,0,1,14.3,3.662a2.188,2.188,0,0,1,1.959.725h0c.383.638.06,1.729-.886,3a16.723,16.723,0,0,1-4.749,4.051A16.758,16.758,0,0,1,4.8,13.7c-1.564.234-2.682,0-3.065-.636s-.06-1.73.886-2.995c.117-.157.146-.234.279-.392a6.252,6.252,0,0,1,.026-1.63A11.552,11.552,0,0,0,1.756,9.419C.517,11.076.181,12.566.809,13.613a3.165,3.165,0,0,0,2.9,1.249,8.434,8.434,0,0,0,1.251-.1,17.855,17.855,0,0,0,6.219-2.4,17.808,17.808,0,0,0,5.061-4.332C17.483,6.369,17.819,4.88,17.191,3.832Z" fill="#50e6ff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -111,7 +111,6 @@ export class Features {
|
||||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
||||||
public static readonly enableTtl = "enablettl";
|
public static readonly enableTtl = "enablettl";
|
||||||
public static readonly enableNotebooks = "enablenotebooks";
|
public static readonly enableNotebooks = "enablenotebooks";
|
||||||
public static readonly enableGallery = "enablegallery";
|
|
||||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||||
public static readonly enableSpark = "enablespark";
|
public static readonly enableSpark = "enablespark";
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
public static readonly livyEndpoint = "livyendpoint";
|
||||||
|
|
|
@ -16,7 +16,6 @@ interface Config {
|
||||||
ARM_API_VERSION: string;
|
ARM_API_VERSION: string;
|
||||||
GRAPH_ENDPOINT: string;
|
GRAPH_ENDPOINT: string;
|
||||||
GRAPH_API_VERSION: string;
|
GRAPH_API_VERSION: string;
|
||||||
AZURESAMPLESCOSMOSDBPAT: string;
|
|
||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
BACKEND_ENDPOINT?: string;
|
||||||
|
@ -44,8 +43,7 @@ let config: Config = {
|
||||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
JUNO_ENDPOINT: "https://tools.cosmos.azure.com"
|
||||||
AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86" //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Injected for local develpment. These will be removed in the production bundle by webpack
|
// Injected for local develpment. These will be removed in the production bundle by webpack
|
||||||
|
|
|
@ -83,7 +83,6 @@ export interface Explorer {
|
||||||
extensionEndpoint: ko.Observable<string>;
|
extensionEndpoint: ko.Observable<string>;
|
||||||
armEndpoint: ko.Observable<string>;
|
armEndpoint: ko.Observable<string>;
|
||||||
isFeatureEnabled: (feature: string) => boolean;
|
isFeatureEnabled: (feature: string) => boolean;
|
||||||
isGalleryEnabled: ko.Computed<boolean>;
|
|
||||||
isGalleryPublishEnabled: ko.Computed<boolean>;
|
isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
isGitHubPaneEnabled: ko.Observable<boolean>;
|
isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
|
@ -230,7 +229,7 @@ export interface Explorer {
|
||||||
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
|
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
|
||||||
resetNotebookWorkspace(): void;
|
resetNotebookWorkspace(): void;
|
||||||
importAndOpen: (path: string) => Promise<boolean>;
|
importAndOpen: (path: string) => Promise<boolean>;
|
||||||
importAndOpenFromGallery: (name: string, content: string) => Promise<boolean>;
|
importAndOpenContent: (name: string, content: string) => Promise<boolean>;
|
||||||
publishNotebook: (name: string, content: string) => void;
|
publishNotebook: (name: string, content: string) => void;
|
||||||
openNotebookTerminal: (kind: TerminalKind) => void;
|
openNotebookTerminal: (kind: TerminalKind) => void;
|
||||||
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
|
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
|
||||||
|
|
|
@ -48,7 +48,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
||||||
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
||||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||||
{ key: "feature.enablegallery", label: "Enable Notebook Gallery", value: "true" },
|
|
||||||
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
||||||
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
||||||
{
|
{
|
||||||
|
|
|
@ -157,14 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
|
||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablegallery"
|
key="feature.enablegallerypublish"
|
||||||
label="Enable Notebook Gallery"
|
label="Enable Notebook Gallery Publishing"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablegallerypublish"
|
key="feature.canexceedmaximumvalue"
|
||||||
label="Enable Notebook Gallery Publishing"
|
label="Can exceed max value"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -172,12 +172,6 @@ exports[`Feature panel renders all flags 1`] = `
|
||||||
className="checkboxRow"
|
className="checkboxRow"
|
||||||
horizontalAlign="space-between"
|
horizontalAlign="space-between"
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
|
||||||
checked={false}
|
|
||||||
key="feature.canexceedmaximumvalue"
|
|
||||||
label="Can exceed max value"
|
|
||||||
onChange={[Function]}
|
|
||||||
/>
|
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablefixedcollectionwithsharedthroughput"
|
key="feature.enablefixedcollectionwithsharedthroughput"
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
import { IGalleryItem } from "../../../../Juno/JunoClient";
|
||||||
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
|
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
|
||||||
|
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
|
||||||
|
|
||||||
export interface GalleryCardComponentProps {
|
export interface GalleryCardComponentProps {
|
||||||
data: IGalleryItem;
|
data: IGalleryItem;
|
||||||
|
@ -55,7 +56,11 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||||
return (
|
return (
|
||||||
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}>
|
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}>
|
||||||
<Card.Item>
|
<Card.Item>
|
||||||
<Persona text={this.props.data.author} secondaryText={dateString} />
|
<Persona
|
||||||
|
imageUrl={this.props.data.isSample && CosmosDBLogo}
|
||||||
|
text={this.props.data.author}
|
||||||
|
secondaryText={dateString}
|
||||||
|
/>
|
||||||
</Card.Item>
|
</Card.Item>
|
||||||
|
|
||||||
<Card.Item fill>
|
<Card.Item fill>
|
||||||
|
@ -89,15 +94,9 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
|
|
||||||
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}>
|
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}>
|
||||||
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
|
{this.generateIconText("RedEye", this.props.data.views.toString())}
|
||||||
<Icon iconName="RedEye" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.views}
|
{this.generateIconText("Download", this.props.data.downloads.toString())}
|
||||||
</Text>
|
{this.props.isFavorite !== undefined && this.generateIconText("Heart", this.props.data.favorites.toString())}
|
||||||
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
|
|
||||||
<Icon iconName="Download" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.downloads}
|
|
||||||
</Text>
|
|
||||||
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
|
|
||||||
<Icon iconName="Heart" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.favorites}
|
|
||||||
</Text>
|
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
|
|
||||||
<Card.Item>
|
<Card.Item>
|
||||||
|
@ -105,7 +104,8 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||||
</Card.Item>
|
</Card.Item>
|
||||||
|
|
||||||
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}>
|
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}>
|
||||||
{this.generateIconButtonWithTooltip(
|
{this.props.isFavorite !== undefined &&
|
||||||
|
this.generateIconButtonWithTooltip(
|
||||||
this.props.isFavorite ? "HeartFill" : "Heart",
|
this.props.isFavorite ? "HeartFill" : "Heart",
|
||||||
this.props.isFavorite ? "Unlike" : "Like",
|
this.props.isFavorite ? "Unlike" : "Like",
|
||||||
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
|
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
|
||||||
|
@ -115,7 +115,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||||
|
|
||||||
{this.props.showDelete && (
|
{this.props.showDelete && (
|
||||||
<div style={{ width: "100%", textAlign: "right" }}>
|
<div style={{ width: "100%", textAlign: "right" }}>
|
||||||
{this.generateIconButtonWithTooltip("Delete", "Remove", this.props.onDeleteClick)}
|
{this.generateIconButtonWithTooltip("Delete", "Remove", this.onDeleteClick)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
|
@ -123,6 +123,14 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private generateIconText = (iconName: string, text: string): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
|
||||||
|
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
|
* 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)
|
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)
|
||||||
|
|
|
@ -14,6 +14,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||||
>
|
>
|
||||||
<CardItem>
|
<CardItem>
|
||||||
<StyledPersonaBase
|
<StyledPersonaBase
|
||||||
|
imageUrl={false}
|
||||||
secondaryText="Invalid Date"
|
secondaryText="Invalid Date"
|
||||||
text="author"
|
text="author"
|
||||||
/>
|
/>
|
||||||
|
@ -256,6 +257,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||||
"iconName": "Delete",
|
"iconName": "Delete",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onClick={[Function]}
|
||||||
title="Remove"
|
title="Remove"
|
||||||
/>
|
/>
|
||||||
</StyledTooltipHostBase>
|
</StyledTooltipHostBase>
|
||||||
|
|
|
@ -75,27 +75,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
|
|
||||||
private static readonly mostViewedText = "Most viewed";
|
private static readonly mostViewedText = "Most viewed";
|
||||||
private static readonly mostDownloadedText = "Most downloaded";
|
private static readonly mostDownloadedText = "Most downloaded";
|
||||||
private static readonly mostFavoritedText = "Most favorited";
|
private static readonly mostFavoritedText = "Most liked";
|
||||||
private static readonly mostRecentText = "Most recent";
|
private static readonly mostRecentText = "Most recent";
|
||||||
|
|
||||||
private static readonly sortingOptions: IDropdownOption[] = [
|
private readonly sortingOptions: IDropdownOption[];
|
||||||
{
|
|
||||||
key: SortBy.MostViewed,
|
|
||||||
text: GalleryViewerComponent.mostViewedText
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SortBy.MostDownloaded,
|
|
||||||
text: GalleryViewerComponent.mostDownloadedText
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SortBy.MostFavorited,
|
|
||||||
text: GalleryViewerComponent.mostFavoritedText
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SortBy.MostRecent,
|
|
||||||
text: GalleryViewerComponent.mostRecentText
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
private sampleNotebooks: IGalleryItem[];
|
private sampleNotebooks: IGalleryItem[];
|
||||||
private publicNotebooks: IGalleryItem[];
|
private publicNotebooks: IGalleryItem[];
|
||||||
|
@ -118,8 +101,29 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
dialogProps: undefined
|
dialogProps: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.sortingOptions = [
|
||||||
|
{
|
||||||
|
key: SortBy.MostViewed,
|
||||||
|
text: GalleryViewerComponent.mostViewedText
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SortBy.MostDownloaded,
|
||||||
|
text: GalleryViewerComponent.mostDownloadedText
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SortBy.MostRecent,
|
||||||
|
text: GalleryViewerComponent.mostRecentText
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
|
this.sortingOptions.push({
|
||||||
|
key: SortBy.MostFavorited,
|
||||||
|
text: GalleryViewerComponent.mostFavoritedText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
|
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
|
||||||
if (this.props.container) {
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
|
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,17 +135,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
||||||
|
|
||||||
if (this.props.container) {
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
if (this.props.container.isGalleryPublishEnabled()) {
|
|
||||||
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
|
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
|
||||||
}
|
|
||||||
|
|
||||||
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
||||||
|
|
||||||
if (this.props.container.isGalleryPublishEnabled()) {
|
|
||||||
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const pivotProps: IPivotProps = {
|
const pivotProps: IPivotProps = {
|
||||||
onLinkClick: this.onPivotChange,
|
onLinkClick: this.onPivotChange,
|
||||||
|
@ -189,11 +187,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
<Label>Sort by</Label>
|
<Label>Sort by</Label>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
||||||
<Dropdown
|
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
|
||||||
options={GalleryViewerComponent.sortingOptions}
|
|
||||||
selectedKey={this.state.sortBy}
|
|
||||||
onChange={this.onDropdownChange}
|
|
||||||
/>
|
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
@ -405,7 +399,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
|
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
|
||||||
const isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
|
let isFavorite: boolean;
|
||||||
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
|
isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
|
||||||
|
}
|
||||||
const props: GalleryCardComponentProps = {
|
const props: GalleryCardComponentProps = {
|
||||||
data,
|
data,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
@ -434,7 +431,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||||
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
|
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
|
||||||
});
|
});
|
||||||
|
|
||||||
window.open(`/notebookViewer.html?${params.toString()}`);
|
const location = new URL("./notebookViewer.html", window.location.href).href;
|
||||||
|
window.open(`${location}?${params.toString()}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,10 +67,6 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||||
"key": 1,
|
"key": 1,
|
||||||
"text": "Most downloaded",
|
"text": "Most downloaded",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"key": 2,
|
|
||||||
"text": "Most favorited",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"key": 3,
|
"key": 3,
|
||||||
"text": "Most recent",
|
"text": "Most recent",
|
||||||
|
|
|
@ -44,11 +44,15 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
|
||||||
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
|
{this.props.isFavorite !== undefined && (
|
||||||
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
||||||
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
||||||
/>
|
/>
|
||||||
{this.props.data.favorites} likes
|
{this.props.data.favorites} likes
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -129,7 +129,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { hideInputs: true })}
|
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { hideInputs: false })}
|
||||||
|
|
||||||
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
|
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -83,6 +83,7 @@ import { UploadFilePane } from "./Panes/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane";
|
||||||
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
|
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
|
||||||
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
|
||||||
|
import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
|
||||||
|
|
||||||
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
|
||||||
|
@ -199,7 +200,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
public publishNotebookPaneAdapter: ReactAdapter;
|
public publishNotebookPaneAdapter: ReactAdapter;
|
||||||
|
|
||||||
// features
|
// features
|
||||||
public isGalleryEnabled: ko.Computed<boolean>;
|
|
||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
|
@ -244,7 +244,10 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
private _isInitializingSparkConnectionInfo: boolean;
|
private _isInitializingSparkConnectionInfo: boolean;
|
||||||
private notebookBasePath: ko.Observable<string>;
|
private notebookBasePath: ko.Observable<string>;
|
||||||
private _arcadiaManager: ViewModels.ArcadiaResourceManager;
|
private _arcadiaManager: ViewModels.ArcadiaResourceManager;
|
||||||
private _filePathToImportAndOpen: string;
|
private notebookToImport: {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
// React adapters
|
// React adapters
|
||||||
private commandBarComponentAdapter: CommandBarComponentAdapter;
|
private commandBarComponentAdapter: CommandBarComponentAdapter;
|
||||||
|
@ -344,7 +347,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
await this.initNotebooks(this.databaseAccount());
|
await this.initNotebooks(this.databaseAccount());
|
||||||
const workspaces = await this._getArcadiaWorkspaces();
|
const workspaces = await this._getArcadiaWorkspaces();
|
||||||
this.arcadiaWorkspaces(workspaces);
|
this.arcadiaWorkspaces(workspaces);
|
||||||
} else if (this._filePathToImportAndOpen) {
|
} else if (this.notebookToImport) {
|
||||||
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
|
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
|
||||||
this._openSetupNotebooksPaneForQuickstart();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
@ -405,7 +408,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
this.shouldShowShareDialogContents = ko.observable<boolean>(false);
|
this.shouldShowShareDialogContents = ko.observable<boolean>(false);
|
||||||
this.shouldShowDataAccessExpiryDialog = ko.observable<boolean>(false);
|
this.shouldShowDataAccessExpiryDialog = ko.observable<boolean>(false);
|
||||||
this.shouldShowContextSwitchPrompt = ko.observable<boolean>(false);
|
this.shouldShowContextSwitchPrompt = ko.observable<boolean>(false);
|
||||||
this.isGalleryEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableGallery));
|
|
||||||
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
||||||
);
|
);
|
||||||
|
@ -2490,10 +2492,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
const parent = this.resourceTree.myNotebooksContentRoot;
|
const parent = this.resourceTree.myNotebooksContentRoot;
|
||||||
|
|
||||||
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
|
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
|
||||||
if (this._filePathToImportAndOpen === path) {
|
|
||||||
this._filePathToImportAndOpen = null; // we don't want to try opening this path again
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingItem = _.find(parent.children, node => node.name === name);
|
const existingItem = _.find(parent.children, node => node.name === name);
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
return this.openNotebook(existingItem);
|
return this.openNotebook(existingItem);
|
||||||
|
@ -2504,24 +2502,27 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
return this.openNotebook(uploadedItem);
|
return this.openNotebook(uploadedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._filePathToImportAndOpen = path; // we'll try opening this path later on
|
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async importAndOpenFromGallery(name: string, content: string): Promise<boolean> {
|
public async importAndOpenContent(name: string, content: string): Promise<boolean> {
|
||||||
const parent = this.resourceTree.myNotebooksContentRoot;
|
const parent = this.resourceTree.myNotebooksContentRoot;
|
||||||
|
|
||||||
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
|
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
|
||||||
|
if (this.notebookToImport && this.notebookToImport.name === name && this.notebookToImport.content === content) {
|
||||||
|
this.notebookToImport = undefined; // we don't want to try opening this notebook again
|
||||||
|
}
|
||||||
|
|
||||||
const existingItem = _.find(parent.children, node => node.name === name);
|
const existingItem = _.find(parent.children, node => node.name === name);
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
this.showOkModalDialog("Download failed", "Notebook with the same name already exists.");
|
return this.openNotebook(existingItem);
|
||||||
return Promise.reject(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadedItem = await this.uploadFile(name, content, parent);
|
const uploadedItem = await this.uploadFile(name, content, parent);
|
||||||
return this.openNotebook(uploadedItem);
|
return this.openNotebook(uploadedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.notebookToImport = { name, content }; // we'll try opening this notebook later on
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2927,8 +2928,8 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.resourceTree.initialize();
|
await this.resourceTree.initialize();
|
||||||
if (this._filePathToImportAndOpen) {
|
if (this.notebookToImport) {
|
||||||
this.importAndOpen(this._filePathToImportAndOpen);
|
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3351,6 +3352,21 @@ export default class Explorer implements ViewModels.Explorer {
|
||||||
this._openSetupNotebooksPaneForQuickstart();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.importAndOpen(path);
|
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
||||||
|
// when launching a notebook quickstart from Portal. In future we should just use gallery id and use Juno to fetch instead of directly
|
||||||
|
// calling GitHub. For now convert this url to a raw url and download content.
|
||||||
|
const gitHubInfo = fromContentUri(path);
|
||||||
|
if (gitHubInfo) {
|
||||||
|
const rawUrl = toRawContentUri(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.branch, gitHubInfo.path);
|
||||||
|
const response = await fetch(rawUrl);
|
||||||
|
if (response.status === Constants.HttpStatusCodes.OK) {
|
||||||
|
this.notebookToImport = {
|
||||||
|
name: NotebookUtil.getName(path),
|
||||||
|
content: await response.text()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
|
@ -82,7 +81,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
|
@ -163,7 +161,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
|
@ -250,7 +247,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||||
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.notebookManager = new NotebookManager();
|
mockExplorer.notebookManager = new NotebookManager();
|
||||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { JunoClient } from "../../Juno/JunoClient";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||||
import { config } from "../../Config";
|
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
||||||
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
||||||
|
@ -54,7 +53,7 @@ export default class NotebookManager {
|
||||||
this.junoClient = new JunoClient(this.params.container.databaseAccount);
|
this.junoClient = new JunoClient(this.params.container.databaseAccount);
|
||||||
|
|
||||||
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
||||||
this.gitHubClient = new GitHubClient(config.AZURESAMPLESCOSMOSDBPAT, this.onGitHubClientError);
|
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
|
||||||
this.gitHubReposPane = new GitHubReposPane({
|
this.gitHubReposPane = new GitHubReposPane({
|
||||||
documentClientUtility: this.params.container.documentClientUtility,
|
documentClientUtility: this.params.container.documentClientUtility,
|
||||||
id: "gitHubReposPane",
|
id: "gitHubReposPane",
|
||||||
|
@ -91,7 +90,7 @@ export default class NotebookManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gitHubOAuthService.getTokenObservable().subscribe(token => {
|
this.gitHubOAuthService.getTokenObservable().subscribe(token => {
|
||||||
this.gitHubClient.setToken(token?.access_token ? token.access_token : config.AZURESAMPLESCOSMOSDBPAT);
|
this.gitHubClient.setToken(token?.access_token);
|
||||||
|
|
||||||
if (this.gitHubReposPane.visible()) {
|
if (this.gitHubReposPane.visible()) {
|
||||||
this.gitHubReposPane.open();
|
this.gitHubReposPane.open();
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
import { IGitHubRepo, IGitHubBranch } from "../../GitHub/GitHubClient";
|
|
||||||
|
|
||||||
export const SamplesRepo: IGitHubRepo = {
|
|
||||||
name: "cosmos-notebooks",
|
|
||||||
owner: "Azure-Samples",
|
|
||||||
private: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SamplesBranch: IGitHubBranch = {
|
|
||||||
name: "master"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isSamplesCall = (owner: string, repo: string, branch?: string): boolean => {
|
|
||||||
return owner === SamplesRepo.owner && repo === SamplesRepo.name && (!branch || branch === SamplesBranch.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
// GitHub API calls have a rate limit of 5000 requests per hour. So if we get high traffic on Data Explorer
|
|
||||||
// loading samples exceed that limit. Using this hard coded response for samples until we fix that.
|
|
||||||
export const SamplesContentsQueryResponse = {
|
|
||||||
repository: {
|
|
||||||
owner: {
|
|
||||||
login: "Azure-Samples"
|
|
||||||
},
|
|
||||||
name: "cosmos-notebooks",
|
|
||||||
isPrivate: false,
|
|
||||||
ref: {
|
|
||||||
name: "master",
|
|
||||||
target: {
|
|
||||||
history: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
oid: "cda7facb9e039b173f3376200c26c859896e7974",
|
|
||||||
message:
|
|
||||||
"Merge pull request #45 from Azure-Samples/users/deborahc/pythonSampleUpdates\n\nAdd bokeh version to notebook",
|
|
||||||
committer: {
|
|
||||||
date: "2020-05-28T11:28:01-07:00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
object: {
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
name: ".github",
|
|
||||||
type: "tree",
|
|
||||||
object: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ".gitignore",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "3e759b75bf455ac809d0987d369aab89137b5689",
|
|
||||||
byteSize: 5582
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1. GettingStarted.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "0732ff5366e4aefdc4c378c61cbd968664f0acec",
|
|
||||||
byteSize: 3933
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "2. Visualization.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "6b16b0740a77afdd38a95bc6c3ebd0f2f17d9465",
|
|
||||||
byteSize: 820317
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "3. RequestUnits.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "252b79a4adc81e9f2ffde453231b695d75e270e8",
|
|
||||||
byteSize: 9490
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "4. Indexing.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "e10dd67bd1c55c345226769e4f80e43659ef9cd5",
|
|
||||||
byteSize: 10394
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "5. StoredProcedures.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "949941949920de4d2d111149e2182e9657cc8134",
|
|
||||||
byteSize: 11818
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "6. GlobalDistribution.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "b91c31dacacbc9e35750d9054063dda4a5309f3b",
|
|
||||||
byteSize: 11375
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "7. IoTAnomalyDetection.ipynb",
|
|
||||||
type: "blob",
|
|
||||||
object: {
|
|
||||||
oid: "82057ae52a67721a5966e2361317f5dfbd0ee595",
|
|
||||||
byteSize: 377939
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "All_API_quickstarts",
|
|
||||||
type: "tree",
|
|
||||||
object: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "CSharp_quickstarts",
|
|
||||||
type: "tree",
|
|
||||||
object: {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -96,7 +96,6 @@ export class ExplorerStub implements ViewModels.Explorer {
|
||||||
public setupNotebooksPane: SetupNotebooksPane;
|
public setupNotebooksPane: SetupNotebooksPane;
|
||||||
public setupSparkClusterPane: ViewModels.ContextualPane;
|
public setupSparkClusterPane: ViewModels.ContextualPane;
|
||||||
public manageSparkClusterPane: ViewModels.ContextualPane;
|
public manageSparkClusterPane: ViewModels.ContextualPane;
|
||||||
public isGalleryEnabled: ko.Computed<boolean>;
|
|
||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
|
@ -333,7 +332,7 @@ export class ExplorerStub implements ViewModels.Explorer {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public importAndOpenFromGallery(name: string, content: string): Promise<boolean> {
|
public importAndOpenContent(name: string, content: string): Promise<boolean> {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,11 @@ import FileIcon from "../../../images/notebook/file-cosmos.svg";
|
||||||
import { ArrayHashMap } from "../../Common/ArrayHashMap";
|
import { ArrayHashMap } from "../../Common/ArrayHashMap";
|
||||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { StringUtils } from "../../Utils/StringUtils";
|
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { Areas } from "../../Common/Constants";
|
import { Areas } from "../../Common/Constants";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { SamplesRepo, SamplesBranch } from "../Notebook/NotebookSamples";
|
|
||||||
import GalleryIcon from "../../../images/GalleryIcon.svg";
|
import GalleryIcon from "../../../images/GalleryIcon.svg";
|
||||||
import { Callout, Text, Link, DirectionalHint, Stack, ICalloutProps, ILinkProps } from "office-ui-fabric-react";
|
import { Callout, Text, Link, DirectionalHint, Stack, ICalloutProps, ILinkProps } from "office-ui-fabric-react";
|
||||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
@ -37,7 +35,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
public parameters: ko.Observable<number>;
|
public parameters: ko.Observable<number>;
|
||||||
|
|
||||||
public galleryContentRoot: NotebookContentItem;
|
public galleryContentRoot: NotebookContentItem;
|
||||||
public sampleNotebooksContentRoot: NotebookContentItem;
|
|
||||||
public myNotebooksContentRoot: NotebookContentItem;
|
public myNotebooksContentRoot: NotebookContentItem;
|
||||||
public gitHubNotebooksContentRoot: NotebookContentItem;
|
public gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
|
|
||||||
|
@ -95,27 +92,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
public async initialize(): Promise<void[]> {
|
public async initialize(): Promise<void[]> {
|
||||||
const refreshTasks: Promise<void>[] = [];
|
const refreshTasks: Promise<void>[] = [];
|
||||||
|
|
||||||
if (this.container.isGalleryEnabled()) {
|
|
||||||
this.galleryContentRoot = {
|
this.galleryContentRoot = {
|
||||||
name: "Gallery",
|
name: "Gallery",
|
||||||
path: "Gallery",
|
path: "Gallery",
|
||||||
type: NotebookContentItemType.File
|
type: NotebookContentItemType.File
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sampleNotebooksContentRoot = undefined;
|
|
||||||
} else {
|
|
||||||
this.galleryContentRoot = undefined;
|
|
||||||
|
|
||||||
this.sampleNotebooksContentRoot = {
|
|
||||||
name: "Sample Notebooks (View Only)",
|
|
||||||
path: GitHubUtils.toContentUri(SamplesRepo.owner, SamplesRepo.name, SamplesBranch.name, ""),
|
|
||||||
type: NotebookContentItemType.Directory
|
|
||||||
};
|
|
||||||
refreshTasks.push(
|
|
||||||
this.container.refreshContentItem(this.sampleNotebooksContentRoot).then(() => this.triggerRender())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.myNotebooksContentRoot = {
|
this.myNotebooksContentRoot = {
|
||||||
name: "My Notebooks",
|
name: "My Notebooks",
|
||||||
path: this.container.getNotebookBasePath(),
|
path: this.container.getNotebookBasePath(),
|
||||||
|
@ -361,10 +343,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
notebooksTree.children.push(this.buildGalleryNotebooksTree());
|
notebooksTree.children.push(this.buildGalleryNotebooksTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sampleNotebooksContentRoot) {
|
|
||||||
notebooksTree.children.push(this.buildSampleNotebooksTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.myNotebooksContentRoot) {
|
if (this.myNotebooksContentRoot) {
|
||||||
notebooksTree.children.push(this.buildMyNotebooksTree());
|
notebooksTree.children.push(this.buildMyNotebooksTree());
|
||||||
}
|
}
|
||||||
|
@ -437,57 +415,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildSampleNotebooksTree(): TreeNode {
|
|
||||||
const sampleNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
|
|
||||||
this.sampleNotebooksContentRoot,
|
|
||||||
(item: NotebookContentItem) => {
|
|
||||||
const databaseAccountName: string = this.container.databaseAccount() && this.container.databaseAccount().name;
|
|
||||||
const defaultExperience: string = this.container.defaultExperience && this.container.defaultExperience();
|
|
||||||
const dataExplorerArea: string = Areas.ResourceTree;
|
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.OpenSampleNotebook, {
|
|
||||||
databaseAccountName,
|
|
||||||
defaultExperience,
|
|
||||||
dataExplorerArea
|
|
||||||
});
|
|
||||||
|
|
||||||
this.container.importAndOpen(item.path).then(hasOpened => {
|
|
||||||
if (hasOpened) {
|
|
||||||
this.pushItemToMostRecent(item);
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.OpenSampleNotebook,
|
|
||||||
{
|
|
||||||
databaseAccountName,
|
|
||||||
defaultExperience,
|
|
||||||
dataExplorerArea
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.OpenSampleNotebook,
|
|
||||||
{
|
|
||||||
databaseAccountName,
|
|
||||||
defaultExperience,
|
|
||||||
dataExplorerArea
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
sampleNotebooksTree.isExpanded = true;
|
|
||||||
// Remove children starting with "."
|
|
||||||
sampleNotebooksTree.children = sampleNotebooksTree.children.filter(
|
|
||||||
node => !StringUtils.startsWith(node.label, ".")
|
|
||||||
);
|
|
||||||
return sampleNotebooksTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildMyNotebooksTree(): TreeNode {
|
private buildMyNotebooksTree(): TreeNode {
|
||||||
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
|
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
|
||||||
this.myNotebooksContentRoot,
|
this.myNotebooksContentRoot,
|
||||||
|
|
|
@ -15,7 +15,7 @@ import * as GalleryUtils from "../Utils/GalleryUtils";
|
||||||
const onInit = async () => {
|
const onInit = async () => {
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
await initializeConfiguration();
|
await initializeConfiguration();
|
||||||
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window);
|
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
|
||||||
|
|
||||||
const props: GalleryViewerComponentProps = {
|
const props: GalleryViewerComponentProps = {
|
||||||
junoClient: new JunoClient(),
|
junoClient: new JunoClient(),
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
|
||||||
import { GitHubClient, IGitHubFile } from "./GitHubClient";
|
|
||||||
import { SamplesRepo, SamplesBranch, SamplesContentsQueryResponse } from "../Explorer/Notebook/NotebookSamples";
|
|
||||||
|
|
||||||
const invalidTokenCallback = jest.fn();
|
|
||||||
// Use a dummy token to get around API rate limit (something which doesn't affect the API quota for AZURESAMPLESCOSMOSDBPAT in Config.ts)
|
|
||||||
const gitHubClient = new GitHubClient("cd1906b9534362fab6ce45d6db6c76b59e55bc50", invalidTokenCallback);
|
|
||||||
|
|
||||||
const validateGitHubFile = (file: IGitHubFile) => {
|
|
||||||
expect(file.branch).toEqual(SamplesBranch);
|
|
||||||
expect(file.commit).toBeDefined();
|
|
||||||
expect(file.name).toBeDefined();
|
|
||||||
expect(file.path).toBeDefined();
|
|
||||||
expect(file.repo).toEqual(SamplesRepo);
|
|
||||||
expect(file.type).toBeDefined();
|
|
||||||
|
|
||||||
switch (file.type) {
|
|
||||||
case "blob":
|
|
||||||
expect(file.sha).toBeDefined();
|
|
||||||
expect(file.size).toBeDefined();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tree":
|
|
||||||
expect(file.sha).toBeUndefined();
|
|
||||||
expect(file.size).toBeUndefined();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported github file type: ${file.type}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("GitHubClient", () => {
|
|
||||||
it("getRepoAsync returns valid repo", async () => {
|
|
||||||
const response = await gitHubClient.getRepoAsync(SamplesRepo.owner, SamplesRepo.name);
|
|
||||||
expect(response).toEqual({
|
|
||||||
status: HttpStatusCodes.OK,
|
|
||||||
data: SamplesRepo
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getReposAsync returns repos for authenticated user", async () => {
|
|
||||||
const response = await gitHubClient.getReposAsync(1);
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
expect(response.data.length).toBe(1);
|
|
||||||
expect(response.pageInfo).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getBranchesAsync returns branches for a repo", async () => {
|
|
||||||
const response = await gitHubClient.getBranchesAsync(SamplesRepo.owner, SamplesRepo.name, 1);
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toEqual([SamplesBranch]);
|
|
||||||
expect(response.pageInfo).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getContentsAsync returns files in the repo", async () => {
|
|
||||||
const response = await gitHubClient.getContentsAsync(SamplesRepo.owner, SamplesRepo.name, SamplesBranch.name);
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
|
|
||||||
const data = response.data as IGitHubFile[];
|
|
||||||
expect(data.length).toBeGreaterThan(0);
|
|
||||||
data.forEach(content => validateGitHubFile(content));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getContentsAsync returns files in a dir", async () => {
|
|
||||||
const samplesDir = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "tree");
|
|
||||||
const response = await gitHubClient.getContentsAsync(
|
|
||||||
SamplesRepo.owner,
|
|
||||||
SamplesRepo.name,
|
|
||||||
SamplesBranch.name,
|
|
||||||
samplesDir.name
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
|
|
||||||
const data = response.data as IGitHubFile[];
|
|
||||||
expect(data.length).toBeGreaterThan(0);
|
|
||||||
data.forEach(content => validateGitHubFile(content));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getContentsAsync returns a file", async () => {
|
|
||||||
const samplesFile = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "blob");
|
|
||||||
const response = await gitHubClient.getContentsAsync(
|
|
||||||
SamplesRepo.owner,
|
|
||||||
SamplesRepo.name,
|
|
||||||
SamplesBranch.name,
|
|
||||||
samplesFile.name
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
|
|
||||||
const file = response.data as IGitHubFile;
|
|
||||||
expect(file.type).toBe("blob");
|
|
||||||
validateGitHubFile(file);
|
|
||||||
expect(file.content).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getBlobAsync returns file content", async () => {
|
|
||||||
const samplesFile = SamplesContentsQueryResponse.repository.object.entries.find(file => file.type === "blob");
|
|
||||||
const response = await gitHubClient.getBlobAsync(SamplesRepo.owner, SamplesRepo.name, samplesFile.object.oid);
|
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
expect(typeof response.data).toBe("string");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -2,7 +2,6 @@ import { Octokit } from "@octokit/rest";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import UrlUtility from "../Common/UrlUtility";
|
import UrlUtility from "../Common/UrlUtility";
|
||||||
import { isSamplesCall, SamplesContentsQueryResponse } from "../Explorer/Notebook/NotebookSamples";
|
|
||||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||||
|
|
||||||
export interface IGitHubPageInfo {
|
export interface IGitHubPageInfo {
|
||||||
|
@ -225,8 +224,8 @@ export class GitHubClient {
|
||||||
private static readonly SelfErrorCode = 599;
|
private static readonly SelfErrorCode = 599;
|
||||||
private ocktokit: Octokit;
|
private ocktokit: Octokit;
|
||||||
|
|
||||||
constructor(token: string, private errorCallback: (error: any) => void) {
|
constructor(private errorCallback: (error: any) => void) {
|
||||||
this.initOctokit(token);
|
this.initOctokit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setToken(token: string): void {
|
public setToken(token: string): void {
|
||||||
|
@ -310,18 +309,13 @@ export class GitHubClient {
|
||||||
path?: string
|
path?: string
|
||||||
): Promise<IGitHubResponse<IGitHubFile | IGitHubFile[]>> {
|
): Promise<IGitHubResponse<IGitHubFile | IGitHubFile[]>> {
|
||||||
try {
|
try {
|
||||||
let response: ContentsQueryResponse;
|
const response = (await this.ocktokit.graphql(contentsQuery, {
|
||||||
if (isSamplesCall(owner, repo, branch) && !path) {
|
|
||||||
response = SamplesContentsQueryResponse;
|
|
||||||
} else {
|
|
||||||
response = (await this.ocktokit.graphql(contentsQuery, {
|
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
ref: `refs/heads/${branch}`,
|
ref: `refs/heads/${branch}`,
|
||||||
path: path || undefined,
|
path: path || undefined,
|
||||||
objectExpression: `refs/heads/${branch}:${path || ""}`
|
objectExpression: `refs/heads/${branch}:${path || ""}`
|
||||||
} as ContentsQueryParams)) as ContentsQueryResponse;
|
} as ContentsQueryParams)) as ContentsQueryResponse;
|
||||||
}
|
|
||||||
|
|
||||||
let data: IGitHubFile | IGitHubFile[];
|
let data: IGitHubFile | IGitHubFile[];
|
||||||
const entries = response.repository.object.entries;
|
const entries = response.repository.object.entries;
|
||||||
|
@ -495,7 +489,7 @@ export class GitHubClient {
|
||||||
return { status: response.status, data: <string>(<unknown>response.data) };
|
return { status: response.status, data: <string>(<unknown>response.data) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initOctokit(token: string) {
|
private async initOctokit(token?: string) {
|
||||||
this.ocktokit = new Octokit({
|
this.ocktokit = new Octokit({
|
||||||
auth: token,
|
auth: token,
|
||||||
log: {
|
log: {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { GitHubClient, IGitHubCommit, IGitHubFile } from "./GitHubClient";
|
||||||
import { GitHubContentProvider } from "./GitHubContentProvider";
|
import { GitHubContentProvider } from "./GitHubContentProvider";
|
||||||
import * as GitHubUtils from "../Utils/GitHubUtils";
|
import * as GitHubUtils from "../Utils/GitHubUtils";
|
||||||
|
|
||||||
const gitHubClient = new GitHubClient("token", () => {});
|
const gitHubClient = new GitHubClient(() => {});
|
||||||
const gitHubContentProvider = new GitHubContentProvider({
|
const gitHubContentProvider = new GitHubContentProvider({
|
||||||
gitHubClient,
|
gitHubClient,
|
||||||
promptForCommitMsg: () => Promise.resolve("commit msg")
|
promptForCommitMsg: () => Promise.resolve("commit msg")
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { IPinnedRepo, JunoClient } from "./JunoClient";
|
import { IPinnedRepo, JunoClient, IGalleryItem } from "./JunoClient";
|
||||||
|
import { config } from "../Config";
|
||||||
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
|
||||||
const sampleDatabaseAccount: ViewModels.DatabaseAccount = {
|
const sampleDatabaseAccount: ViewModels.DatabaseAccount = {
|
||||||
id: "id",
|
id: "id",
|
||||||
|
@ -31,6 +33,23 @@ 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
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
describe("Pinned repos", () => {
|
describe("Pinned repos", () => {
|
||||||
const junoClient = new JunoClient(ko.observable<ViewModels.DatabaseAccount>(sampleDatabaseAccount));
|
const junoClient = new JunoClient(ko.observable<ViewModels.DatabaseAccount>(sampleDatabaseAccount));
|
||||||
|
|
||||||
|
@ -126,3 +145,231 @@ describe("GitHub", () => {
|
||||||
expect(fetchUrlParams.get("client_id")).toBeDefined();
|
expect(fetchUrlParams.get("client_id")).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Gallery", () => {
|
||||||
|
const junoClient = new JunoClient(ko.observable<ViewModels.DatabaseAccount>(sampleDatabaseAccount));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getSampleNotebooks", async () => {
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getSampleNotebooks();
|
||||||
|
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/samples`, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getPublicNotebooks", async () => {
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getPublicNotebooks();
|
||||||
|
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/public`, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getNotebook", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getNotebook(id);
|
||||||
|
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/${id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getNotebookContent", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
text: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getNotebookContent(id);
|
||||||
|
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/content`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("increaseNotebookViews", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.increaseNotebookViews(id);
|
||||||
|
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/views`, {
|
||||||
|
method: "PATCH"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("increaseNotebookDownloadCount", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.increaseNotebookDownloadCount(id);
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(
|
||||||
|
`${config.JUNO_ENDPOINT}/api/notebooks/${sampleDatabaseAccount.name}/gallery/${id}/downloads`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("favoriteNotebook", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.favoriteNotebook(id);
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(
|
||||||
|
`${config.JUNO_ENDPOINT}/api/notebooks/${sampleDatabaseAccount.name}/gallery/${id}/favorite`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unfavoriteNotebook", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.unfavoriteNotebook(id);
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/unfavorite`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFavoriteNotebooks", async () => {
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getFavoriteNotebooks();
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/favorites`, {
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getPublishedNotebooks", async () => {
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.getPublishedNotebooks();
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/published`, {
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteNotebook", async () => {
|
||||||
|
const id = "id";
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.deleteNotebook(id);
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/gallery/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("publishNotebook", async () => {
|
||||||
|
const name = "name";
|
||||||
|
const description = "description";
|
||||||
|
const tags = ["tag"];
|
||||||
|
const author = "author";
|
||||||
|
const thumbnailUrl = "thumbnailUrl";
|
||||||
|
const content = `{ "key": "value" }`;
|
||||||
|
window.fetch = jest.fn().mockReturnValue({
|
||||||
|
status: HttpStatusCodes.OK,
|
||||||
|
json: () => undefined as any
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await junoClient.publishNotebook(name, description, tags, author, thumbnailUrl, content);
|
||||||
|
|
||||||
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
|
expect(window.fetch).toBeCalledWith(`${config.JUNO_ENDPOINT}/api/notebooks/${sampleDatabaseAccount.name}/gallery`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
tags,
|
||||||
|
author,
|
||||||
|
thumbnailUrl,
|
||||||
|
content: JSON.parse(content)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -13,8 +13,8 @@ import * as GalleryUtils from "../Utils/GalleryUtils";
|
||||||
const onInit = async () => {
|
const onInit = async () => {
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
await initializeConfiguration();
|
await initializeConfiguration();
|
||||||
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window);
|
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
|
||||||
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window);
|
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
|
||||||
const backNavigationText = galleryViewerProps.selectedTab && GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
|
const backNavigationText = galleryViewerProps.selectedTab && GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
|
||||||
|
|
||||||
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
|
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import * as GalleryUtils from "./GalleryUtils";
|
||||||
|
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
|
||||||
|
import { ExplorerStub } from "../Explorer/OpenActionsStubs";
|
||||||
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
|
import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("GalleryUtils", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("downloadItem shows dialog in standalone gallery", () => {
|
||||||
|
const setDialogProps = jest.fn().mockImplementation();
|
||||||
|
|
||||||
|
GalleryUtils.downloadItem({ setDialogProps }, undefined, undefined, galleryItem, undefined);
|
||||||
|
|
||||||
|
expect(setDialogProps).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("downloadItem shows dialog in data explorer", () => {
|
||||||
|
const setDialogProps = jest.fn().mockImplementation();
|
||||||
|
const container = new ExplorerStub();
|
||||||
|
container.showOkCancelModalDialog = jest.fn().mockImplementation();
|
||||||
|
|
||||||
|
GalleryUtils.downloadItem({ setDialogProps }, container, undefined, galleryItem, undefined);
|
||||||
|
|
||||||
|
expect(setDialogProps).not.toBeCalled();
|
||||||
|
expect(container.showOkCancelModalDialog).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("favoriteItem favorites item", async () => {
|
||||||
|
const container = new ExplorerStub();
|
||||||
|
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).toBeCalledWith(galleryItem.id);
|
||||||
|
expect(onComplete).toBeCalledWith(galleryItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unfavoriteItem unfavorites item", async () => {
|
||||||
|
const container = new ExplorerStub();
|
||||||
|
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).toBeCalledWith(galleryItem.id);
|
||||||
|
expect(onComplete).toBeCalledWith(galleryItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteItem shows dialog in data explorer", () => {
|
||||||
|
const container = new ExplorerStub();
|
||||||
|
container.showOkCancelModalDialog = jest.fn().mockImplementation();
|
||||||
|
|
||||||
|
GalleryUtils.deleteItem(container, undefined, galleryItem, undefined);
|
||||||
|
|
||||||
|
expect(container.showOkCancelModalDialog).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getNotebookViewerProps gets notebook viewer props correctly", () => {
|
||||||
|
const notebookUrl = "https%3A%2F%2Fnotebook.url";
|
||||||
|
const galleryItemId = "1234-abcd-efgh";
|
||||||
|
|
||||||
|
const response = GalleryUtils.getNotebookViewerProps(
|
||||||
|
`?${GalleryUtils.NotebookViewerParams.NotebookUrl}=${notebookUrl}&${GalleryUtils.NotebookViewerParams.GalleryItemId}=${galleryItemId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
notebookUrl: decodeURIComponent(notebookUrl),
|
||||||
|
galleryItemId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getTabTitle returns correct title for official samples", () => {
|
||||||
|
expect(GalleryUtils.getTabTitle(GalleryTab.OfficialSamples)).toBe("Official samples");
|
||||||
|
});
|
||||||
|
});
|
|
@ -36,7 +36,7 @@ export interface GalleryViewerProps {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showOkCancelModalDialog(
|
function showOkCancelModalDialog(
|
||||||
component: DialogEnabledComponent,
|
component: DialogEnabledComponent,
|
||||||
title: string,
|
title: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
|
@ -91,7 +91,7 @@ export function downloadItem(
|
||||||
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await container.importAndOpenFromGallery(data.name, response.data);
|
await container.importAndOpenContent(data.name, response.data);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Info,
|
ConsoleDataType.Info,
|
||||||
`Successfully downloaded ${name} to My Notebooks`
|
`Successfully downloaded ${name} to My Notebooks`
|
||||||
|
@ -217,8 +217,8 @@ export function deleteItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGalleryViewerProps(window: Window & typeof globalThis): GalleryViewerProps {
|
export function getGalleryViewerProps(search: string): GalleryViewerProps {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(search);
|
||||||
let selectedTab: GalleryTab;
|
let selectedTab: GalleryTab;
|
||||||
if (params.has(GalleryViewerParams.SelectedTab)) {
|
if (params.has(GalleryViewerParams.SelectedTab)) {
|
||||||
selectedTab = GalleryTab[params.get(GalleryViewerParams.SelectedTab) as keyof typeof GalleryTab];
|
selectedTab = GalleryTab[params.get(GalleryViewerParams.SelectedTab) as keyof typeof GalleryTab];
|
||||||
|
@ -236,8 +236,8 @@ export function getGalleryViewerProps(window: Window & typeof globalThis): Galle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNotebookViewerProps(window: Window & typeof globalThis): NotebookViewerProps {
|
export function getNotebookViewerProps(search: string): NotebookViewerProps {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(search);
|
||||||
return {
|
return {
|
||||||
notebookUrl: params.get(NotebookViewerParams.NotebookUrl),
|
notebookUrl: params.get(NotebookViewerParams.NotebookUrl),
|
||||||
galleryItemId: params.get(NotebookViewerParams.GalleryItemId)
|
galleryItemId: params.get(NotebookViewerParams.GalleryItemId)
|
||||||
|
|
|
@ -58,3 +58,7 @@ export function fromContentUri(
|
||||||
export function toContentUri(owner: string, repo: string, branch: string, path: string): string {
|
export function toContentUri(owner: string, repo: string, branch: string, path: string): string {
|
||||||
return `github://${owner}/${repo}/${path}?ref=${branch}`;
|
return `github://${owner}/${repo}/${path}?ref=${branch}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toRawContentUri(owner: string, repo: string, branch: string, path: string): string {
|
||||||
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { RepoListItem } from "../Explorer/Controls/GitHub/GitHubReposComponent";
|
||||||
|
import { IPinnedRepo } from "../Juno/JunoClient";
|
||||||
|
import { JunoUtils } from "./JunoUtils";
|
||||||
|
import { IGitHubRepo } from "../GitHub/GitHubClient";
|
||||||
|
|
||||||
|
const gitHubRepo: IGitHubRepo = {
|
||||||
|
name: "repo-name",
|
||||||
|
owner: "owner",
|
||||||
|
private: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const repoListItem: RepoListItem = {
|
||||||
|
key: "key",
|
||||||
|
repo: {
|
||||||
|
name: "repo-name",
|
||||||
|
owner: "owner",
|
||||||
|
private: false
|
||||||
|
},
|
||||||
|
branches: [
|
||||||
|
{
|
||||||
|
name: "branch-name"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const pinnedRepo: IPinnedRepo = {
|
||||||
|
name: "repo-name",
|
||||||
|
owner: "owner",
|
||||||
|
private: false,
|
||||||
|
branches: [
|
||||||
|
{
|
||||||
|
name: "branch-name"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("JunoUtils", () => {
|
||||||
|
it("toPinnedRepo converts RepoListItem to IPinnedRepo", () => {
|
||||||
|
expect(JunoUtils.toPinnedRepo(repoListItem)).toEqual(pinnedRepo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toGitHubRepo converts IPinnedRepo to IGitHubRepo", () => {
|
||||||
|
expect(JunoUtils.toGitHubRepo(pinnedRepo)).toEqual(gitHubRepo);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
|
||||||
|
import * as UserUtils from "./UserUtils";
|
||||||
|
|
||||||
|
describe("UserUtils", () => {
|
||||||
|
it("getFullName works in regular data explorer (inside portal)", () => {
|
||||||
|
const user: AuthenticationContext.UserInfo = {
|
||||||
|
userName: "userName",
|
||||||
|
profile: {
|
||||||
|
name: "name"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AuthHeadersUtil.getCachedUser = jest.fn().mockReturnValue(user);
|
||||||
|
|
||||||
|
expect(UserUtils.getFullName()).toBe("name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFullName works in fullscreen data explorer (outside portal)", () => {
|
||||||
|
jest.mock("./AuthorizationUtils", () => {
|
||||||
|
(): { name: string } => ({ name: "name" });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(UserUtils.getFullName()).toBe("name");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue