mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-06-05 15:12:04 +01:00
Merge branch 'master' of https://github.com/MokireddySampath/cosmos-explorer
This commit is contained in:
commit
d63f1251dd
@ -2576,6 +2576,10 @@ a:link {
|
|||||||
.querydropdown.placeholderVisible {
|
.querydropdown.placeholderVisible {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||||
|
color: #767474;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.querydropdown:hover {
|
.querydropdown:hover {
|
||||||
background-color: @AccentLow;
|
background-color: @AccentLow;
|
||||||
@ -3087,3 +3091,4 @@ a:link {
|
|||||||
background: white;
|
background: white;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,17 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
.databaseHeader {
|
.databaseHeader {
|
||||||
|
padding: 1px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectionHeader {
|
.collectionHeader {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadMoreHeader {
|
||||||
|
color: RGB(5, 99, 193);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookResourceTree {
|
.notebookResourceTree {
|
||||||
@ -23,6 +28,4 @@
|
|||||||
.clickDisabled {
|
.clickDisabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ export class Queries {
|
|||||||
public static UnlimitedPageOption: string = "unlimited";
|
public static UnlimitedPageOption: string = "unlimited";
|
||||||
public static itemsPerPage: number = 100;
|
public static itemsPerPage: number = 100;
|
||||||
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
||||||
|
public static containersPerPage: number = 50;
|
||||||
public static QueryEditorMinHeightRatio: number = 0.1;
|
public static QueryEditorMinHeightRatio: number = 0.1;
|
||||||
public static QueryEditorMaxHeightRatio: number = 0.4;
|
public static QueryEditorMaxHeightRatio: number = 0.4;
|
||||||
public static readonly DefaultMaxDegreeOfParallelism = 6;
|
public static readonly DefaultMaxDegreeOfParallelism = 6;
|
||||||
|
14
src/Common/EnvironmentUtility.test.ts
Normal file
14
src/Common/EnvironmentUtility.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as EnvironmentUtility from "./EnvironmentUtility";
|
||||||
|
|
||||||
|
describe("Environment Utility Test", () => {
|
||||||
|
it("Test sample URI with /", () => {
|
||||||
|
const uri = "test/";
|
||||||
|
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Test sample URI without /", () => {
|
||||||
|
const uri = "test";
|
||||||
|
const expectedResult = "test/";
|
||||||
|
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import * as OfferUtility from "./OfferUtility";
|
|
||||||
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
|
|
||||||
import { OfferResponse } from "@azure/cosmos";
|
import { OfferResponse } from "@azure/cosmos";
|
||||||
|
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||||
|
import * as OfferUtility from "./OfferUtility";
|
||||||
|
|
||||||
describe("parseSDKOfferResponse", () => {
|
describe("parseSDKOfferResponse", () => {
|
||||||
it("manual throughput", () => {
|
it("manual throughput", () => {
|
||||||
@ -31,6 +31,26 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("offerContent not defined", () => {
|
||||||
|
const mockOfferDefinition = {
|
||||||
|
id: "test",
|
||||||
|
} as SDKOfferDefinition;
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
resource: mockOfferDefinition,
|
||||||
|
} as OfferResponse;
|
||||||
|
|
||||||
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("offerDefinition is null", () => {
|
||||||
|
const mockResponse = {
|
||||||
|
resource: undefined,
|
||||||
|
} as OfferResponse;
|
||||||
|
|
||||||
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it("autoscale throughput", () => {
|
it("autoscale throughput", () => {
|
||||||
const mockOfferDefinition = {
|
const mockOfferDefinition = {
|
||||||
content: {
|
content: {
|
||||||
|
@ -139,14 +139,15 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
|||||||
|
|
||||||
<TooltipHost content="Edit property" id="editTooltip">
|
<TooltipHost content="Edit property" id="editTooltip">
|
||||||
<div tabIndex={0}>
|
<div tabIndex={0}>
|
||||||
<Image
|
<Image
|
||||||
{...imageProps}
|
{...imageProps}
|
||||||
src={EditIcon}
|
src={EditIcon}
|
||||||
alt="editEntity"
|
alt="editEntity"
|
||||||
id="editEntity"
|
id="editEntity"
|
||||||
onClick={onEditEntity}
|
onClick={onEditEntity}
|
||||||
onKeyPress={handleKeyPress}
|
tabIndex={0}
|
||||||
/>
|
onKeyPress={handleKeyPress}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
|
|
||||||
|
49
src/Common/UrlUtility.test.ts
Normal file
49
src/Common/UrlUtility.test.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as UrlUtility from "./UrlUtility";
|
||||||
|
|
||||||
|
describe("parseDocumentsPath", () => {
|
||||||
|
it("empty resource path", () => {
|
||||||
|
const resourcePath = "";
|
||||||
|
|
||||||
|
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resourcePath does not begin or end with /", () => {
|
||||||
|
const resourcePath = "localhost/portal/home";
|
||||||
|
const expectedResult = {
|
||||||
|
type: "home",
|
||||||
|
objectBody: {
|
||||||
|
id: "portal",
|
||||||
|
self: "/localhost/portal/home/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resourcePath length is even", () => {
|
||||||
|
const resourcePath = "/localhost/portal/src/home/";
|
||||||
|
const expectedResult = {
|
||||||
|
type: "src",
|
||||||
|
objectBody: {
|
||||||
|
id: "home",
|
||||||
|
self: resourcePath,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(UrlUtility.parseDocumentsPath(resourcePath)).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createUri", () => {
|
||||||
|
const baseUri = "http://foo.com/bar/";
|
||||||
|
const relativeUri = "/index.html";
|
||||||
|
const expectedUri = "http://foo.com/bar/index.html";
|
||||||
|
|
||||||
|
expect(UrlUtility.createUri(baseUri, relativeUri)).toEqual(expectedUri);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if baseUri is empty", () => {
|
||||||
|
expect(() => {
|
||||||
|
UrlUtility.createUri("", "/home");
|
||||||
|
}).toThrow("baseUri is null or empty");
|
||||||
|
});
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Queries } from "Common/Constants";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@ -31,6 +32,35 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readCollectionsWithPagination(
|
||||||
|
databaseId: string,
|
||||||
|
continuationToken?: string
|
||||||
|
): Promise<DataModels.CollectionsWithPagination> {
|
||||||
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
|
try {
|
||||||
|
const sdkResponse = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.containers.query(
|
||||||
|
{ query: "SELECT * FROM c" },
|
||||||
|
{
|
||||||
|
continuationToken,
|
||||||
|
maxItemCount: Queries.containersPerPage,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fetchNext();
|
||||||
|
const collectionsWithPagination: DataModels.CollectionsWithPagination = {
|
||||||
|
collections: sdkResponse.resources as DataModels.Collection[],
|
||||||
|
continuationToken: sdkResponse.continuationToken,
|
||||||
|
};
|
||||||
|
return collectionsWithPagination;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
let rpResponse;
|
let rpResponse;
|
||||||
|
|
||||||
|
@ -156,6 +156,11 @@ export interface Collection extends Resource {
|
|||||||
requestSchema?: () => void;
|
requestSchema?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CollectionsWithPagination {
|
||||||
|
continuationToken?: string;
|
||||||
|
collections?: Collection[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Database extends Resource {
|
export interface Database extends Resource {
|
||||||
collections?: Collection[];
|
collections?: Collection[];
|
||||||
}
|
}
|
||||||
|
@ -87,13 +87,13 @@ export interface Database extends TreeNode {
|
|||||||
isDatabaseExpanded: ko.Observable<boolean>;
|
isDatabaseExpanded: ko.Observable<boolean>;
|
||||||
isDatabaseShared: ko.Computed<boolean>;
|
isDatabaseShared: ko.Computed<boolean>;
|
||||||
isSampleDB?: boolean;
|
isSampleDB?: boolean;
|
||||||
|
collectionsContinuationToken?: string;
|
||||||
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
|
||||||
|
|
||||||
expandDatabase(): Promise<void>;
|
expandDatabase(): Promise<void>;
|
||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
loadCollections(): Promise<void>;
|
loadCollections(restart?: boolean): Promise<void>;
|
||||||
findCollectionWithId(collectionId: string): Collection;
|
findCollectionWithId(collectionId: string): Collection;
|
||||||
openAddCollection(database: Database, event: MouseEvent): void;
|
openAddCollection(database: Database, event: MouseEvent): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
|
@ -46,10 +46,13 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let className = "toggleSwitch";
|
let className = "toggleSwitch";
|
||||||
|
let ariaselected;
|
||||||
if (index === this.props.currentTabIndex) {
|
if (index === this.props.currentTabIndex) {
|
||||||
className += " selectedToggle";
|
className += " selectedToggle";
|
||||||
|
ariaselected = true;
|
||||||
} else {
|
} else {
|
||||||
className += " unselectedToggle";
|
className += " unselectedToggle";
|
||||||
|
ariaselected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -57,9 +60,10 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
|||||||
<AccessibleElement
|
<AccessibleElement
|
||||||
as="span"
|
as="span"
|
||||||
className={className}
|
className={className}
|
||||||
role="presentation"
|
role="tab"
|
||||||
onActivated={() => this.setActiveTab(index)}
|
onActivated={() => this.setActiveTab(index)}
|
||||||
aria-label={`Select tab: ${tab.title}`}
|
aria-label={`Select tab: ${tab.title}`}
|
||||||
|
aria-selected={ariaselected}
|
||||||
>
|
>
|
||||||
{tab.title}
|
{tab.title}
|
||||||
</AccessibleElement>
|
</AccessibleElement>
|
||||||
@ -77,7 +81,11 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tabComponentContainer">
|
<div className="tabComponentContainer">
|
||||||
{!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
|
{!this.props.hideHeader && (
|
||||||
|
<div className="tabs tabSwitch" role="tablist">
|
||||||
|
{this.renderTabTitles()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className={className}>{currentTabContent.render()}</div>
|
<div className={className}>{currentTabContent.render()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -186,20 +186,20 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<div role="radiogroup">
|
<div role="radiogroup">
|
||||||
<input
|
<input
|
||||||
id="Autoscale-input"
|
id="Autoscale-input"
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Autoscale database throughput"
|
aria-label="Autoscale database throughput"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
|
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
|
||||||
Autoscale
|
Autoscale
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
id="Manual-input"
|
id="Manual-input"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
.treeComponent {
|
.treeComponent {
|
||||||
.nodeItem {
|
.nodeItem {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 1px dashed @AccentMedium;
|
outline: 2px @AccentMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.treeNodeHeader {
|
.treeNodeHeader {
|
||||||
|
@ -577,7 +577,7 @@ export default class Explorer {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
databasesToLoad.map(async (database: ViewModels.Database) => {
|
databasesToLoad.map(async (database: ViewModels.Database) => {
|
||||||
await database.loadCollections();
|
await database.loadCollections(true);
|
||||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
||||||
if (isNewDatabase) {
|
if (isNewDatabase) {
|
||||||
database.expandDatabase();
|
database.expandDatabase();
|
||||||
|
@ -18,13 +18,12 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
|
|||||||
<div className="graphTitle">
|
<div className="graphTitle">
|
||||||
<span className="paneTitle">Graph</span>
|
<span className="paneTitle">Graph</span>
|
||||||
<button
|
<button
|
||||||
style={{border:'none',background:'none'}}
|
style={{ border: "none", background: "none" }}
|
||||||
className="graphExpandCollapseBtn pull-right"
|
className="graphExpandCollapseBtn pull-right"
|
||||||
onClick={this.props.toggleExpandGraph}
|
onClick={this.props.toggleExpandGraph}
|
||||||
role="button"
|
role="button"
|
||||||
aria-expanded={this.props.isTabsContentExpanded}
|
aria-expanded={this.props.isTabsContentExpanded}
|
||||||
aria-name="View graph in full screen"
|
aria-name="View graph in full screen"
|
||||||
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
||||||
|
@ -202,6 +202,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
if (showOpenFullScreen) {
|
if (showOpenFullScreen) {
|
||||||
const label = "Open Full Screen";
|
const label = "Open Full Screen";
|
||||||
const fullScreenButton: CommandButtonComponentProps = {
|
const fullScreenButton: CommandButtonComponentProps = {
|
||||||
|
id: "openFullScreenBtn",
|
||||||
iconSrc: OpenInTabIcon,
|
iconSrc: OpenInTabIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
|
@ -150,7 +150,6 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
selectedKey={this.state.selectedFilter}
|
selectedKey={this.state.selectedFilter}
|
||||||
options={NotificationConsoleComponent.FilterOptions}
|
options={NotificationConsoleComponent.FilterOptions}
|
||||||
onChange={this.onFilterSelected.bind(this)}
|
onChange={this.onFilterSelected.bind(this)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<span className="consoleSplitter" />
|
<span className="consoleSplitter" />
|
||||||
<span
|
<span
|
||||||
|
@ -134,7 +134,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
role="combobox"
|
|
||||||
selectedKey="All"
|
selectedKey="All"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@ -298,7 +297,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
role="combobox"
|
|
||||||
selectedKey="All"
|
selectedKey="All"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import * as InMemoryContentProviderUtils from "./ContentProviders/InMemoryContentProviderUtils";
|
||||||
|
|
||||||
|
describe("fromContentUri", () => {
|
||||||
|
it("fromContentUri should return valid result", () => {
|
||||||
|
const contentUri = "memory://resource/path";
|
||||||
|
const result = "resource";
|
||||||
|
|
||||||
|
expect(InMemoryContentProviderUtils.fromContentUri(contentUri)).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fromContentUri should return undefined on invalid input", () => {
|
||||||
|
const contentUri = "invalid";
|
||||||
|
|
||||||
|
expect(InMemoryContentProviderUtils.fromContentUri(contentUri)).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toContentUri should return valid result", () => {
|
||||||
|
const path = "resource/path";
|
||||||
|
const result = "memory://resource/path";
|
||||||
|
|
||||||
|
expect(InMemoryContentProviderUtils.toContentUri(path)).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
@ -279,33 +279,33 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<div role='radiogroup'>
|
<div role="radiogroup">
|
||||||
<input
|
<input
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
checked={this.state.createNewDatabase}
|
checked={this.state.createNewDatabase}
|
||||||
aria-label="Create new database"
|
aria-label="Create new database"
|
||||||
aria-checked={this.state.createNewDatabase}
|
aria-checked={this.state.createNewDatabase}
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
id="databaseCreateNew"
|
id="databaseCreateNew"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
<span className="panelRadioBtnLabel">Create new</span>
|
<span className="panelRadioBtnLabel">Create new</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
checked={!this.state.createNewDatabase}
|
checked={!this.state.createNewDatabase}
|
||||||
aria-label="Use existing database"
|
aria-label="Use existing database"
|
||||||
aria-checked={!this.state.createNewDatabase}
|
aria-checked={!this.state.createNewDatabase}
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
<span className="panelRadioBtnLabel">Use existing</span>
|
<span className="panelRadioBtnLabel">Use existing</span>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@ -408,6 +408,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
role="button"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@ -811,35 +812,35 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<div role="radiogroup">
|
<div role="radiogroup">
|
||||||
<input
|
<input
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
checked={this.state.enableAnalyticalStore}
|
checked={this.state.enableAnalyticalStore}
|
||||||
disabled={!this.isSynapseLinkEnabled()}
|
disabled={!this.isSynapseLinkEnabled()}
|
||||||
aria-label="Enable analytical store"
|
aria-label="Enable analytical store"
|
||||||
aria-checked={this.state.enableAnalyticalStore}
|
aria-checked={this.state.enableAnalyticalStore}
|
||||||
name="analyticalStore"
|
name="analyticalStore"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
id="enableAnalyticalStoreBtn"
|
id="enableAnalyticalStoreBtn"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
<span className="panelRadioBtnLabel">On</span>
|
<span className="panelRadioBtnLabel">On</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
checked={!this.state.enableAnalyticalStore}
|
checked={!this.state.enableAnalyticalStore}
|
||||||
disabled={!this.isSynapseLinkEnabled()}
|
disabled={!this.isSynapseLinkEnabled()}
|
||||||
aria-label="Disable analytical store"
|
aria-label="Disable analytical store"
|
||||||
aria-checked={!this.state.enableAnalyticalStore}
|
aria-checked={!this.state.enableAnalyticalStore}
|
||||||
name="analyticalStore"
|
name="analyticalStore"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
id="disableAnalyticalStoreBtn"
|
id="disableAnalyticalStoreBtn"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
<span className="panelRadioBtnLabel">Off</span>
|
<span className="panelRadioBtnLabel">Off</span>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
@ -21,6 +21,11 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
|
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
|
||||||
LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0
|
LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0
|
||||||
);
|
);
|
||||||
|
const [containerPaginationEnabled, setContainerPaginationEnabled] = useState<boolean>(
|
||||||
|
LocalStorageUtility.hasItem(StorageKey.ContainerPaginationEnabled)
|
||||||
|
? LocalStorageUtility.getEntryString(StorageKey.ContainerPaginationEnabled) === "true"
|
||||||
|
: false
|
||||||
|
);
|
||||||
const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState<boolean>(
|
const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState<boolean>(
|
||||||
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled)
|
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled)
|
||||||
? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
||||||
@ -50,6 +55,7 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage
|
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage
|
||||||
);
|
);
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||||
|
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
|
||||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||||
|
|
||||||
@ -185,6 +191,25 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="settingsSection">
|
||||||
|
<div className="settingsSectionPart">
|
||||||
|
<div className="settingsSectionLabel">
|
||||||
|
Enable container pagination
|
||||||
|
<InfoTooltip>
|
||||||
|
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
styles={{
|
||||||
|
label: { padding: 0 },
|
||||||
|
}}
|
||||||
|
className="padding"
|
||||||
|
ariaLabel="Enable container pagination"
|
||||||
|
checked={containerPaginationEnabled}
|
||||||
|
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{shouldShowCrossPartitionOption && (
|
{shouldShowCrossPartitionOption && (
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
|
@ -97,6 +97,35 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="settingsSection"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionPart"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionLabel"
|
||||||
|
>
|
||||||
|
Enable container pagination
|
||||||
|
<InfoTooltip>
|
||||||
|
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
ariaLabel="Enable container pagination"
|
||||||
|
checked={false}
|
||||||
|
className="padding"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"label": Object {
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
@ -182,6 +211,35 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSection"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionPart"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settingsSectionLabel"
|
||||||
|
>
|
||||||
|
Enable container pagination
|
||||||
|
<InfoTooltip>
|
||||||
|
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
ariaLabel="Enable container pagination"
|
||||||
|
checked={false}
|
||||||
|
className="padding"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"label": Object {
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -18,6 +18,11 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totalSteps = 9;
|
||||||
|
if (userContext.isTryCosmosDBSubscription) {
|
||||||
|
totalSteps = 10;
|
||||||
|
}
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 1:
|
case 1:
|
||||||
return isSampleDBExpanded ? (
|
return isSampleDBExpanded ? (
|
||||||
@ -33,7 +38,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 1 of 8"
|
footerContent={"Step 1 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Start viewing and working with your data by opening Documents under Data
|
Start viewing and working with your data by opening Documents under Data
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -55,7 +60,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(1),
|
onClick: () => setStep(1),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 2 of 8"
|
footerContent={"Step 2 of " + totalSteps}
|
||||||
>
|
>
|
||||||
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
|
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
|
||||||
so.
|
so.
|
||||||
@ -78,7 +83,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(2),
|
onClick: () => setStep(2),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 3 of 8"
|
footerContent={"Step 3 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
|
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
|
||||||
drivers to do so.
|
drivers to do so.
|
||||||
@ -99,7 +104,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(3),
|
onClick: () => setStep(3),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 4 of 8"
|
footerContent={"Step 4 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Query your data using the filter function. Azure Cosmos DB for MongoDB provides comprehensive support for
|
Query your data using the filter function. Azure Cosmos DB for MongoDB provides comprehensive support for
|
||||||
MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so.
|
MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so.
|
||||||
@ -120,7 +125,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(4),
|
onClick: () => setStep(4),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 5 of 8"
|
footerContent={"Step 5 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Change throughput provisioned to your collection according to your needs
|
Change throughput provisioned to your collection according to your needs
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -140,7 +145,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(5),
|
onClick: () => setStep(5),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 6 of 8"
|
footerContent={"Step 6 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Use the indexing policy editor to create and edit your indexes.
|
Use the indexing policy editor to create and edit your indexes.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -160,12 +165,54 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(6),
|
onClick: () => setStep(6),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 7 of 8"
|
footerContent={"Step 7 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Visualize your data, store queries in an interactive document
|
Visualize your data, store queries in an interactive document
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
);
|
);
|
||||||
case 8:
|
case 8:
|
||||||
|
return (
|
||||||
|
<TeachingBubble
|
||||||
|
headline="Launch full screen"
|
||||||
|
target={"#openFullScreenBtn"}
|
||||||
|
hasCloseButton
|
||||||
|
primaryButtonProps={{
|
||||||
|
text: "Next",
|
||||||
|
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(10)),
|
||||||
|
}}
|
||||||
|
secondaryButtonProps={{
|
||||||
|
text: "Previous",
|
||||||
|
onClick: () => setStep(7),
|
||||||
|
}}
|
||||||
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
|
footerContent={"Step 8 of " + totalSteps}
|
||||||
|
>
|
||||||
|
This will open a new tab in your browser to use Cosmos DB Explorer. Using the provided URLs you can share
|
||||||
|
read-write or read-only access with other people.
|
||||||
|
</TeachingBubble>
|
||||||
|
);
|
||||||
|
case 9:
|
||||||
|
return (
|
||||||
|
<TeachingBubble
|
||||||
|
headline="Boost your experience"
|
||||||
|
target={"#freeTierTeachingBubble"}
|
||||||
|
hasCloseButton
|
||||||
|
primaryButtonProps={{
|
||||||
|
text: "Next",
|
||||||
|
onClick: () => setStep(10),
|
||||||
|
}}
|
||||||
|
secondaryButtonProps={{
|
||||||
|
text: "Previous",
|
||||||
|
onClick: () => setStep(8),
|
||||||
|
}}
|
||||||
|
calloutProps={{ directionalHint: DirectionalHint.leftCenter }}
|
||||||
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
|
footerContent={"Step 9 of " + totalSteps}
|
||||||
|
>
|
||||||
|
Unlock everything Azure Cosmos DB has to offer When you're ready, upgrade to production.
|
||||||
|
</TeachingBubble>
|
||||||
|
);
|
||||||
|
case 10:
|
||||||
return (
|
return (
|
||||||
<TeachingBubble
|
<TeachingBubble
|
||||||
headline="Congratulations!"
|
headline="Congratulations!"
|
||||||
@ -180,10 +227,10 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
secondaryButtonProps={{
|
secondaryButtonProps={{
|
||||||
text: "Previous",
|
text: "Previous",
|
||||||
onClick: () => setStep(7),
|
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 8 of 8"
|
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text style={{ color: "white" }}>
|
<Text style={{ color: "white" }}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -17,6 +17,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
if (userContext.apiType !== "SQL") {
|
if (userContext.apiType !== "SQL") {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
let totalSteps = 8;
|
||||||
|
if (userContext.isTryCosmosDBSubscription) {
|
||||||
|
totalSteps = 9;
|
||||||
|
}
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 1:
|
case 1:
|
||||||
@ -33,7 +37,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 1 of 7"
|
footerContent={"Step 1 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Start viewing and working with your data by opening Items under Data
|
Start viewing and working with your data by opening Items under Data
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -55,7 +59,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(1),
|
onClick: () => setStep(1),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 2 of 7"
|
footerContent={"Step 2 of " + totalSteps}
|
||||||
>
|
>
|
||||||
View item here using the items window. Additionally you can also filter items to be reviewed with the filter
|
View item here using the items window. Additionally you can also filter items to be reviewed with the filter
|
||||||
function
|
function
|
||||||
@ -78,7 +82,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(2),
|
onClick: () => setStep(2),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 3 of 7"
|
footerContent={"Step 3 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Add new item by copy / pasting JSON; or uploading a JSON
|
Add new item by copy / pasting JSON; or uploading a JSON
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -98,7 +102,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(3),
|
onClick: () => setStep(3),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 4 of 7"
|
footerContent={"Step 4 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Query your data using either the filter function or new query.
|
Query your data using either the filter function or new query.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -118,7 +122,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(4),
|
onClick: () => setStep(4),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 5 of 7"
|
footerContent={"Step 5 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Change throughput provisioned to your container according to your needs
|
Change throughput provisioned to your container according to your needs
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
@ -138,12 +142,54 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
onClick: () => setStep(5),
|
onClick: () => setStep(5),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 6 of 7"
|
footerContent={"Step 6 of " + totalSteps}
|
||||||
>
|
>
|
||||||
Visualize your data, store queries in an interactive document
|
Visualize your data, store queries in an interactive document
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
);
|
);
|
||||||
case 7:
|
case 7:
|
||||||
|
return (
|
||||||
|
<TeachingBubble
|
||||||
|
headline="Launch full screen"
|
||||||
|
target={"#openFullScreenBtn"}
|
||||||
|
hasCloseButton
|
||||||
|
primaryButtonProps={{
|
||||||
|
text: "Next",
|
||||||
|
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(9)),
|
||||||
|
}}
|
||||||
|
secondaryButtonProps={{
|
||||||
|
text: "Previous",
|
||||||
|
onClick: () => setStep(6),
|
||||||
|
}}
|
||||||
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
|
footerContent={"Step 7 of " + totalSteps}
|
||||||
|
>
|
||||||
|
This will open a new tab in your browser to use Cosmos DB Explorer. Using the provided URLs you can share
|
||||||
|
read-write or read-only access with other people.
|
||||||
|
</TeachingBubble>
|
||||||
|
);
|
||||||
|
case 8:
|
||||||
|
return (
|
||||||
|
<TeachingBubble
|
||||||
|
headline="Boost your experience"
|
||||||
|
target={"#freeTierTeachingBubble"}
|
||||||
|
hasCloseButton
|
||||||
|
primaryButtonProps={{
|
||||||
|
text: "Next",
|
||||||
|
onClick: () => setStep(9),
|
||||||
|
}}
|
||||||
|
secondaryButtonProps={{
|
||||||
|
text: "Previous",
|
||||||
|
onClick: () => setStep(7),
|
||||||
|
}}
|
||||||
|
calloutProps={{ directionalHint: DirectionalHint.leftCenter }}
|
||||||
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
|
footerContent={"Step 8 of " + totalSteps}
|
||||||
|
>
|
||||||
|
Unlock everything Azure Cosmos DB has to offer When you're ready, upgrade to production.
|
||||||
|
</TeachingBubble>
|
||||||
|
);
|
||||||
|
case 9:
|
||||||
return (
|
return (
|
||||||
<TeachingBubble
|
<TeachingBubble
|
||||||
headline="Congratulations!"
|
headline="Congratulations!"
|
||||||
@ -158,10 +204,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
secondaryButtonProps={{
|
secondaryButtonProps={{
|
||||||
text: "Previous",
|
text: "Previous",
|
||||||
onClick: () => setStep(6),
|
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)),
|
||||||
}}
|
}}
|
||||||
onDismiss={() => onDimissTeachingBubble()}
|
onDismiss={() => onDimissTeachingBubble()}
|
||||||
footerContent="Step 7 of 7"
|
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text style={{ color: "white" }}>
|
<Text style={{ color: "white" }}>
|
||||||
|
@ -116,9 +116,14 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
<form className="connectExplorerFormContainer">
|
<form className="connectExplorerFormContainer">
|
||||||
<div className="splashScreenContainer">
|
<div className="splashScreenContainer">
|
||||||
<div className="splashScreen">
|
<div className="splashScreen">
|
||||||
<div className="title" aria-label={userContext.apiType === "Postgres"
|
<div
|
||||||
? "Welcome to Azure Cosmos DB for PostgreSQL"
|
className="title"
|
||||||
: "Welcome to Azure Cosmos DB"}>
|
aria-label={
|
||||||
|
userContext.apiType === "Postgres"
|
||||||
|
? "Welcome to Azure Cosmos DB for PostgreSQL"
|
||||||
|
: "Welcome to Azure Cosmos DB"
|
||||||
|
}
|
||||||
|
>
|
||||||
{userContext.apiType === "Postgres"
|
{userContext.apiType === "Postgres"
|
||||||
? "Welcome to Azure Cosmos DB for PostgreSQL"
|
? "Welcome to Azure Cosmos DB for PostgreSQL"
|
||||||
: "Welcome to Azure Cosmos DB"}
|
: "Welcome to Azure Cosmos DB"}
|
||||||
|
@ -92,6 +92,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={active ? "active tabList" : "tabList"}
|
className={active ? "active tabList" : "tabList"}
|
||||||
|
style={active ? { fontWeight: "bolder" } : {}}
|
||||||
title={useObservable(tab?.tabPath || ko.observable(""))}
|
title={useObservable(tab?.tabPath || ko.observable(""))}
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
aria-expanded={active}
|
aria-expanded={active}
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { readCollections } from "../../Common/dataAccess/readCollections";
|
import { readCollections, readCollectionsWithPagination } from "../../Common/dataAccess/readCollections";
|
||||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
@ -13,6 +13,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
|||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
||||||
|
import * as StorageUtility from "../../Shared/StorageUtility";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@ -38,6 +39,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||||
public junoClient: JunoClient;
|
public junoClient: JunoClient;
|
||||||
public isSampleDB: boolean;
|
public isSampleDB: boolean;
|
||||||
|
public collectionsContinuationToken?: string;
|
||||||
private isOfferRead: boolean;
|
private isOfferRead: boolean;
|
||||||
|
|
||||||
constructor(container: Explorer, data: DataModels.Database) {
|
constructor(container: Explorer, data: DataModels.Database) {
|
||||||
@ -140,7 +142,11 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.loadOffer();
|
await this.loadOffer();
|
||||||
await this.loadCollections();
|
|
||||||
|
if (this.collections()?.length === 0) {
|
||||||
|
await this.loadCollections(true);
|
||||||
|
}
|
||||||
|
|
||||||
this.isDatabaseExpanded(true);
|
this.isDatabaseExpanded(true);
|
||||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||||
description: "Database node",
|
description: "Database node",
|
||||||
@ -162,9 +168,31 @@ export default class Database implements ViewModels.Database {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadCollections(): Promise<void> {
|
public async loadCollections(restart = false) {
|
||||||
const collectionVMs: Collection[] = [];
|
const collectionVMs: Collection[] = [];
|
||||||
const collections: DataModels.Collection[] = await readCollections(this.id());
|
let collections: DataModels.Collection[] = [];
|
||||||
|
if (restart) {
|
||||||
|
this.collectionsContinuationToken = undefined;
|
||||||
|
}
|
||||||
|
const containerPaginationEnabled =
|
||||||
|
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.ContainerPaginationEnabled) ===
|
||||||
|
"true";
|
||||||
|
if (containerPaginationEnabled) {
|
||||||
|
const collectionsWithPagination: DataModels.CollectionsWithPagination = await readCollectionsWithPagination(
|
||||||
|
this.id(),
|
||||||
|
this.collectionsContinuationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
if (collectionsWithPagination.collections?.length === Constants.Queries.containersPerPage) {
|
||||||
|
this.collectionsContinuationToken = collectionsWithPagination.continuationToken;
|
||||||
|
} else {
|
||||||
|
this.collectionsContinuationToken = undefined;
|
||||||
|
}
|
||||||
|
collections = collectionsWithPagination.collections;
|
||||||
|
} else {
|
||||||
|
collections = await readCollections(this.id());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Remove
|
// TODO Remove
|
||||||
// This is a hack to make Mongo collections read via ARM have a SQL-ish partitionKey property
|
// This is a hack to make Mongo collections read via ARM have a SQL-ish partitionKey property
|
||||||
if (userContext.apiType === "Mongo" && userContext.authType === AuthType.AAD) {
|
if (userContext.apiType === "Mongo" && userContext.authType === AuthType.AAD) {
|
||||||
@ -199,7 +227,9 @@ export default class Database implements ViewModels.Database {
|
|||||||
|
|
||||||
//merge collections
|
//merge collections
|
||||||
this.addCollectionsToList(collectionVMs);
|
this.addCollectionsToList(collectionVMs);
|
||||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
if (!containerPaginationEnabled || restart) {
|
||||||
|
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||||
|
}
|
||||||
|
|
||||||
useDatabases.getState().updateDatabase(this);
|
useDatabases.getState().updateDatabase(this);
|
||||||
}
|
}
|
||||||
|
@ -479,6 +479,18 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
databaseNode.children.push(buildCollectionNode(database, collection))
|
databaseNode.children.push(buildCollectionNode(database, collection))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (database.collectionsContinuationToken) {
|
||||||
|
const loadMoreNode: TreeNode = {
|
||||||
|
label: "load more",
|
||||||
|
className: "loadMoreHeader",
|
||||||
|
onClick: async () => {
|
||||||
|
await database.loadCollections();
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
databaseNode.children.push(loadMoreNode);
|
||||||
|
}
|
||||||
|
|
||||||
database.collections.subscribe((collections: ViewModels.Collection[]) => {
|
database.collections.subscribe((collections: ViewModels.Collection[]) => {
|
||||||
collections.forEach((collection: ViewModels.Collection) =>
|
collections.forEach((collection: ViewModels.Collection) =>
|
||||||
databaseNode.children.push(buildCollectionNode(database, collection))
|
databaseNode.children.push(buildCollectionNode(database, collection))
|
||||||
|
@ -80,6 +80,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flexContainer">
|
<div className="flexContainer">
|
||||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||||
|
<div id="freeTierTeachingBubble"> </div>
|
||||||
{/* Main Command Bar - Start */}
|
{/* Main Command Bar - Start */}
|
||||||
<CommandBar container={explorer} />
|
<CommandBar container={explorer} />
|
||||||
{/* Collections Tree and Tabs - Begin */}
|
{/* Collections Tree and Tabs - Begin */}
|
||||||
|
@ -4,6 +4,7 @@ import * as SessionStorageUtility from "./SessionStorageUtility";
|
|||||||
export { LocalStorageUtility, SessionStorageUtility };
|
export { LocalStorageUtility, SessionStorageUtility };
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
ActualItemPerPage,
|
ActualItemPerPage,
|
||||||
|
ContainerPaginationEnabled,
|
||||||
CustomItemPerPage,
|
CustomItemPerPage,
|
||||||
DatabaseAccountId,
|
DatabaseAccountId,
|
||||||
EncryptedKeyToken,
|
EncryptedKeyToken,
|
||||||
|
@ -49,7 +49,7 @@ export function getMsalInstance() {
|
|||||||
cacheLocation: "localStorage",
|
cacheLocation: "localStorage",
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
authority: `${configContext.AAD_ENDPOINT}common`,
|
authority: `${configContext.AAD_ENDPOINT}organizations`,
|
||||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user