Compare commits

...

10 Commits

Author SHA1 Message Date
Steve Faulkner
0f8c36bbf0 Move serverId 2020-12-15 23:26:30 -06:00
Steve Faulkner
661fb66f7b Readme updates 2020-12-15 19:40:36 -06:00
Steve Faulkner
2eabc377b0 Update README 2020-12-15 19:31:43 -06:00
Steve Faulkner
1aa3fb8e7b Update snapshot 2020-12-15 19:16:53 -06:00
Steve Faulkner
57aef782d8 Explorer.ts Cleanup 2020-12-15 19:11:51 -06:00
Steve Faulkner
ea39c1d092 Fix offer update notification for AAD users (#338) 2020-12-11 13:38:57 -06:00
vchske
c21f42159f Updated cost messaging for new db/container pane (#333)
* Adds information text further explaining estimated cost.

* Minor tweak to cost messaging

* Updated unit tests

* Added text and link for capacity planner when choosing manual RUs
2020-12-11 10:06:43 -08:00
victor-meng
31e4b49f11 Only call getCollectionDataUsageSize for AAD users (#337) 2020-12-10 14:13:08 -08:00
Tanuj Mittal
40491ec9c5 Gallery related fixes (#312)
* AVERT fixes

* Remove enableCodeOfConduct feature flag

* Fix reporting abuse

* Add empty screen for Liked and Published tabs in Gallery

* fix build

* Remove unused code

* Fix standalone public gallery
2020-12-10 13:09:18 -08:00
Tanuj Mittal
e133df18dd Record baseUrl for OpenTerminal success/failure telemetry (#335)
This is useful to know which terminal is opening.
2020-12-10 19:54:21 +00:00
43 changed files with 262 additions and 337 deletions

View File

@@ -14,7 +14,6 @@ src/Common/DataAccessUtilityBase.ts
src/Common/DeleteFeedback.ts src/Common/DeleteFeedback.ts
src/Common/DocumentClientUtilityBase.ts src/Common/DocumentClientUtilityBase.ts
src/Common/EditableUtility.ts src/Common/EditableUtility.ts
src/Common/EnvironmentUtility.ts
src/Common/HashMap.test.ts src/Common/HashMap.test.ts
src/Common/HashMap.ts src/Common/HashMap.ts
src/Common/HeadersUtility.test.ts src/Common/HeadersUtility.test.ts

View File

@@ -13,29 +13,18 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
### Watch mode ### Watch mode
Run `npm run watch` to start the development server and automatically rebuild on changes Run `npm start` to start the development server and automatically rebuild on changes
### Specifying Development Platform ### Hosted Development (https://cosmos.azure.com)
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options: - Visit: `https://localhost:1234/hostedExplorer.html`
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
- Hosted - The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
- Emulator
- Portal
`PLATFORM=Emulator npm run watch`
### Hosted Development
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
### Emulator Development ### Emulator Development
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you. - Start the Cosmos Emulator
- Visit: https://localhost:1234/index.html
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
#### Setting up a Remote Emulator #### Setting up a Remote Emulator
@@ -55,16 +44,8 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
### Portal Development ### Portal Development
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment - Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
You can however load a local running instance of data explorer in the production portal.
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
### Testing ### Testing

10
package-lock.json generated
View File

@@ -6359,7 +6359,6 @@
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"requires": { "requires": {
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"ieee754": "^1.1.13" "ieee754": "^1.1.13"
@@ -14691,6 +14690,14 @@
"requires": { "requires": {
"nan": "2.14.1", "nan": "2.14.1",
"prebuild-install": "5.3.3" "prebuild-install": "5.3.3"
},
"dependencies": {
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
"optional": true
}
} }
}, },
"killable": { "killable": {
@@ -20134,7 +20141,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dev": true,
"requires": { "requires": {
"chownr": "^1.1.1", "chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2", "mkdirp-classic": "^0.5.2",

View File

@@ -113,7 +113,6 @@ export class Features {
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks"; public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableCodeOfConduct = "enablecodeofconduct";
public static readonly enableLinkInjection = "enablelinkinjection"; public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; public static readonly livyEndpoint = "livyendpoint";

View File

@@ -1,8 +1,6 @@
export default class EnvironmentUtility { export function normalizeArmEndpoint(uri: string): string {
public static normalizeArmEndpointUri(uri: string): string { if (uri && uri.slice(-1) !== "/") {
if (uri && uri.slice(-1) !== "/") { return `${uri}/`;
return `${uri}/`;
}
return uri;
} }
return uri;
} }

View File

@@ -24,7 +24,8 @@ describe("parseSDKOfferResponse", () => {
autoscaleMaxThroughput: undefined, autoscaleMaxThroughput: undefined,
minimumThroughput: 400, minimumThroughput: 400,
id: "test", id: "test",
offerDefinition: mockOfferDefinition offerDefinition: mockOfferDefinition,
offerReplacePending: false
}; };
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult); expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
@@ -54,7 +55,8 @@ describe("parseSDKOfferResponse", () => {
autoscaleMaxThroughput: 5000, autoscaleMaxThroughput: 5000,
minimumThroughput: 400, minimumThroughput: 400,
id: "test", id: "test",
offerDefinition: mockOfferDefinition offerDefinition: mockOfferDefinition,
offerReplacePending: false
}; };
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult); expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);

View File

@@ -1,5 +1,6 @@
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels"; import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos"; import { OfferResponse } from "@azure/cosmos";
import { HttpHeaders } from "./Constants";
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => { export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
const offerDefinition: SDKOfferDefinition = offerResponse?.resource; const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
@@ -18,7 +19,7 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
manualThroughput: undefined, manualThroughput: undefined,
minimumThroughput, minimumThroughput,
offerDefinition, offerDefinition,
headers: offerResponse.headers offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
}; };
} }
@@ -28,6 +29,6 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
manualThroughput: offerContent.offerThroughput, manualThroughput: offerContent.offerThroughput,
minimumThroughput, minimumThroughput,
offerDefinition, offerDefinition,
headers: offerResponse.headers offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
}; };
}; };

View File

