From 72c3605dbec1a6215be0f6eaa69d8adf879ce67f Mon Sep 17 00:00:00 2001 From: MokireddySampath <120497218+MokireddySampath@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:49:21 +0530 Subject: [PATCH 1/5] Sampath accessibility sev 2 (#1400) * autoscale and manual radiobuutton fixes * alt text attribute for images * Revert "alt text attribute for images" This reverts commit 5a660551c6289d475b6298f90abc9d149146772e. * alt text for decorative images * sev2 accessibilitydefects in data explorer * Revert "sev2 accessibilitydefects in data explorer" This reverts commit b84d5b572c6f127cd17033995c919867285d897e. * Sev2 accessibilitydefects * Revert "Sev2 accessibilitydefects" This reverts commit a4e60f106c43d0fe994fc9a0749b084ae427397e. * accessibilitydefects-data explorer * Remove extra white space --------- Co-authored-by: Victor Meng --- images/imgarrowlefticon.svg | 2 +- src/Common/TableEntity.tsx | 20 +-- .../ThroughputInput/ThroughputInput.tsx | 58 +++---- .../ThroughputInput.test.tsx.snap | 73 ++++----- .../Controls/TreeComponent/TreeComponent.tsx | 2 +- .../__snapshots__/TreeComponent.test.tsx.snap | 5 + .../GraphVizComponent.tsx | 2 + .../MiddlePaneComponent.tsx | 6 +- .../NotificationConsoleComponent.tsx | 3 - ...NotificationConsoleComponent.test.tsx.snap | 6 - src/Explorer/Panes/AddCollectionPanel.tsx | 110 +++++++------- .../AddCollectionPanel.test.tsx.snap | 142 +++++++++--------- src/Explorer/SplashScreen/SplashScreen.tsx | 9 +- 13 files changed, 230 insertions(+), 208 deletions(-) diff --git a/images/imgarrowlefticon.svg b/images/imgarrowlefticon.svg index cd886d754..716024607 100644 --- a/images/imgarrowlefticon.svg +++ b/images/imgarrowlefticon.svg @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/src/Common/TableEntity.tsx b/src/Common/TableEntity.tsx index 54c27adf5..f3ec564f5 100644 --- a/src/Common/TableEntity.tsx +++ b/src/Common/TableEntity.tsx @@ -137,15 +137,17 @@ export const TableEntity: FunctionComponent = ({ /> {!isEntityValueDisable && ( - editEntity +
+ editEntity +
)} {isDeleteOptionVisible && userContext.apiType !== "Cassandra" && ( diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index d86b0414f..926b1af5c 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -185,35 +185,37 @@ export const ThroughputInput: FunctionComponent = ({ - handleOnChangeMode(e, "Autoscale")} - /> - +
+ handleOnChangeMode(e, "Autoscale")} + /> + - handleOnChangeMode(e, "Manual")} - /> - + handleOnChangeMode(e, "Manual")} + /> + +
{isAutoscaleSelected && ( diff --git a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap index 009eab607..864124ad4 100644 --- a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap +++ b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap @@ -654,44 +654,45 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
- - - - + + + + +
{node.children && ( -
+
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
@@ -465,6 +468,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
{ {/* svg load more icon inlined as-is here: remove the style="fill:#374649;" so we can override it */} {
Graph - {this.props.isTabsContentExpanded - +
diff --git a/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx b/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx index c7a2cf108..f27470a04 100644 --- a/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx +++ b/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx @@ -147,12 +147,9 @@ export class NotificationConsoleComponent extends React.Component<
- - Create new +
+ + Create new - - Use existing + + Use existing +
{this.state.createNewDatabase && ( @@ -802,35 +804,37 @@ export class AddCollectionPanel extends React.Component - - On +
+ + On - - Off + + Off +
{!this.isSynapseLinkEnabled() && ( diff --git a/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap index 7adf8316f..000310d32 100644 --- a/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap @@ -42,39 +42,43 @@ exports[`AddCollectionPanel should render Default properly 1`] = ` horizontal={true} verticalAlign="center" > - - - Create new - - - - Use existing - + + + Create new + + + + Use existing + +
- - - On - - - - Off - + + + On + + + + Off + +
{
-
+
{userContext.apiType === "Postgres" ? "Welcome to Azure Cosmos DB for PostgreSQL" : "Welcome to Azure Cosmos DB"} From 1ee6abf89076ed8542286add3dbcfad24faa27de Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Mon, 27 Mar 2023 10:47:04 -0700 Subject: [PATCH 2/5] Change AAD endpoint from /common to /organizations. (#1408) --- src/Utils/AuthorizationUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", }, }; From 7f220bf8be09a57ce50e30a18c67a94822a7bfd3 Mon Sep 17 00:00:00 2001 From: sindhuba <122321535+sindhuba@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:33:55 -0700 Subject: [PATCH 3/5] Add additional teaching bubbles in Quickstart (#1407) * Add additional teaching bubbles in Quickstart * Run npm format * Fix lint error * Add unit tests * Add Mongo teaching bubbles for Try CosmosDB and Launch full screen * Add additional tests for UrlUtility * Run npm format * Add tests for Notebook Utils --- src/Common/EnvironmentUtility.test.ts | 14 ++++ src/Common/OfferUtility.test.ts | 24 ++++++- src/Common/UrlUtility.test.ts | 49 ++++++++++++++ .../CommandBarComponentButtonFactory.tsx | 1 + .../InMemoryContentProviderUtils.test.ts | 23 +++++++ .../Tutorials/MongoQuickstartTutorial.tsx | 67 ++++++++++++++++--- .../Tutorials/SQLQuickstartTutorial.tsx | 64 +++++++++++++++--- src/Main.tsx | 1 + 8 files changed, 222 insertions(+), 21 deletions(-) create mode 100644 src/Common/EnvironmentUtility.test.ts create mode 100644 src/Common/UrlUtility.test.ts create mode 100644 src/Explorer/Notebook/NotebookComponent/InMemoryContentProviderUtils.test.ts 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/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/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index c02639f09..938383d5b 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -202,6 +202,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt if (showOpenFullScreen) { const label = "Open Full Screen"; const fullScreenButton: CommandButtonComponentProps = { + id: "openFullScreenBtn", iconSrc: OpenInTabIcon, iconAlt: label, onCommandClick: () => { diff --git a/src/Explorer/Notebook/NotebookComponent/InMemoryContentProviderUtils.test.ts b/src/Explorer/Notebook/NotebookComponent/InMemoryContentProviderUtils.test.ts new file mode 100644 index 000000000..a5fd5df80 --- /dev/null +++ b/src/Explorer/Notebook/NotebookComponent/InMemoryContentProviderUtils.test.ts @@ -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); + }); +}); 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/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 */} From 547954c3dc2ea7c9562ee41482016b30d4f9f509 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Thu, 30 Mar 2023 17:53:36 -0400 Subject: [PATCH 4/5] Lazy loading containers (#1411) Co-authored-by: Asier Isayas --- less/resourceTree.less | 8 ++- src/Common/Constants.ts | 2 +- src/Common/dataAccess/readCollections.ts | 30 ++++++++++ src/Contracts/DataModels.ts | 5 ++ src/Contracts/ViewModels.ts | 4 +- src/Explorer/Explorer.tsx | 2 +- .../Panes/SettingsPane/SettingsPane.tsx | 25 ++++++++ .../__snapshots__/SettingsPane.test.tsx.snap | 58 +++++++++++++++++++ src/Explorer/Tree/Database.tsx | 40 +++++++++++-- src/Explorer/Tree/ResourceTree.tsx | 12 ++++ src/Shared/StorageUtility.ts | 1 + 11 files changed, 175 insertions(+), 12 deletions(-) diff --git a/less/resourceTree.less b/less/resourceTree.less index 31410ab32..68aa3d0de 100644 --- a/less/resourceTree.less +++ b/less/resourceTree.less @@ -11,6 +11,10 @@ .collectionHeader { font-size: 12px; } + + .loadMoreHeader { + color: RGB(5, 99, 193); + } } .notebookResourceTree { @@ -23,6 +27,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/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/Explorer.tsx b/src/Explorer/Explorer.tsx index f1c689d47..f8ecb6923 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -577,7 +577,7 @@ export default class Explorer { try { await Promise.all( 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()); if (isNewDatabase) { database.expandDatabase(); diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 9abc63f82..d7dced7a8 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -21,6 +21,11 @@ export const SettingsPane: FunctionComponent = () => { const [customItemPerPage, setCustomItemPerPage] = useState( LocalStorageUtility.getEntryNumber(StorageKey.CustomItemPerPage) || 0 ); + const [containerPaginationEnabled, setContainerPaginationEnabled] = useState( + LocalStorageUtility.hasItem(StorageKey.ContainerPaginationEnabled) + ? LocalStorageUtility.getEntryString(StorageKey.ContainerPaginationEnabled) === "true" + : false + ); const [crossPartitionQueryEnabled, setCrossPartitionQueryEnabled] = useState( LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) ? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true" @@ -50,6 +55,7 @@ export const SettingsPane: FunctionComponent = () => { isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage ); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); + LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); @@ -185,6 +191,25 @@ export const SettingsPane: FunctionComponent = () => {
)} +
+
+
+ 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/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/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, From c7c894d6d93f568646fc018c51359db7c8a26681 Mon Sep 17 00:00:00 2001 From: MokireddySampath <120497218+MokireddySampath@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:11:40 +0530 Subject: [PATCH 5/5] Sampath accessibility sev 2 2 (#1404) * autoscale and manual radiobuutton fixes * alt text attribute for images * Revert "alt text attribute for images" This reverts commit 5a660551c6289d475b6298f90abc9d149146772e. * alt text for decorative images * sev2 accessibilitydefects in data explorer * Revert "sev2 accessibilitydefects in data explorer" This reverts commit b84d5b572c6f127cd17033995c919867285d897e. * Sev2 accessibilitydefects * Revert "Sev2 accessibilitydefects" This reverts commit a4e60f106c43d0fe994fc9a0749b084ae427397e. * accessibilitydefects-data explorer * Accessibility sev-2 defects-2 * corrections for 2278347,2278096 and fix for 2264174 * color for placeholder changed to 767474, margin is set to accommodate height between treeheader elements * padding added for databaseheader, removed margin and restored padding to treenodeheader --- less/documentDB.less | 5 +++++ less/resourceTree.less | 1 + src/Explorer/Controls/Tabs/TabComponent.tsx | 12 ++++++++++-- .../Controls/TreeComponent/treeComponent.less | 2 +- src/Explorer/Panes/AddCollectionPanel.tsx | 1 + .../__snapshots__/AddCollectionPanel.test.tsx.snap | 1 + src/Explorer/Tabs/Tabs.tsx | 1 + 7 files changed, 20 insertions(+), 3 deletions(-) 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 68aa3d0de..da7a5c90d 100644 --- a/less/resourceTree.less +++ b/less/resourceTree.less @@ -5,6 +5,7 @@ overflow: auto; .databaseHeader { + padding: 1px; font-size: 14px; } 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/TreeComponent/treeComponent.less b/src/Explorer/Controls/TreeComponent/treeComponent.less index e13a9e40d..8be1a3c52 100644 --- a/src/Explorer/Controls/TreeComponent/treeComponent.less +++ b/src/Explorer/Controls/TreeComponent/treeComponent.less @@ -3,7 +3,7 @@ .treeComponent { .nodeItem { &:focus { - outline: 1px dashed @AccentMedium; + outline: 2px @AccentMedium; } .treeNodeHeader { diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 540e4d85c..4fff25384 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -403,6 +403,7 @@ export class AddCollectionPanel extends React.Component 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}