diff --git a/less/documentDB.less b/less/documentDB.less index bc36d1483..1395c9482 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2576,6 +2576,10 @@ a:link { .querydropdown.placeholderVisible { font-style: italic; } +.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: #767474; + opacity: 1; +} .querydropdown:hover { background-color: @AccentLow; @@ -3087,3 +3091,4 @@ a:link { background: white; height: 100%; } + diff --git a/less/resourceTree.less b/less/resourceTree.less index 31410ab32..da7a5c90d 100644 --- a/less/resourceTree.less +++ b/less/resourceTree.less @@ -5,12 +5,17 @@ overflow: auto; .databaseHeader { + padding: 1px; font-size: 14px; } .collectionHeader { font-size: 12px; } + + .loadMoreHeader { + color: RGB(5, 99, 193); + } } .notebookResourceTree { @@ -23,6 +28,4 @@ .clickDisabled { pointer-events: none; } -} - - +} \ No newline at end of file diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index c9a458558..3feba8cc2 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -141,7 +141,7 @@ export class Queries { public static UnlimitedPageOption: string = "unlimited"; 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 containersPerPage: number = 50; public static QueryEditorMinHeightRatio: number = 0.1; public static QueryEditorMaxHeightRatio: number = 0.4; public static readonly DefaultMaxDegreeOfParallelism = 6; diff --git a/src/Common/EnvironmentUtility.test.ts b/src/Common/EnvironmentUtility.test.ts new file mode 100644 index 000000000..6b5b1e218 --- /dev/null +++ b/src/Common/EnvironmentUtility.test.ts @@ -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); + }); +}); diff --git a/src/Common/OfferUtility.test.ts b/src/Common/OfferUtility.test.ts index ce86c0d8a..d854f74d0 100644 --- a/src/Common/OfferUtility.test.ts +++ b/src/Common/OfferUtility.test.ts @@ -1,6 +1,6 @@ -import * as OfferUtility from "./OfferUtility"; -import { SDKOfferDefinition, Offer } from "../Contracts/DataModels"; import { OfferResponse } from "@azure/cosmos"; +import { Offer, SDKOfferDefinition } from "../Contracts/DataModels"; +import * as OfferUtility from "./OfferUtility"; describe("parseSDKOfferResponse", () => { it("manual throughput", () => { @@ -31,6 +31,26 @@ describe("parseSDKOfferResponse", () => { 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", () => { const mockOfferDefinition = { content: { diff --git a/src/Common/TableEntity.tsx b/src/Common/TableEntity.tsx index b3711fcbc..aa98119c5 100644 --- a/src/Common/TableEntity.tsx +++ b/src/Common/TableEntity.tsx @@ -139,14 +139,15 @@ export const TableEntity: FunctionComponent = ({
- editEntity + editEntity
diff --git a/src/Common/UrlUtility.test.ts b/src/Common/UrlUtility.test.ts new file mode 100644 index 000000000..8170cc63f --- /dev/null +++ b/src/Common/UrlUtility.test.ts @@ -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"); + }); +}); diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts index a5c8dd928..4a528b66c 100644 --- a/src/Common/dataAccess/readCollections.ts +++ b/src/Common/dataAccess/readCollections.ts @@ -1,3 +1,4 @@ +import { Queries } from "Common/Constants"; import { AuthType } from "../../AuthType"; import * as DataModels from "../../Contracts/DataModels"; import { userContext } from "../../UserContext"; @@ -31,6 +32,35 @@ export async function readCollections(databaseId: string): Promise { + 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 { let rpResponse; diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index c913310a3..e4f865c2c 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -156,6 +156,11 @@ export interface Collection extends Resource { requestSchema?: () => void; } +export interface CollectionsWithPagination { + continuationToken?: string; + collections?: Collection[]; +} + export interface Database extends Resource { collections?: Collection[]; } diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 77ccc1cfe..c9bb351bc 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -87,13 +87,13 @@ export interface Database extends TreeNode { isDatabaseExpanded: ko.Observable; isDatabaseShared: ko.Computed; isSampleDB?: boolean; - + collectionsContinuationToken?: string; selectedSubnodeKind: ko.Observable; expandDatabase(): Promise; collapseDatabase(): void; - loadCollections(): Promise; + loadCollections(restart?: boolean): Promise; findCollectionWithId(collectionId: string): Collection; openAddCollection(database: Database, event: MouseEvent): void; onSettingsClick: () => void; diff --git a/src/Explorer/Controls/Tabs/TabComponent.tsx b/src/Explorer/Controls/Tabs/TabComponent.tsx index 7936321d0..9cfcf8ae5 100644 --- a/src/Explorer/Controls/Tabs/TabComponent.tsx +++ b/src/Explorer/Controls/Tabs/TabComponent.tsx @@ -46,10 +46,13 @@ export class TabComponent extends React.Component { } let className = "toggleSwitch"; + let ariaselected; if (index === this.props.currentTabIndex) { className += " selectedToggle"; + ariaselected = true; } else { className += " unselectedToggle"; + ariaselected = false; } return ( @@ -57,9 +60,10 @@ export class TabComponent extends React.Component { this.setActiveTab(index)} aria-label={`Select tab: ${tab.title}`} + aria-selected={ariaselected} > {tab.title} @@ -77,7 +81,11 @@ export class TabComponent extends React.Component { return (
- {!this.props.hideHeader &&
{this.renderTabTitles()}
} + {!this.props.hideHeader && ( +
+ {this.renderTabTitles()} +
+ )}
{currentTabContent.render()}
); diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 8b73d9c36..b822fade5 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -186,20 +186,20 @@ export const ThroughputInput: FunctionComponent = ({
- handleOnChangeMode(e, "Autoscale")} - /> - + handleOnChangeMode(e, "Autoscale")} + /> + { - await database.loadCollections(); + await database.loadCollections(true); const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id()); if (isNewDatabase) { database.expandDatabase(); diff --git a/src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx b/src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx index 776ad350e..063a70e56 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx +++ b/src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx @@ -18,13 +18,12 @@ export class MiddlePaneComponent extends React.Component Graph
)} +
+
+
+ Enable container pagination + + Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order. + +
+ setContainerPaginationEnabled(!containerPaginationEnabled)} + /> +
+
{shouldShowCrossPartitionOption && (
diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 928e7f7a7..ffa847681 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -97,6 +97,35 @@ exports[`Settings Pane should render Default properly 1`] = `
+
+
+
+ Enable container pagination + + Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order. + +
+ +
+
@@ -182,6 +211,35 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
+
+
+
+ Enable container pagination + + Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order. + +
+ +
+
diff --git a/src/Explorer/Quickstart/Tutorials/MongoQuickstartTutorial.tsx b/src/Explorer/Quickstart/Tutorials/MongoQuickstartTutorial.tsx index d6655e14b..aa9c5e300 100644 --- a/src/Explorer/Quickstart/Tutorials/MongoQuickstartTutorial.tsx +++ b/src/Explorer/Quickstart/Tutorials/MongoQuickstartTutorial.tsx @@ -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 { useTeachingBubble } from "hooks/useTeachingBubble"; import React from "react"; @@ -18,6 +18,11 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { return <>; } + let totalSteps = 9; + if (userContext.isTryCosmosDBSubscription) { + totalSteps = 10; + } + switch (step) { case 1: return isSampleDBExpanded ? ( @@ -33,7 +38,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { }, }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 1 of 8" + footerContent={"Step 1 of " + totalSteps} > Start viewing and working with your data by opening Documents under Data @@ -55,7 +60,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(1), }} 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 so. @@ -78,7 +83,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(2), }} 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 drivers to do so. @@ -99,7 +104,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(3), }} 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 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), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 5 of 8" + footerContent={"Step 5 of " + totalSteps} > Change throughput provisioned to your collection according to your needs @@ -140,7 +145,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(5), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 6 of 8" + footerContent={"Step 6 of " + totalSteps} > Use the indexing policy editor to create and edit your indexes. @@ -160,12 +165,54 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(6), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 7 of 8" + footerContent={"Step 7 of " + totalSteps} > Visualize your data, store queries in an interactive document ); case 8: + return ( + (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. + + ); + case 9: + return ( + 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. + + ); + case 10: return ( { }} secondaryButtonProps={{ text: "Previous", - onClick: () => setStep(7), + onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 8 of 8" + footerContent={"Step " + totalSteps + " of " + totalSteps} > diff --git a/src/Explorer/Quickstart/Tutorials/SQLQuickstartTutorial.tsx b/src/Explorer/Quickstart/Tutorials/SQLQuickstartTutorial.tsx index cced06a50..ddb35aa5e 100644 --- a/src/Explorer/Quickstart/Tutorials/SQLQuickstartTutorial.tsx +++ b/src/Explorer/Quickstart/Tutorials/SQLQuickstartTutorial.tsx @@ -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 { useTeachingBubble } from "hooks/useTeachingBubble"; import React from "react"; @@ -17,6 +17,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { if (userContext.apiType !== "SQL") { return <>; } + let totalSteps = 8; + if (userContext.isTryCosmosDBSubscription) { + totalSteps = 9; + } switch (step) { case 1: @@ -33,7 +37,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { }, }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 1 of 7" + footerContent={"Step 1 of " + totalSteps} > Start viewing and working with your data by opening Items under Data @@ -55,7 +59,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(1), }} 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 function @@ -78,7 +82,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(2), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 3 of 7" + footerContent={"Step 3 of " + totalSteps} > Add new item by copy / pasting JSON; or uploading a JSON @@ -98,7 +102,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(3), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 4 of 7" + footerContent={"Step 4 of " + totalSteps} > Query your data using either the filter function or new query. @@ -118,7 +122,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(4), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 5 of 7" + footerContent={"Step 5 of " + totalSteps} > Change throughput provisioned to your container according to your needs @@ -138,12 +142,54 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => { onClick: () => setStep(5), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 6 of 7" + footerContent={"Step 6 of " + totalSteps} > Visualize your data, store queries in an interactive document ); case 7: + return ( + (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. + + ); + case 8: + return ( + 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. + + ); + case 9: return ( { }} secondaryButtonProps={{ text: "Previous", - onClick: () => setStep(6), + onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)), }} onDismiss={() => onDimissTeachingBubble()} - footerContent="Step 7 of 7" + footerContent={"Step " + totalSteps + " of " + totalSteps} > diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index c8aac8830..e714a8952 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -116,9 +116,14 @@ export class SplashScreen extends React.Component {
-
+
{userContext.apiType === "Postgres" ? "Welcome to Azure Cosmos DB for PostgreSQL" : "Welcome to Azure Cosmos DB"} diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index b289c658b..bcd867756 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -92,6 +92,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind? } }} className={active ? "active tabList" : "tabList"} + style={active ? { fontWeight: "bolder" } : {}} title={useObservable(tab?.tabPath || ko.observable(""))} aria-selected={active} aria-expanded={active} diff --git a/src/Explorer/Tree/Database.tsx b/src/Explorer/Tree/Database.tsx index a31b79b97..19cf82e31 100644 --- a/src/Explorer/Tree/Database.tsx +++ b/src/Explorer/Tree/Database.tsx @@ -3,7 +3,7 @@ import React from "react"; import * as _ from "underscore"; import { AuthType } from "../../AuthType"; 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 { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; import * as Logger from "../../Common/Logger"; @@ -13,6 +13,7 @@ import * as ViewModels from "../../Contracts/ViewModels"; import { useSidePanel } from "../../hooks/useSidePanel"; import { useTabs } from "../../hooks/useTabs"; import { IJunoResponse, JunoClient } from "../../Juno/JunoClient"; +import * as StorageUtility from "../../Shared/StorageUtility"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; @@ -38,6 +39,7 @@ export default class Database implements ViewModels.Database { public selectedSubnodeKind: ko.Observable; public junoClient: JunoClient; public isSampleDB: boolean; + public collectionsContinuationToken?: string; private isOfferRead: boolean; constructor(container: Explorer, data: DataModels.Database) { @@ -140,7 +142,11 @@ export default class Database implements ViewModels.Database { } await this.loadOffer(); - await this.loadCollections(); + + if (this.collections()?.length === 0) { + await this.loadCollections(true); + } + this.isDatabaseExpanded(true); TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, { description: "Database node", @@ -162,9 +168,31 @@ export default class Database implements ViewModels.Database { }); } - public async loadCollections(): Promise { + public async loadCollections(restart = false) { 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 // 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) { @@ -199,7 +227,9 @@ export default class Database implements ViewModels.Database { //merge collections this.addCollectionsToList(collectionVMs); - this.deleteCollectionsFromList(deltaCollections.toDelete); + if (!containerPaginationEnabled || restart) { + this.deleteCollectionsFromList(deltaCollections.toDelete); + } useDatabases.getState().updateDatabase(this); } diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index 24eb34579..afb5a118e 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -479,6 +479,18 @@ export const ResourceTree: React.FC = ({ container }: Resourc 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[]) => { collections.forEach((collection: ViewModels.Collection) => databaseNode.children.push(buildCollectionNode(database, collection)) diff --git a/src/Main.tsx b/src/Main.tsx index 2115406ce..d8d817847 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -80,6 +80,7 @@ const App: React.FunctionComponent = () => { return (
+
{/* Main Command Bar - Start */} {/* Collections Tree and Tabs - Begin */} diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts index 0d8477016..ad8a932e1 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -4,6 +4,7 @@ import * as SessionStorageUtility from "./SessionStorageUtility"; export { LocalStorageUtility, SessionStorageUtility }; export enum StorageKey { ActualItemPerPage, + ContainerPaginationEnabled, CustomItemPerPage, DatabaseAccountId, EncryptedKeyToken, diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts index 0da7e310f..6e99d2351 100644 --- a/src/Utils/AuthorizationUtils.ts +++ b/src/Utils/AuthorizationUtils.ts @@ -49,7 +49,7 @@ export function getMsalInstance() { cacheLocation: "localStorage", }, auth: { - authority: `${configContext.AAD_ENDPOINT}common`, + authority: `${configContext.AAD_ENDPOINT}organizations`, clientId: "203f1145-856a-4232-83d4-a43568fba23d", }, };