@@ -1,3 +1,4 @@
import { AuthType } from "../../AuthType";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -40,6 +41,10 @@ interface MetricsResponse {
} }
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => { export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
if (window.authType !== AuthType.AAD) {
return undefined;
}
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;

View File

@@ -105,7 +105,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
id: offerId, id: offerId,
autoscaleMaxThroughput: autoscaleSettings.maxThroughput, autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
manualThroughput: undefined, manualThroughput: undefined,
minimumThroughput minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true"
}; };
} }
@@ -113,7 +114,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
id: offerId, id: offerId,
autoscaleMaxThroughput: undefined, autoscaleMaxThroughput: undefined,
manualThroughput: resource.throughput, manualThroughput: resource.throughput,
minimumThroughput minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true"
}; };
} }

View File

@@ -77,7 +77,8 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
id: offerId, id: offerId,
autoscaleMaxThroughput: autoscaleSettings.maxThroughput, autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
manualThroughput: undefined, manualThroughput: undefined,
minimumThroughput minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true"
}; };
} }
@@ -85,7 +86,8 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
id: offerId, id: offerId,
autoscaleMaxThroughput: undefined, autoscaleMaxThroughput: undefined,
manualThroughput: resource.throughput, manualThroughput: resource.throughput,
minimumThroughput minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true"
}; };
} }

View File

@@ -26,6 +26,7 @@ interface ConfigContext {
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
serverId?: string;
} }
// Default configuration // Default configuration

View File

@@ -214,7 +214,7 @@ export interface Offer {
manualThroughput: number; manualThroughput: number;
minimumThroughput: number; minimumThroughput: number;
offerDefinition?: SDKOfferDefinition; offerDefinition?: SDKOfferDefinition;
headers?: any; offerReplacePending: boolean;
} }
export interface SDKOfferDefinition extends Resource { export interface SDKOfferDefinition extends Resource {

View File

@@ -48,7 +48,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{ 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.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" }, { key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
{ {
key: "feature.enableLinkInjection", key: "feature.enableLinkInjection",
label: "Enable Injecting Notebook Viewer Link into the first cell", label: "Enable Injecting Notebook Viewer Link into the first cell",

View File

@@ -157,14 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
/> />
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enablecodeofconduct" key="feature.enableLinkInjection"
label="Enable Code Of Conduct Acknowledgement" label="Enable Injecting Notebook Viewer Link into the first cell"
onChange={[Function]} onChange={[Function]}
/> />
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enableLinkInjection" key="feature.canexceedmaximumvalue"
label="Enable Injecting Notebook Viewer Link into the first cell" 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"

View File

@@ -1,6 +1,7 @@
import { import {
Dropdown, Dropdown,
FocusZone, FocusZone,
FontIcon,
FontWeights, FontWeights,
IDropdownOption, IDropdownOption,
IPageSpecification, IPageSpecification,
@@ -16,7 +17,7 @@ import {
Text Text
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient"; import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent"; import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
@@ -136,7 +137,7 @@ 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.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
if (this.props.container?.isGalleryPublishEnabled()) { if (this.props.container?.isGalleryPublishEnabled()) {
tabs.push( tabs.push(
@@ -146,7 +147,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
this.state.isCodeOfConductAccepted this.state.isCodeOfConductAccepted
) )
); );
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks)); tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined. // explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
// Displaying code of conduct component on gallery load should not be the default behavior. // Displaying code of conduct component on gallery load should not be the default behavior.
@@ -183,6 +184,27 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
} }
private isEmptyData = (data: IGalleryItem[]): boolean => {
return !data || data.length === 0;
};
private createEmptyTabContent = (iconName: string, line1: string, line2: string): JSX.Element => {
return (
<Stack horizontalAlign="center" tokens={{ childrenGap: 10 }}>
<FontIcon iconName={iconName} style={{ fontSize: 100, color: "lightgray", marginTop: 20 }} />
<Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{line1}</Text>
<Text>{line2}</Text>
</Stack>
);
};
private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
return {
tab,
content: this.createSearchBarHeader(this.createCardsTabContent(data))
};
};
private createPublicGalleryTab( private createPublicGalleryTab(
tab: GalleryTab, tab: GalleryTab,
data: IGalleryItem[], data: IGalleryItem[],
@@ -194,17 +216,29 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
} }
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo { private createFavoritesTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
return { return {
tab, tab,
content: this.createSearchBarHeader(this.createCardsTabContent(data)) content: this.isEmptyData(data)
? this.createEmptyTabContent(
"ContactHeart",
"You have not liked anything",
"Like any notebook from Official Samples or Public gallery"
)
: this.createSearchBarHeader(this.createCardsTabContent(data))
}; };
} }
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => { private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
return { return {
tab, tab,
content: this.createPublishedNotebooksTabContent(data) content: this.isEmptyData(data)
? this.createEmptyTabContent(
"Contact",
"You have not published anything",
"Publish your sample notebooks to share your published work with others"
)
: this.createPublishedNotebooksTabContent(data)
}; };
}; };
@@ -364,9 +398,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> { private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
if (!offline) { if (!offline) {
try { try {
let response: IJunoResponse<IPublicGalleryData> | IJunoResponse<IGalleryItem[]>; let response: IJunoResponse<IGalleryItem[]> | IJunoResponse<IPublicGalleryData>;
if (this.props.container.isCodeOfConductEnabled()) { if (this.props.container) {
response = await this.props.junoClient.fetchPublicNotebooks(); response = await this.props.junoClient.getPublicGalleryData();
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct; this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
this.publicNotebooks = response.data?.notebooksData; this.publicNotebooks = response.data?.notebooksData;
} else { } else {
@@ -568,7 +602,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => { GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
this.publishedNotebooks = this.publishedNotebooks.filter(notebook => item.id !== notebook.id); this.publishedNotebooks = this.publishedNotebooks?.filter(notebook => item.id !== notebook.id);
this.refreshSelectedTab(item); this.refreshSelectedTab(item);
}); });
}; };

View File

@@ -92,7 +92,8 @@ describe("SettingsComponent", () => {
autoscaleMaxThroughput: 10000, autoscaleMaxThroughput: 10000,
manualThroughput: undefined, manualThroughput: undefined,
minimumThroughput: 400, minimumThroughput: 400,
id: "test" id: "test",
offerReplacePending: false
}); });
const props = { ...baseProps }; const props = { ...baseProps };

View File

@@ -295,7 +295,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
!!this.collection.conflictResolutionPolicy(); !!this.collection.conflictResolutionPolicy();
public isOfferReplacePending = (): boolean => { public isOfferReplacePending = (): boolean => {
return !!this.collection?.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending]; return this.collection?.offer()?.offerReplacePending;
}; };
public onSaveClick = async (): Promise<void> => { public onSaveClick = async (): Promise<void> => {

View File

@@ -59,7 +59,7 @@ describe("ScaleComponent", () => {
autoscaleMaxThroughput: maxThroughput, autoscaleMaxThroughput: maxThroughput,
minimumThroughput: 400, minimumThroughput: 400,
id: "offer", id: "offer",
headers: { "x-ms-offer-replace-pending": true } offerReplacePending: true
}); });
const newProps = { const newProps = {
...baseProps, ...baseProps,

View File

@@ -116,7 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
} }
const offer = this.props.collection?.offer(); const offer = this.props.collection?.offer();
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) { if (offer?.offerReplacePending) {
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput; const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
return getThroughputApplyShortDelayMessage( return getThroughputApplyShortDelayMessage(
this.props.isAutoPilotSelected, this.props.isAutoPilotSelected,
@@ -165,7 +165,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
private getThroughputInputComponent = (): JSX.Element => ( private getThroughputInputComponent = (): JSX.Element => (
<ThroughputInputAutoPilotV3Component <ThroughputInputAutoPilotV3Component
databaseAccount={this.props.container.databaseAccount()} databaseAccount={this.props.container.databaseAccount()}
serverId={this.props.container.serverId()} serverId={configContext.serverId}
throughput={this.props.throughput} throughput={this.props.throughput}
throughputBaseline={this.props.throughputBaseline} throughputBaseline={this.props.throughputBaseline}
onThroughputChange={this.props.onThroughputChange} onThroughputChange={this.props.onThroughputChange}

View File

@@ -23,7 +23,8 @@ export const collection = ({
autoscaleMaxThroughput: undefined, autoscaleMaxThroughput: undefined,
manualThroughput: 10000, manualThroughput: 10000,
minimumThroughput: 6000, minimumThroughput: 6000,
id: "offer" id: "offer",
offerReplacePending: false
}), }),
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>( conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
{} as DataModels.ConflictResolutionPolicy {} as DataModels.ConflictResolutionPolicy

View File

@@ -731,7 +731,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"arcadiaToken": [Function], "arcadiaToken": [Function],
"armEndpoint": [Function],
"browseQueriesPane": BrowseQueriesPane { "browseQueriesPane": BrowseQueriesPane {
"canSaveQueries": [Function], "canSaveQueries": [Function],
"container": [Circular], "container": [Circular],
@@ -943,7 +942,6 @@ exports[`SettingsComponent renders 1`] = `
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -1024,7 +1022,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "onToggleKeyDown": [Function],
"parentFrameDataExplorerVersion": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
"container": [Circular], "container": [Circular],
@@ -1050,7 +1047,6 @@ exports[`SettingsComponent renders 1`] = `
"titleLabel": "Select Columns", "titleLabel": "Select Columns",
"visible": [Function], "visible": [Function],
}, },
"quotaId": [Function],
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
@@ -2006,7 +2002,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"arcadiaToken": [Function], "arcadiaToken": [Function],
"armEndpoint": [Function],
"browseQueriesPane": BrowseQueriesPane { "browseQueriesPane": BrowseQueriesPane {
"canSaveQueries": [Function], "canSaveQueries": [Function],
"container": [Circular], "container": [Circular],
@@ -2218,7 +2213,6 @@ exports[`SettingsComponent renders 1`] = `
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -2299,7 +2293,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "onToggleKeyDown": [Function],
"parentFrameDataExplorerVersion": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
"container": [Circular], "container": [Circular],
@@ -2325,7 +2318,6 @@ exports[`SettingsComponent renders 1`] = `
"titleLabel": "Select Columns", "titleLabel": "Select Columns",
"visible": [Function], "visible": [Function],
}, },
"quotaId": [Function],
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
@@ -3294,7 +3286,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"arcadiaToken": [Function], "arcadiaToken": [Function],
"armEndpoint": [Function],
"browseQueriesPane": BrowseQueriesPane { "browseQueriesPane": BrowseQueriesPane {
"canSaveQueries": [Function], "canSaveQueries": [Function],
"container": [Circular], "container": [Circular],
@@ -3506,7 +3497,6 @@ exports[`SettingsComponent renders 1`] = `
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -3587,7 +3577,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "onToggleKeyDown": [Function],
"parentFrameDataExplorerVersion": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
"container": [Circular], "container": [Circular],
@@ -3613,7 +3602,6 @@ exports[`SettingsComponent renders 1`] = `
"titleLabel": "Select Columns", "titleLabel": "Select Columns",
"visible": [Function], "visible": [Function],
}, },
"quotaId": [Function],
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
@@ -4569,7 +4557,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"arcadiaToken": [Function], "arcadiaToken": [Function],
"armEndpoint": [Function],
"browseQueriesPane": BrowseQueriesPane { "browseQueriesPane": BrowseQueriesPane {
"canSaveQueries": [Function], "canSaveQueries": [Function],
"container": [Circular], "container": [Circular],
@@ -4781,7 +4768,6 @@ exports[`SettingsComponent renders 1`] = `
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -4862,7 +4848,6 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "onToggleKeyDown": [Function],
"parentFrameDataExplorerVersion": [Function],
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
"container": [Circular], "container": [Circular],
@@ -4888,7 +4873,6 @@ exports[`SettingsComponent renders 1`] = `
"titleLabel": "Select Columns", "titleLabel": "Select Columns",
"visible": [Function], "visible": [Function],
}, },
"quotaId": [Function],
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],

View File

@@ -126,6 +126,12 @@
</div> </div>
<div data-bind="visible: !isAutoPilotSelected()"> <div data-bind="visible: !isAutoPilotSelected()">
<p>
<span
>Estimate your required throughput with
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
>
</p>
<div data-bind="setTemplateReady: true"> <div data-bind="setTemplateReady: true">
<input <input
data-bind=" data-bind="

View File

@@ -18,7 +18,7 @@ import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPa
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases"; import { readDatabases } from "../Common/dataAccess/readDatabases";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
import EnvironmentUtility from "../Common/EnvironmentUtility"; import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import GraphStylingPane from "./Panes/GraphStylingPane"; import GraphStylingPane from "./Panes/GraphStylingPane";
import hasher from "hasher"; import hasher from "hasher";
import NewVertexPane from "./Panes/NewVertexPane"; import NewVertexPane from "./Panes/NewVertexPane";
@@ -121,7 +121,6 @@ export default class Explorer {
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>; public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults; public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
public subscriptionType: ko.Observable<SubscriptionType>; public subscriptionType: ko.Observable<SubscriptionType>;
public quotaId: ko.Observable<string>;
public defaultExperience: ko.Observable<string>; public defaultExperience: ko.Observable<string>;
public isPreferredApiDocumentDB: ko.Computed<boolean>; public isPreferredApiDocumentDB: ko.Computed<boolean>;
public isPreferredApiCassandra: ko.Computed<boolean>; public isPreferredApiCassandra: ko.Computed<boolean>;
@@ -134,13 +133,10 @@ export default class Explorer {
public isAccountReady: ko.Observable<boolean>; public isAccountReady: ko.Observable<boolean>;
public canSaveQueries: ko.Computed<boolean>; public canSaveQueries: ko.Computed<boolean>;
public features: ko.Observable<any>; public features: ko.Observable<any>;
public serverId: ko.Observable<string>;
public armEndpoint: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>; public isTryCosmosDBSubscription: ko.Observable<boolean>;
public queriesClient: QueriesClient; public queriesClient: QueriesClient;
public tableDataClient: TableDataClient; public tableDataClient: TableDataClient;
public splitter: Splitter; public splitter: Splitter;
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
public mostRecentActivity: MostRecentActivity.MostRecentActivity; public mostRecentActivity: MostRecentActivity.MostRecentActivity;
// Notification Console // Notification Console
@@ -204,7 +200,6 @@ export default class Explorer {
// features // features
public isGalleryPublishEnabled: ko.Computed<boolean>; public isGalleryPublishEnabled: ko.Computed<boolean>;
public isCodeOfConductEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>; public isLinkInjectionEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
@@ -279,7 +274,6 @@ export default class Explorer {
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>(); this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType); this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
this.quotaId = ko.observable<string>("");
let firstInitialization = true; let firstInitialization = true;
this.isRefreshingExplorer = ko.observable<boolean>(true); this.isRefreshingExplorer = ko.observable<boolean>(true);
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => { this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
@@ -319,9 +313,9 @@ export default class Explorer {
if (isAccountReady) { if (isAccountReady) {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true); this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler(); RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint()); this.notebookWorkspaceManager = new NotebookWorkspaceManager();
this.arcadiaWorkspaces = ko.observableArray(); this.arcadiaWorkspaces = ko.observableArray();
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint()); this._arcadiaManager = new ArcadiaResourceManager();
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered => this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
this.hasStorageAnalyticsAfecFeature(isRegistered) this.hasStorageAnalyticsAfecFeature(isRegistered)
); );
@@ -370,8 +364,6 @@ export default class Explorer {
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>(); this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.features = ko.observable(); this.features = ko.observable();
this.serverId = ko.observable<string>();
this.armEndpoint = ko.observable<string>(undefined);
this.queriesClient = new QueriesClient(this); this.queriesClient = new QueriesClient(this);
this.isTryCosmosDBSubscription = ko.observable<boolean>(false); this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
@@ -404,9 +396,6 @@ export default class Explorer {
this.isGalleryPublishEnabled = ko.computed<boolean>(() => this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableGalleryPublish) this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
); );
this.isCodeOfConductEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableCodeOfConduct)
);
this.isLinkInjectionEnabled = ko.computed<boolean>(() => this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection) this.isFeatureEnabled(Constants.Features.enableLinkInjection)
); );
@@ -1016,9 +1005,7 @@ export default class Explorer {
this.isSynapseLinkUpdating(true); this.isSynapseLinkUpdating(true);
this._closeSynapseLinkModalDialog(); this._closeSynapseLinkModalDialog();
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate( const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(this.databaseAccount().id);
this.databaseAccount().id
);
try { try {
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync( const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
@@ -1758,61 +1745,59 @@ export default class Explorer {
inputs.extensionEndpoint = configContext.PROXY_PATH; inputs.extensionEndpoint = configContext.PROXY_PATH;
} }
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q(); this.initDataExplorerWithFrameInputs(inputs);
initPromise.then(() => { const openAction: ActionContracts.DataExplorerAction = message.openAction;
const openAction: ActionContracts.DataExplorerAction = message.openAction; if (!!openAction) {
if (!!openAction) { if (this.isRefreshingExplorer()) {
if (this.isRefreshingExplorer()) { const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
handleOpenAction(openAction, this.nonSystemDatabases(), this);
subscription.dispose();
});
} else {
handleOpenAction(openAction, this.nonSystemDatabases(), this); handleOpenAction(openAction, this.nonSystemDatabases(), this);
} subscription.dispose();
});
} else {
handleOpenAction(openAction, this.nonSystemDatabases(), this);
} }
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) { }
handleCachedDataMessage(message); if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
return; handleCachedDataMessage(message);
} return;
if (message.type) { }
switch (message.type) { if (message.type) {
case MessageTypes.UpdateLocationHash: switch (message.type) {
if (!message.locationHash) { case MessageTypes.UpdateLocationHash:
break; if (!message.locationHash) {
} break;
hasher.replaceHash(message.locationHash); }
RouteHandler.getInstance().parseHash(message.locationHash); hasher.replaceHash(message.locationHash);
break; RouteHandler.getInstance().parseHash(message.locationHash);
case MessageTypes.SendNotification: break;
if (!message.message) { case MessageTypes.SendNotification:
break; if (!message.message) {
} break;
NotificationConsoleUtils.logConsoleMessage( }
message.consoleDataType || ConsoleDataType.Info, NotificationConsoleUtils.logConsoleMessage(
message.message, message.consoleDataType || ConsoleDataType.Info,
message.id message.message,
); message.id
break; );
case MessageTypes.ClearNotification: break;
if (!message.id) { case MessageTypes.ClearNotification:
break; if (!message.id) {
} break;
NotificationConsoleUtils.clearInProgressMessageWithId(message.id); }
break; NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
case MessageTypes.LoadingStatus: break;
if (!message.text) { case MessageTypes.LoadingStatus:
break; if (!message.text) {
} break;
this._setLoadingStatusText(message.text, message.title); }
break; this._setLoadingStatusText(message.text, message.title);
} break;
return;
} }
return;
}
this.splashScreenAdapter.forceRender(); this.splashScreenAdapter.forceRender();
});
} }
public findSelectedDatabase(): ViewModels.Database { public findSelectedDatabase(): ViewModels.Database {
@@ -1852,8 +1837,14 @@ export default class Explorer {
return false; return false;
} }
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> { public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
if (inputs != null) { if (inputs != null) {
// In development mode, save the iframe message from the portal in session storage.
// This allows webpack hot reload to funciton properly
if (process.env.NODE_ENV === "development") {
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
}
const authorizationToken = inputs.authorizationToken || ""; const authorizationToken = inputs.authorizationToken || "";
const masterKey = inputs.masterKey || ""; const masterKey = inputs.masterKey || "";
const databaseAccount = inputs.databaseAccount || null; const databaseAccount = inputs.databaseAccount || null;
@@ -1861,26 +1852,19 @@ export default class Explorer {
this.collectionCreationDefaults = inputs.defaultCollectionThroughput; this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
} }
this.features(inputs.features); this.features(inputs.features);
this.serverId(inputs.serverId);
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
this.databaseAccount(databaseAccount); this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType); this.subscriptionType(inputs.subscriptionType);
this.quotaId(inputs.quotaId);
this.hasWriteAccess(inputs.hasWriteAccess); this.hasWriteAccess(inputs.hasWriteAccess);
this.flight(inputs.addCollectionDefaultFlight); this.flight(inputs.addCollectionDefaultFlight);
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription); this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken); this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
this.setFeatureFlagsFromFlights(inputs.flights); this.setFeatureFlagsFromFlights(inputs.flights);
if (!!inputs.dataExplorerVersion) {
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
}
this._importExplorerConfigComplete = true; this._importExplorerConfigComplete = true;
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: inputs.extensionEndpoint || "", BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
ARM_ENDPOINT: this.armEndpoint() ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
serverId: inputs.serverId
}); });
updateUserContext({ updateUserContext({
@@ -1889,7 +1873,8 @@ export default class Explorer {
databaseAccount, databaseAccount,
resourceGroup: inputs.resourceGroup, resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId, subscriptionId: inputs.subscriptionId,
subscriptionType: inputs.subscriptionType subscriptionType: inputs.subscriptionType,
quotaId: inputs.quotaId
}); });
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount, Action.LoadDatabaseAccount,
@@ -1903,7 +1888,6 @@ export default class Explorer {
this.isAccountReady(true); this.isAccountReady(true);
} }
return Q();
} }
public setFeatureFlagsFromFlights(flights: readonly string[]): void { public setFeatureFlagsFromFlights(flights: readonly string[]): void {
@@ -1967,9 +1951,9 @@ export default class Explorer {
public isRunningOnNationalCloud(): boolean { public isRunningOnNationalCloud(): boolean {
return ( return (
this.serverId() === Constants.ServerIds.blackforest || userContext === Constants.ServerIds.blackforest ||
this.serverId() === Constants.ServerIds.fairfax || userContext === Constants.ServerIds.fairfax ||
this.serverId() === Constants.ServerIds.mooncake userContext === Constants.ServerIds.mooncake
); );
} }
@@ -2276,7 +2260,6 @@ export default class Explorer {
name, name,
content, content,
parentDomElement, parentDomElement,
this.isCodeOfConductEnabled(),
this.isLinkInjectionEnabled() this.isLinkInjectionEnabled()
); );
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter; this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
@@ -2567,7 +2550,7 @@ export default class Explorer {
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => { public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const armEndpoint = this.armEndpoint(); const armEndpoint = configContext.ARM_ENDPOINT;
const authType = window.authType as AuthType; const authType = window.authType as AuthType;
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
// explorer is not aware of the database account yet // explorer is not aware of the database account yet
@@ -2576,7 +2559,7 @@ export default class Explorer {
} }
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`; const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri); const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
try { try {
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync( const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
featureUri, featureUri,
@@ -2596,7 +2579,7 @@ export default class Explorer {
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => { public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const armEndpoint = this.armEndpoint(); const armEndpoint = configContext.ARM_ENDPOINT;
const authType = window.authType as AuthType; const authType = window.authType as AuthType;
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
// explorer is not aware of the database account yet // explorer is not aware of the database account yet
@@ -2604,7 +2587,7 @@ export default class Explorer {
} }
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`; const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri); const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
try { try {
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync( const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
featureUri, featureUri,

View File

@@ -128,17 +128,9 @@ export default class NotebookManager {
name: string, name: string,
content: string | ImmutableNotebook, content: string | ImmutableNotebook,
parentDomElement: HTMLElement, parentDomElement: HTMLElement,
isCodeOfConductEnabled: boolean,
isLinkInjectionEnabled: boolean isLinkInjectionEnabled: boolean
): Promise<void> { ): Promise<void> {
await this.publishNotebookPaneAdapter.open( await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement, isLinkInjectionEnabled);
name,
getFullName(),
content,
parentDomElement,
isCodeOfConductEnabled,
isLinkInjectionEnabled
);
} }
public openCopyNotebookPane(name: string, content: string): void { public openCopyNotebookPane(name: string, content: string): void {

View File

@@ -16,6 +16,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { createCollection } from "../../Common/dataAccess/createCollection";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { userContext } from "../../UserContext";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions { export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed<boolean>; isPreferredApiTable: ko.Computed<boolean>;
@@ -185,7 +186,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const serverId: string = this.container.serverId(); const serverId = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -199,7 +200,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (!this.isSharedAutoPilotSelected()) { if (!this.isSharedAutoPilotSelected()) {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput, offerThroughput,
serverId, configContext.serverId,
regions, regions,
multimaster, multimaster,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
@@ -239,7 +240,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const serverId: string = this.container.serverId(); const serverId: string = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -481,7 +482,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
}); });
this.upsellMessage = ko.pureComputed<string>(() => { this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount()); return PricingUtils.getUpsellMessage(configContext.serverId, this.isFreeTierAccount());
}); });
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => { this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
@@ -668,7 +669,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
databaseId: this.databaseId() databaseId: this.databaseId()
}), }),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
throughput: this._getThroughput(), throughput: this._getThroughput(),
@@ -770,7 +771,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
throughput: offerThroughput, throughput: offerThroughput,
@@ -844,7 +845,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
throughput: offerThroughput, throughput: offerThroughput,
@@ -878,7 +879,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}, },
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
throughput: offerThroughput, throughput: offerThroughput,

View File

@@ -13,6 +13,7 @@ import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { SubscriptionType } from "../../Contracts/SubscriptionType"; import { SubscriptionType } from "../../Contracts/SubscriptionType";
import { userContext } from "../../UserContext";
export default class AddDatabasePane extends ContextualPaneBase { export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>; public defaultExperience: ko.Computed<string>;
@@ -121,7 +122,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
return ""; return "";
} }
const serverId = this.container.serverId(); const serverId = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -219,7 +220,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
}); });
this.upsellMessage = ko.pureComputed<string>(() => { this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount()); return PricingUtils.getUpsellMessage(configContext.serverId, this.isFreeTierAccount());
}); });
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => { this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
@@ -250,7 +251,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
throughput: this.throughput(), throughput: this.throughput(),
flight: this.container.flight() flight: this.container.flight()
@@ -278,7 +279,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
}), }),
offerThroughput, offerThroughput,
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
flight: this.container.flight() flight: this.container.flight()
}, },
@@ -342,7 +343,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
}), }),
offerThroughput: offerThroughput, offerThroughput: offerThroughput,
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
flight: this.container.flight() flight: this.container.flight()
}, },
@@ -366,7 +367,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
}), }),
offerThroughput: offerThroughput, offerThroughput: offerThroughput,
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
flight: this.container.flight() flight: this.container.flight()
}, },

View File

@@ -15,6 +15,7 @@ import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { SubscriptionType } from "../../Contracts/SubscriptionType"; import { SubscriptionType } from "../../Contracts/SubscriptionType";
import { userContext } from "../../UserContext";
export default class CassandraAddCollectionPane extends ContextualPaneBase { export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable<string>; public createTableQuery: ko.Observable<string>;
@@ -126,7 +127,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const serverId = this.container.serverId(); const serverId = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -171,7 +172,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const serverId = this.container.serverId(); const serverId = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -299,7 +300,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
databaseId: this.keyspaceId() databaseId: this.keyspaceId()
}), }),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: "u", storage: "u",
throughput: this.throughput(), throughput: this.throughput(),
@@ -353,7 +354,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: "u", storage: "u",
throughput: this.throughput(), throughput: this.throughput(),
@@ -399,7 +400,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: "u", storage: "u",
throughput: this.throughput(), throughput: this.throughput(),
@@ -429,7 +430,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}, },
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: "u", storage: "u",
throughput: this.throughput(), throughput: this.throughput(),

View File

@@ -98,26 +98,21 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
author: string, author: string,
notebookContent: string | ImmutableNotebook, notebookContent: string | ImmutableNotebook,
parentDomElement: HTMLElement, parentDomElement: HTMLElement,
isCodeOfConductEnabled: boolean,
isLinkInjectionEnabled: boolean isLinkInjectionEnabled: boolean
): Promise<void> { ): Promise<void> {
if (isCodeOfConductEnabled) { try {
try { const response = await this.junoClient.isCodeOfConductAccepted();
const response = await this.junoClient.isCodeOfConductAccepted(); if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
this.isCodeOfConductAccepted = response.data;
} catch (error) {
handleError(
error,
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
"Failed to check if code of conduct was accepted"
);
} }
} else {
this.isCodeOfConductAccepted = true; this.isCodeOfConductAccepted = response.data;
} catch (error) {
handleError(
error,
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
"Failed to check if code of conduct was accepted"
);
} }
this.name = name; this.name = name;

View File

@@ -139,7 +139,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return ""; return "";
} }
const serverId = this.container.serverId(); const serverId = configContext.serverId;
const regions = const regions =
(account && (account &&
account.properties && account.properties &&
@@ -230,9 +230,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return this.throughputTitle() + this.requestUnitsUsageCost(); return this.throughputTitle() + this.requestUnitsUsageCost();
}); });
this.pendingNotification = ko.observable<DataModels.Notification>(); this.pendingNotification = ko.observable<DataModels.Notification>();
this._offerReplacePending = ko.observable<boolean>( this._offerReplacePending = ko.observable<boolean>(!!this.database.offer()?.offerReplacePending);
!!this.database.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending]
);
this.notificationStatusInfo = ko.observable<string>(""); this.notificationStatusInfo = ko.observable<string>("");
this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0); this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0);
this.warningMessage = ko.computed<string>(() => { this.warningMessage = ko.computed<string>(() => {
@@ -241,7 +239,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
} }
const offer = this.database.offer(); const offer = this.database.offer();
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) { if (offer?.offerReplacePending) {
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput; const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id()); return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
} }

View File

@@ -33,7 +33,7 @@ export default class MongoShellTab extends TabsBase {
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : ""; this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || ""; const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/"; let baseUrl = "/content/mongoshell/dist/";
if (this._container.serverId() === "localhost") { if (configContext.serverId === "localhost") {
baseUrl = "/content/mongoshell/"; baseUrl = "/content/mongoshell/";
} }

View File

@@ -178,8 +178,7 @@ export class JunoClient {
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`); return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
} }
// will be renamed once feature.enableCodeOfConduct flag is removed public async getPublicGalleryData(): Promise<IJunoResponse<IPublicGalleryData>> {
public async fetchPublicNotebooks(): Promise<IJunoResponse<IPublicGalleryData>> {
const url = `${this.getNotebooksAccountUrl()}/gallery/public`; const url = `${this.getNotebooksAccountUrl()}/gallery/public`;
const response = await window.fetch(url, { const response = await window.fetch(url, {
method: "PATCH", method: "PATCH",
@@ -405,7 +404,7 @@ export class JunoClient {
} }
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> { public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
const response = await window.fetch(`${this.getNotebooksUrl()}/avert/reportAbuse`, { const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/reportAbuse`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
notebookId, notebookId,

View File

@@ -12,8 +12,8 @@ import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export class NotebookWorkspaceManager { export class NotebookWorkspaceManager {
private resourceProviderClientFactory: IResourceProviderClientFactory<any>; private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
constructor(private _armEndpoint: string) { constructor() {
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this._armEndpoint); this.resourceProviderClientFactory = new ResourceProviderClientFactory();
} }
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> { public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {

View File

@@ -19,6 +19,7 @@ export function initializeExplorer(): Explorer {
cassandraEndpoint: "" cassandraEndpoint: ""
} }
}); });
explorer.isAccountReady(true); explorer.isAccountReady(true);
return explorer; return explorer;
} }

View File

@@ -268,7 +268,7 @@ export default class Main {
masterKey?: string /* master key extracted from connection string if available */, masterKey?: string /* master key extracted from connection string if available */,
account?: DatabaseAccount, account?: DatabaseAccount,
authorizationToken?: string /* access key */ authorizationToken?: string /* access key */
): Q.Promise<void> { ): void {
const serverId: string = AuthHeadersUtil.serverId; const serverId: string = AuthHeadersUtil.serverId;
const authType: string = (<any>window).authType; const authType: string = (<any>window).authType;
const accountResourceId = const accountResourceId =
@@ -373,7 +373,7 @@ export default class Main {
}); });
} }
return Q.reject(`Unsupported AuthType ${authType}`); throw new Error(`Unsupported AuthType ${authType}`);
} }
private static _instantiateExplorer(): Explorer { private static _instantiateExplorer(): Explorer {

View File

@@ -1,9 +1,23 @@
import "../../Explorer/Tables/DataTable/DataTableBindingManager"; import "../../Explorer/Tables/DataTable/DataTableBindingManager";
import Explorer from "../../Explorer/Explorer"; import Explorer from "../../Explorer/Explorer";
import { handleMessage } from "../../Controls/Heatmap/Heatmap";
export function initializeExplorer(): Explorer { export function initializeExplorer(): Explorer {
const explorer = new Explorer(); const explorer = new Explorer();
// In development mode, try to load the iframe message from session storage.
// This allows webpack hot reload to funciton properly
if (process.env.NODE_ENV === "development") {
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
if (initMessage) {
const message = JSON.parse(initMessage);
console.warn("Loaded cached portal iframe message from session storage");
console.dir(message);
explorer.initDataExplorerWithFrameInputs(message);
}
}
window.addEventListener("message", explorer.handleMessage.bind(explorer), false); window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
return explorer; return explorer;
} }

View File

@@ -1,10 +1,14 @@
import { configContext } from "../ConfigContext";
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient"; import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
import { ResourceProviderClient } from "./ResourceProviderClient"; import { ResourceProviderClient } from "./ResourceProviderClient";
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> { export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
private armEndpoint: string;
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {}; private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
constructor(private armEndpoint: string) {} constructor() {
this.armEndpoint = configContext.ARM_ENDPOINT;
}
public getOrCreate(url: string): IResourceProviderClient<any> { public getOrCreate(url: string): IResourceProviderClient<any> {
if (!url) { if (!url) {

View File

@@ -144,10 +144,6 @@ export class OfferPricing {
}; };
} }
export class GeneralResources {
public static loadingText: string = "Loading...";
}
export class CollectionCreation { export class CollectionCreation {
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml // TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
public static readonly MinRUPerPartitionBelow7Partitions: number = 400; public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
@@ -228,32 +224,6 @@ export class IndexingPolicies {
} }
export class SubscriptionUtilMappings { export class SubscriptionUtilMappings {
// TODO: Expose this through a web API from the portal
public static SubscriptionTypeMap: { [key: string]: SubscriptionType } = {
"AAD_2015-09-01": SubscriptionType.Free,
"AzureDynamics_2014-09-01": SubscriptionType.Free,
"AzureInOpen_2014-09-01": SubscriptionType.EA,
"AzurePass_2014-09-01": SubscriptionType.Free,
"BackupStorage_2014-09-01": SubscriptionType.PAYG,
"BizSpark_2014-09-01": SubscriptionType.Benefits,
"BizSparkPlus_2014-09-01": SubscriptionType.Benefits,
"CSP_2015-05-01": SubscriptionType.EA,
"Default_2014-09-01": SubscriptionType.PAYG,
"DevEssentials_2016-01-01": SubscriptionType.Benefits,
"DreamSpark_2015-02-01": SubscriptionType.Benefits,
"EnterpriseAgreement_2014-09-01": SubscriptionType.EA,
"FreeTrial_2014-09-01": SubscriptionType.Free,
"Internal_2014-09-01": SubscriptionType.Internal,
"LegacyMonetaryCommitment_2014-09-01": SubscriptionType.EA,
"LightweightTrial_2016-09-01": SubscriptionType.Free,
"MonetaryCommitment_2015-05-01": SubscriptionType.EA,
"MPN_2014-09-01": SubscriptionType.Benefits,
"MSDN_2014-09-01": SubscriptionType.Benefits,
"MSDNDevTest_2014-09-01": SubscriptionType.Benefits,
"PayAsYouGo_2014-09-01": SubscriptionType.PAYG,
"Sponsored_2016-01-01": SubscriptionType.Benefits
};
public static FreeTierSubscriptionIds: string[] = [ public static FreeTierSubscriptionIds: string[] = [
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2", "b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea", "39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
@@ -264,57 +234,6 @@ export class SubscriptionUtilMappings {
]; ];
} }
export class Offers {
public static offerTypeS1: string = "S1";
public static offerTypeS2: string = "S2";
public static offerTypeS3: string = "S3";
public static offerTypeStandard: string = "Standard";
}
export class OfferThoughput {
public static offerS1Throughput: number = 250;
public static offerS2Throughput: number = 1000;
public static offerS3Throughput: number = 2500;
}
export class OfferVersions {
public static offerV1: string = "V1";
public static offerV2: string = "V2";
}
export class InvalidOffers {
public static offerTypeInvalid: string = "Invalid";
public static offerTypeError: string = "Loading Error";
}
export class SpecTypes {
public static collection: string = "DocumentDbCollection";
}
export class CurrencyCodes {
public static usd: string = "USD";
public static rmb: string = "RMB";
}
export class ColorSchemes {
public static standard: string = "mediumBlue";
public static legacy: string = "yellowGreen";
}
export class FeatureIds {
public static storage: string = "storage";
public static sla: string = "sla";
public static partitioned: string = "partitioned";
public static singlePartitioned: string = "singlePartition";
public static legacySinglePartitioned: string = "legacySinglePartition";
}
export class FeatureIconNames {
public static storage: string = "SSD";
public static sla: string = "Monitoring";
public static productionReady: string = "ProductionReadyDb";
}
export class AutopilotDocumentation { export class AutopilotDocumentation {
public static Url: string = "https://aka.ms/cosmos-autoscale-info"; public static Url: string = "https://aka.ms/cosmos-autoscale-info";
} }

View File

@@ -8,14 +8,13 @@ import { ArmApiVersions, ArmResourceTypes } from "../Common/Constants";
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient"; import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory"; import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { configContext } from "../ConfigContext";
import { getErrorMessage } from "../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export class ArcadiaResourceManager { export class ArcadiaResourceManager {
private resourceProviderClientFactory: IResourceProviderClientFactory<any>; private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
constructor(private armEndpoint = configContext.ARM_ENDPOINT) { constructor() {
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this.armEndpoint); this.resourceProviderClientFactory = new ResourceProviderClientFactory();
} }
public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> { public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> {

View File

@@ -59,9 +59,8 @@ const main = async (): Promise<void> => {
const serverSettings = createServerSettings(urlVars); const serverSettings = createServerSettings(urlVars);
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, { const data = { baseUrl: serverSettings.baseUrl };
baseUrl: serverSettings.baseUrl const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
});
try { try {
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) { if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
@@ -70,9 +69,9 @@ const main = async (): Promise<void> => {
throw new Error("Only terminal is supported"); throw new Error("Only terminal is supported");
} }
TelemetryProcessor.traceSuccess(Action.OpenTerminal, startTime); TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
} catch (error) { } catch (error) {
TelemetryProcessor.traceFailure(Action.OpenTerminal, startTime); TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
} }
}; };

View File

@@ -14,6 +14,7 @@ interface UserContext {
defaultExperience?: DefaultAccountExperienceType; defaultExperience?: DefaultAccountExperienceType;
useSDKOperations?: boolean; useSDKOperations?: boolean;
subscriptionType?: SubscriptionType; subscriptionType?: SubscriptionType;
quotaId?: string;
} }
const userContext: Readonly<UserContext> = {} as const; const userContext: Readonly<UserContext> = {} as const;

View File

@@ -10,6 +10,7 @@ import Explorer from "../Explorer/Explorer";
import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react"; import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent"; import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { handleError } from "../Common/ErrorHandlingUtils"; import { handleError } from "../Common/ErrorHandlingUtils";
import { HttpStatusCodes } from "../Common/Constants";
const defaultSelectedAbuseCategory = "Other"; const defaultSelectedAbuseCategory = "Other";
const abuseCategories: IChoiceGroupOption[] = [ const abuseCategories: IChoiceGroupOption[] = [
@@ -113,7 +114,7 @@ export function reportAbuse(
try { try {
const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails); const response = await junoClient.reportAbuse(notebookId, abuseCategory, additionalDetails);
if (!response.data) { if (response.status !== HttpStatusCodes.Accepted) {
throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`); throw new Error(`Received HTTP ${response.status} when submitting report for ${data.name}`);
} }

View File

@@ -242,7 +242,7 @@ describe("PricingUtils Tests", () => {
}); });
describe("getEstimatedSpendHtml()", () => { describe("getEstimatedSpendHtml()", () => {
it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster", () => { it("should return 'Cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 1RU/s on default cloud, 1 region, with multimaster", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
@@ -250,11 +250,11 @@ describe("PricingUtils Tests", () => {
true /* multimaster */ true /* multimaster */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)" "Cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
); );
}); });
it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster", () => { it("should return 'Cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 1RU/s on mooncake, 1 region, with multimaster", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"mooncake" /* cloud */, "mooncake" /* cloud */,
@@ -262,11 +262,11 @@ describe("PricingUtils Tests", () => {
true /* multimaster */ true /* multimaster */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)" "Cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster", () => { it("should return 'Cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 400RU/s on default cloud, 2 region, with multimaster", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
@@ -274,11 +274,11 @@ describe("PricingUtils Tests", () => {
true /* multimaster */ true /* multimaster */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)" "Cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster", () => { it("should return 'Cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>' for 400RU/s on default cloud, 2 region, without multimaster", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
@@ -286,7 +286,7 @@ describe("PricingUtils Tests", () => {
false /* multimaster */ false /* multimaster */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)" "Cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)<p style='padding: 10px 0px 0px 0px;'><em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>"
); );
}); });
}); });

View File

@@ -211,11 +211,13 @@ export function getEstimatedSpendHtml(
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster); const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
return ( return (
`Estimated cost (${currency}): <b>` + `Cost (${currency}): <b>` +
`${currencySign}${calculateEstimateNumber(hourlyPrice)} hourly / ` + `${currencySign}${calculateEstimateNumber(hourlyPrice)} hourly / ` +
`${currencySign}${calculateEstimateNumber(dailyPrice)} daily / ` + `${currencySign}${calculateEstimateNumber(dailyPrice)} daily / ` +
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` + `${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` `(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
`<p style='padding: 10px 0px 0px 0px;'>` +
`<em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>`
); );
} }