Compare commits

..

1 Commits

Author SHA1 Message Date
Sampath
420f676986 Revert "sev2 accessibilitydefects in data explorer"
This reverts commit b84d5b572c.
2023-03-13 13:39:25 +05:30
43 changed files with 518 additions and 7176 deletions

View File

@@ -92,11 +92,11 @@ jobs:
name: dist
path: dist/
- name: Upload build to preview blob storage
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
- name: Upload preview config to blob storage
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator:
@@ -182,7 +182,7 @@ jobs:
with:
name: dist
- run: cp ./configs/prod.json config.json
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "vimeng@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
@@ -207,7 +207,7 @@ jobs:
name: dist
- run: cp ./configs/mpac.json config.json
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "vimeng@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path stroke="white" stroke-width="0.5" fill="#000" d="M179.199,38.399c0,1.637-0.625,3.274-1.875,4.524l-85.076,85.075l85.076,85.075c2.5,2.5,2.5,6.55,0,9.05s-6.55,2.5-9.05,0 l-89.601-89.6c-2.5-2.5-2.5-6.551,0-9.051l89.601-89.6c2.5-2.5,6.55-2.5,9.05,0C178.574,35.124,179.199,36.762,179.199,38.399z"/>
<path stroke="white" stroke-width="0.5" fill="#b5a3a3" d="M179.199,38.399c0,1.637-0.625,3.274-1.875,4.524l-85.076,85.075l85.076,85.075c2.5,2.5,2.5,6.55,0,9.05s-6.55,2.5-9.05,0 l-89.601-89.6c-2.5-2.5-2.5-6.551,0-9.051l89.601-89.6c2.5-2.5,6.55-2.5,9.05,0C178.574,35.124,179.199,36.762,179.199,38.399z"/>
</svg>

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -2576,10 +2576,6 @@ 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;
@@ -3091,4 +3087,3 @@ a:link {
background: white;
height: 100%;
}

View File

@@ -5,17 +5,12 @@
overflow: auto;
.databaseHeader {
padding: 1px;
font-size: 14px;
}
.collectionHeader {
font-size: 12px;
}
.loadMoreHeader {
color: RGB(5, 99, 193);
}
}
.notebookResourceTree {
@@ -28,4 +23,6 @@
.clickDisabled {
pointer-events: none;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -55,6 +55,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
label={entityValueLabel && entityValueLabel}
className="addEntityTextField"
id="entityValueId"
autoFocus
disabled={isEntityValueDisable}
type={entityValueType}
placeholder={entityValuePlaceholder}

View File

@@ -1,14 +0,0 @@
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);
});
});

View File

@@ -1,6 +1,6 @@
import { OfferResponse } from "@azure/cosmos";
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
import * as OfferUtility from "./OfferUtility";
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos";
describe("parseSDKOfferResponse", () => {
it("manual throughput", () => {
@@ -31,26 +31,6 @@ 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: {

View File

@@ -7,7 +7,7 @@ import {
IStackTokens,
Stack,
TextField,
TooltipHost
TooltipHost,
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg";
@@ -136,21 +136,17 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onEntityTimeValueChange={onEntityTimeValueChange}
/>
{!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip">
<div tabIndex={0}>
<Image
{...imageProps}
src={EditIcon}
alt="editEntity"
id="editEntity"
onClick={onEditEntity}
tabIndex={0}
onKeyPress={handleKeyPress}
/>
</div>
<Image
{...imageProps}
src={EditIcon}
alt="editEntity"
id="editEntity"
onClick={onEditEntity}
tabIndex={0}
onKeyPress={handleKeyPress}
/>
</TooltipHost>
)}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip">

View File

@@ -1,49 +0,0 @@
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");
});
});

View File

@@ -1,4 +1,3 @@
import { Queries } from "Common/Constants";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
@@ -32,35 +31,6 @@ 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[]> {
let rpResponse;

View File

@@ -156,11 +156,6 @@ export interface Collection extends Resource {
requestSchema?: () => void;
}
export interface CollectionsWithPagination {
continuationToken?: string;
collections?: Collection[];
}
export interface Database extends Resource {
collections?: Collection[];
}

View File

@@ -87,13 +87,13 @@ export interface Database extends TreeNode {
isDatabaseExpanded: ko.Observable<boolean>;
isDatabaseShared: ko.Computed<boolean>;
isSampleDB?: boolean;
collectionsContinuationToken?: string;
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
expandDatabase(): Promise<void>;
collapseDatabase(): void;
loadCollections(restart?: boolean): Promise<void>;
loadCollections(): Promise<void>;
findCollectionWithId(collectionId: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void;
onSettingsClick: () => void;

View File

@@ -46,13 +46,10 @@ export class TabComponent extends React.Component<TabComponentProps> {
}
let className = "toggleSwitch";
let ariaselected;
if (index === this.props.currentTabIndex) {
className += " selectedToggle";
ariaselected = true;
} else {
className += " unselectedToggle";
ariaselected = false;
}
return (
@@ -60,10 +57,9 @@ export class TabComponent extends React.Component<TabComponentProps> {
<AccessibleElement
as="span"
className={className}
role="tab"
role="presentation"
onActivated={() => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`}
aria-selected={ariaselected}
>
{tab.title}
</AccessibleElement>
@@ -81,11 +77,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
return (
<div className="tabComponentContainer">
{!this.props.hideHeader && (
<div className="tabs tabSwitch" role="tablist">
{this.renderTabTitles()}
</div>
)}
{!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
<div className={className}>{currentTabContent.render()}</div>
</div>
);

View File

@@ -185,21 +185,20 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack>
<Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input
id="Autoscale-input"
className="throughputInputRadioBtn"
aria-label="Autoscale database throughput"
aria-required={true}
checked={isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/>
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
Autoscale
</label>
<input
id="Autoscale-input"
className="throughputInputRadioBtn"
aria-label="Autoscale database throughput"
aria-required={true}
checked={isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/>
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
Autoscale
</label>
<input
id="Manual-input"
@@ -215,7 +214,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input">
Manual
</label>
</div>
</Stack>
{isAutoscaleSelected && (

View File

@@ -347,14 +347,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
className="panelInfoIcon"
iconName="Info"
role="tooltip"
tabIndex={0}
>
<IconBase
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
className="panelInfoIcon"
iconName="Info"
role="tooltip"
styles={[Function]}
tabIndex={0}
theme={
@@ -635,7 +633,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
className="panelInfoIcon root-57"
data-icon-name="Info"
role="tooltip"
role="img"
tabIndex={0}
>
@@ -656,45 +654,44 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<div
className="ms-Stack css-58"
>
<div
<input
aria-label="Autoscale database throughput"
aria-required={true}
checked={true}
className="throughputInputRadioBtn"
id="Autoscale-input"
key=".0:$.0"
role="radiogroup"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<label
className="throughputInputRadioBtnLabel"
htmlFor="Autoscale-input"
key=".0:$.1"
>
<input
aria-label="Autoscale database throughput"
aria-required={true}
checked={true}
className="throughputInputRadioBtn"
id="Autoscale-input"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<label
className="throughputInputRadioBtnLabel"
htmlFor="Autoscale-input"
>
Autoscale
</label>
<input
aria-label="Manual database throughput"
aria-required={true}
checked={false}
className="throughputInputRadioBtn"
id="Manual-input"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<label
className="throughputInputRadioBtnLabel"
htmlFor="Manual-input"
>
Manual
</label>
</div>
Autoscale
</label>
<input
aria-label="Manual database throughput"
aria-required={true}
checked={false}
className="throughputInputRadioBtn"
id="Manual-input"
key=".0:$.2"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<label
className="throughputInputRadioBtnLabel"
htmlFor="Manual-input"
key=".0:$.3"
>
Manual
</label>
</div>
</Stack>
<Stack
@@ -1341,14 +1338,12 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
className="panelInfoIcon"
iconName="Info"
role="tooltip"
tabIndex={0}
>
<IconBase
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
className="panelInfoIcon"
iconName="Info"
role="tooltip"
styles={[Function]}
tabIndex={0}
theme={
@@ -1629,7 +1624,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
className="panelInfoIcon root-57"
data-icon-name="Info"
role="tooltip"
role="img"
tabIndex={0}
>

View File

@@ -10,7 +10,7 @@ import {
IButtonStyles,
IconButton,
IContextualMenuItemProps,
IContextualMenuProps
IContextualMenuProps,
} from "@fluentui/react";
import * as React from "react";
import AnimateHeight from "react-animate-height";
@@ -195,7 +195,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
</div>
{node.children && (
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
<div className="nodeChildren" data-test={node.label} role="group">
<div className="nodeChildren" data-test={node.label}>
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
<TreeNodeComponent
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}

View File

@@ -40,32 +40,30 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
onKeyPress={[Function]}
role="treeitem"
>
<div>
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
tabIndex={-1}
}
tabIndex={-1}
>
<img
alt="label branch is collapsed"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
<img
alt="label branch is collapsed"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
label
</span>
</div>
label
</span>
</div>
<div
className="loadingIconContainer"
@@ -102,7 +100,6 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
<div
className="nodeChildren"
data-test="label"
role="group"
>
<TreeNodeComponent
generation={3}
@@ -145,80 +142,78 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
onKeyPress={[Function]}
role="treeitem"
>
<div>
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 23,
}
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 23,
}
tabIndex={-1}
}
tabIndex={-1}
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
label
</span>
<div
onContextMenu={[Function]}
onKeyPress={[Function]}
>
<CustomizedIconButton
ariaLabel="More"
className="treeMenuEllipsis"
menuIconProps={
Object {
"iconName": "More",
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
},
}
}
menuProps={
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": Array [
Object {
"className": undefined,
"disabled": true,
"key": "menuLabel",
"onClick": [Function],
"onRenderIcon": [Function],
"text": "menuLabel",
},
],
"onMenuDismissed": [Function],
"onMenuOpened": [Function],
}
}
name="More"
styles={
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
}
title="More"
/>
<span
className="nodeLabel"
title="label"
>
label
</span>
<div
onContextMenu={[Function]}
onKeyPress={[Function]}
>
<CustomizedIconButton
ariaLabel="More"
className="treeMenuEllipsis"
menuIconProps={
Object {
"iconName": "More",
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
},
}
}
menuProps={
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": Array [
Object {
"className": undefined,
"disabled": true,
"key": "menuLabel",
"onClick": [Function],
"onRenderIcon": [Function],
"text": "menuLabel",
},
],
"onMenuDismissed": [Function],
"onMenuOpened": [Function],
}
}
name="More"
styles={
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
}
title="More"
/>
</div>
</div>
</div>
<div
@@ -256,7 +251,6 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
<div
className="nodeChildren"
data-test="label"
role="group"
>
<TreeNodeComponent
generation={13}
@@ -298,32 +292,30 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
onKeyPress={[Function]}
role="treeitem"
>
<div>
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
tabIndex={-1}
}
tabIndex={-1}
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
label
</span>
</div>
label
</span>
</div>
<div
className="loadingIconContainer"
@@ -360,7 +352,6 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
<div
className="nodeChildren"
data-test="label"
role="group"
/>
</AnimateHeight>
</div>
@@ -374,71 +365,69 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
onKeyPress={[Function]}
role="treeitem"
>
<div>
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 23,
}
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 23,
}
tabIndex={-1}
}
tabIndex={-1}
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
label
</span>
<div
onContextMenu={[Function]}
onKeyPress={[Function]}
>
<CustomizedIconButton
ariaLabel="More"
className="treeMenuEllipsis"
menuIconProps={
Object {
"iconName": "More",
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
},
}
}
menuProps={
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": Array [],
"onMenuDismissed": [Function],
"onMenuOpened": [Function],
}
}
name="More"
styles={
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
}
title="More"
/>
<span
className="nodeLabel"
title="label"
>
label
</span>
<div
onContextMenu={[Function]}
onKeyPress={[Function]}
>
<CustomizedIconButton
ariaLabel="More"
className="treeMenuEllipsis"
menuIconProps={
Object {
"iconName": "More",
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
},
}
}
menuProps={
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": Array [],
"onMenuDismissed": [Function],
"onMenuOpened": [Function],
}
}
name="More"
styles={
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
}
title="More"
/>
</div>
</div>
</div>
<div
@@ -476,7 +465,6 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
<div
className="nodeChildren"
data-test="label"
role="group"
>
<TreeNodeComponent
generation={13}
@@ -546,32 +534,30 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
onKeyPress={[Function]}
role="treeitem"
>
<div>
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
<div
className="treeNodeHeader "
data-test="label"
style={
Object {
"paddingLeft": 9,
}
tabIndex={-1}
}
tabIndex={-1}
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
<img
alt="label branch is expanded"
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src=""
tabIndex={0}
/>
<span
className="nodeLabel"
title="label"
>
label
</span>
</div>
label
</span>
</div>
<div
className="loadingIconContainer"
@@ -608,7 +594,6 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
<div
className="nodeChildren"
data-test="label"
role="group"
>
<TreeNodeComponent
generation={3}

View File

@@ -3,7 +3,7 @@
.treeComponent {
.nodeItem {
&:focus {
outline: 2px @AccentMedium;
outline: 1px dashed @AccentMedium;
}
.treeNodeHeader {

View File

@@ -577,7 +577,7 @@ export default class Explorer {
try {
await Promise.all(
databasesToLoad.map(async (database: ViewModels.Database) => {
await database.loadCollections(true);
await database.loadCollections();
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
if (isNewDatabase) {
database.expandDatabase();

View File

@@ -40,7 +40,6 @@ export class GraphVizComponent extends React.Component<GraphVizComponentProps> {
{/* svg load more icon inlined as-is here: remove the style="fill:#374649;" so we can override it */}
<svg
role="img"
aria-label="graph"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
@@ -136,7 +135,6 @@ export class GraphVizComponent extends React.Component<GraphVizComponentProps> {
<g id="triangleRight">
<svg
role="img"
aria-label="graph"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -17,19 +17,19 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
<div className="middlePane">
<div className="graphTitle">
<span className="paneTitle">Graph</span>
<button
style={{ border: "none", background: "none" }}
<span
className="graphExpandCollapseBtn pull-right"
onClick={this.props.toggleExpandGraph}
role="button"
aria-expanded={this.props.isTabsContentExpanded}
aria-name="View graph in full screen"
tabIndex={0}
>
<img
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
/>
</button>
</span>
</div>
<div className="maingraphContainer">
<GraphVizComponent forceGraphParams={this.props.graphVizProps.forceGraphParams} />

View File

@@ -202,7 +202,6 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
if (showOpenFullScreen) {
const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = {
id: "openFullScreenBtn",
iconSrc: OpenInTabIcon,
iconAlt: label,
onCommandClick: () => {

View File

@@ -147,9 +147,12 @@ export class NotificationConsoleComponent extends React.Component<
<div className="notificationConsoleControls">
<Dropdown
label="Filter:"
role="combobox"
selectedKey={this.state.selectedFilter}
options={NotificationConsoleComponent.FilterOptions}
onChange={this.onFilterSelected.bind(this)}
aria-labelledby="consoleFilterLabel"
aria-label={this.state.selectedFilter}
/>
<span className="consoleSplitter" />
<span

View File

@@ -112,6 +112,8 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
className="notificationConsoleControls"
>
<Dropdown
aria-label="All"
aria-labelledby="consoleFilterLabel"
label="Filter:"
onChange={[Function]}
options={
@@ -134,6 +136,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
},
]
}
role="combobox"
selectedKey="All"
/>
<span
@@ -275,6 +278,8 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
className="notificationConsoleControls"
>
<Dropdown
aria-label="All"
aria-labelledby="consoleFilterLabel"
label="Filter:"
onChange={[Function]}
options={
@@ -297,6 +302,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
},
]
}
role="combobox"
selectedKey="All"
/>
<span

View File

@@ -1,23 +0,0 @@
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);
});
});

View File

@@ -13,7 +13,7 @@ import {
Stack,
TeachingBubble,
Text,
TooltipHost
TooltipHost,
} from "@fluentui/react";
import * as Constants from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection";
@@ -100,7 +100,6 @@ export interface AddCollectionPanelState {
isExecuting: boolean;
isThroughputCapExceeded: boolean;
teachingBubbleStep: number;
isParentTooltipVisible:boolean;
}
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
@@ -109,7 +108,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private collectionThroughput: number;
private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean;
constructor(props: AddCollectionPanelProps) {
super(props);
@@ -136,16 +134,13 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
isExecuting: false,
isThroughputCapExceeded: false,
teachingBubbleStep: 0,
isParentTooltipVisible: false,
};
}
componentDidMount(): void {
if (this.state.teachingBubbleStep === 0 && this.props.isQuickstart) {
this.setState({ teachingBubbleStep: 1 });
}
}
render(): JSX.Element {
@@ -279,34 +274,32 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
<Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={this.state.createNewDatabase}
aria-label="Create new database"
aria-checked={this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
id="databaseCreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Create new</span>
<input
className="panelRadioBtn"
checked={this.state.createNewDatabase}
aria-label="Create new database"
aria-checked={this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
id="databaseCreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Create new</span>
<input
className="panelRadioBtn"
checked={!this.state.createNewDatabase}
aria-label="Use existing database"
aria-checked={!this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Use existing</span>
</div>
<input
className="panelRadioBtn"
checked={!this.state.createNewDatabase}
aria-label="Use existing database"
aria-checked={!this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Use existing</span>
</Stack>
{this.state.createNewDatabase && (
@@ -408,12 +401,10 @@ 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.`}
>
<Icon
role="button"
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
/>
</TooltipHost>
</Stack>
@@ -811,37 +802,35 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
<Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Enable analytical store"
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="enableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">On</span>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</div>
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!this.isSynapseLinkEnabled()}
aria-label="Disable analytical store"
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
type="radio"
role="radio"
id="disableAnalyticalStoreBtn"
tabIndex={0}
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Off</span>
</Stack>
{!this.isSynapseLinkEnabled() && (
@@ -990,7 +979,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private getPartitionKeyPlaceHolder(index?: number): string {
switch (userContext.apiType) {
case "Mongo":
return "e.g., categoryId";
return "e.g., address.zipCode";
case "Gremlin":
return "e.g., /address";
case "SQL":
@@ -1122,7 +1111,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return userContext.apiType === "SQL" ? "/pk" : "pk";
}
if (this.props.isQuickstart) {
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId";
return userContext.apiType === "SQL" ? "/address" : "address";
}
return "";
}

View File

@@ -21,11 +21,6 @@ export const SettingsPane: FunctionComponent = () => {
const [customItemPerPage, setCustomItemPerPage] = useState<number>(
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>(
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled)
? LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
@@ -55,7 +50,6 @@ 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);
@@ -191,25 +185,6 @@ export const SettingsPane: FunctionComponent = () => {
</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 && (
<div className="settingsSection">
<div className="settingsSectionPart">

View File

@@ -97,35 +97,6 @@ exports[`Settings Pane should render Default properly 1`] = `
</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
className="settingsSection"
>
@@ -211,35 +182,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
<div
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
className="settingsSection"
>

View File

@@ -42,43 +42,39 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
horizontal={true}
verticalAlign="center"
>
<div
role="radiogroup"
<input
aria-checked={true}
aria-label="Create new database"
checked={true}
className="panelRadioBtn"
id="databaseCreateNew"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
<input
aria-checked={true}
aria-label="Create new database"
checked={true}
className="panelRadioBtn"
id="databaseCreateNew"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Create new
</span>
<input
aria-checked={false}
aria-label="Use existing database"
checked={false}
className="panelRadioBtn"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Use existing
</span>
</div>
Create new
</span>
<input
aria-checked={false}
aria-label="Use existing database"
checked={false}
className="panelRadioBtn"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Use existing
</span>
</Stack>
<Stack
className="panelGroupSpacing"
@@ -172,7 +168,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
ariaLabel="Unique identifier for the container and used for id-based routing through REST and all SDKs."
className="panelInfoIcon"
iconName="Info"
role="button"
tabIndex={0}
/>
</StyledTooltipHostBase>
@@ -347,46 +342,42 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
horizontal={true}
verticalAlign="center"
>
<div
role="radiogroup"
<input
aria-checked={false}
aria-label="Enable analytical store"
checked={false}
className="panelRadioBtn"
disabled={true}
id="enableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
<input
aria-checked={false}
aria-label="Enable analytical store"
checked={false}
className="panelRadioBtn"
disabled={true}
id="enableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
On
</span>
<input
aria-checked={true}
aria-label="Disable analytical store"
checked={true}
className="panelRadioBtn"
disabled={true}
id="disableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Off
</span>
</div>
On
</span>
<input
aria-checked={true}
aria-label="Disable analytical store"
checked={true}
className="panelRadioBtn"
disabled={true}
id="disableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Off
</span>
</Stack>
<Stack
className="panelGroupSpacing"

View File

@@ -1,4 +1,4 @@
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
@@ -18,11 +18,6 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
return <></>;
}
let totalSteps = 9;
if (userContext.isTryCosmosDBSubscription) {
totalSteps = 10;
}
switch (step) {
case 1:
return isSampleDBExpanded ? (
@@ -38,7 +33,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
},
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 1 of " + totalSteps}
footerContent="Step 1 of 8"
>
Start viewing and working with your data by opening Documents under Data
</TeachingBubble>
@@ -60,7 +55,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(1),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 2 of " + totalSteps}
footerContent="Step 2 of 8"
>
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
so.
@@ -83,7 +78,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(2),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 3 of " + totalSteps}
footerContent="Step 3 of 8"
>
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
drivers to do so.
@@ -104,7 +99,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(3),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 4 of " + totalSteps}
footerContent="Step 4 of 8"
>
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.
@@ -125,7 +120,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(4),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 5 of " + totalSteps}
footerContent="Step 5 of 8"
>
Change throughput provisioned to your collection according to your needs
</TeachingBubble>
@@ -145,7 +140,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(5),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 6 of " + totalSteps}
footerContent="Step 6 of 8"
>
Use the indexing policy editor to create and edit your indexes.
</TeachingBubble>
@@ -165,54 +160,12 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(6),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 7 of " + totalSteps}
footerContent="Step 7 of 8"
>
Visualize your data, store queries in an interactive document
</TeachingBubble>
);
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&apos;re ready, upgrade to production.
</TeachingBubble>
);
case 10:
return (
<TeachingBubble
headline="Congratulations!"
@@ -227,10 +180,10 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)),
onClick: () => setStep(7),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step " + totalSteps + " of " + totalSteps}
footerContent="Step 8 of 8"
>
<Stack>
<Text style={{ color: "white" }}>

View File

@@ -1,4 +1,4 @@
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
@@ -17,10 +17,6 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
if (userContext.apiType !== "SQL") {
return <></>;
}
let totalSteps = 8;
if (userContext.isTryCosmosDBSubscription) {
totalSteps = 9;
}
switch (step) {
case 1:
@@ -37,7 +33,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
},
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 1 of " + totalSteps}
footerContent="Step 1 of 7"
>
Start viewing and working with your data by opening Items under Data
</TeachingBubble>
@@ -59,7 +55,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(1),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 2 of " + totalSteps}
footerContent="Step 2 of 7"
>
View item here using the items window. Additionally you can also filter items to be reviewed with the filter
function
@@ -82,7 +78,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(2),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 3 of " + totalSteps}
footerContent="Step 3 of 7"
>
Add new item by copy / pasting JSON; or uploading a JSON
</TeachingBubble>
@@ -102,7 +98,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(3),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 4 of " + totalSteps}
footerContent="Step 4 of 7"
>
Query your data using either the filter function or new query.
</TeachingBubble>
@@ -122,7 +118,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(4),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 5 of " + totalSteps}
footerContent="Step 5 of 7"
>
Change throughput provisioned to your container according to your needs
</TeachingBubble>
@@ -142,54 +138,12 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
onClick: () => setStep(5),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step 6 of " + totalSteps}
footerContent="Step 6 of 7"
>
Visualize your data, store queries in an interactive document
</TeachingBubble>
);
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&apos;re ready, upgrade to production.
</TeachingBubble>
);
case 9:
return (
<TeachingBubble
headline="Congratulations!"
@@ -204,10 +158,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)),
onClick: () => setStep(6),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent={"Step " + totalSteps + " of " + totalSteps}
footerContent="Step 7 of 7"
>
<Stack>
<Text style={{ color: "white" }}>

View File

@@ -9,7 +9,7 @@ import {
Stack,
TeachingBubble,
TeachingBubbleContent,
Text
Text,
} from "@fluentui/react";
import { sendMessage } from "Common/MessageHandler";
import { MessageTypes } from "Contracts/ExplorerContracts";
@@ -116,14 +116,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<form className="connectExplorerFormContainer">
<div className="splashScreenContainer">
<div className="splashScreen">
<div
className="title"
aria-label={
userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"
}
>
<div className="title">
{userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"}
@@ -570,17 +563,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}
private getLearningResourceItems(): JSX.Element {
interface item {
link: string;
title: string;
description: string;
}
const cdbLiveTv: item = {
link: "https://developer.azurecosmosdb.com/tv",
title: "Learn the Fundamentals",
description: "Watch Azure Cosmos DB Live TV show introductory and how to videos.",
};
let items: item[];
let items: { link: string; title: string; description: string }[];
switch (userContext.apiType) {
case "SQL":
case "Postgres":
@@ -590,7 +573,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Get Started using an SDK",
description: "Learn about the Azure Cosmos DB SDK.",
},
cdbLiveTv,
{
link: "https://aka.ms/msl-complex-queries",
title: "Master Complex Queries",
description: "Learn how to author complex queries.",
},
{
link: "https://aka.ms/msl-move-data",
title: "Migrate Your Data",
@@ -610,7 +597,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Getting Started Guide",
description: "Learn the basics to get started.",
},
cdbLiveTv,
{
link: "http://aka.ms/mongodotnet",
title: "Build a web API",
description: "Create a web API with the.NET SDK.",
},
];
break;
case "Cassandra":
@@ -620,7 +611,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Create a Container",
description: "Get to know the create a container options.",
},
cdbLiveTv,
{
link: "https://aka.ms/cassandraserverdiagnostics",
title: "Run Server Diagnostics",
description: "Learn how to run server diagnostics.",
},
{
link: "https://aka.ms/Cassandrathroughput",
title: "Provision Throughput",
@@ -640,7 +635,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Import Graph Data",
description: "Learn Bulk ingestion data using BulkExecutor",
},
cdbLiveTv,
{
link: "https://aka.ms/graphoptimize",
title: "Optimize your Queries",
description: "Learn how to evaluate your Gremlin queries",
},
];
break;
case "Tables":
@@ -655,7 +654,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
title: "Build a Java App",
description: "Create a Azure Cosmos DB for Table app with Java SDK ",
},
cdbLiveTv,
{
link: "https://aka.ms/tablenodejs",
title: "Build a Node.js App",
description: "Create a Azure Cosmos DB for Table app with Node.js SDK",
},
];
break;
}

View File

@@ -92,7 +92,6 @@ 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}

View File

@@ -3,7 +3,7 @@ import React from "react";
import * as _ from "underscore";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import { readCollections, readCollectionsWithPagination } from "../../Common/dataAccess/readCollections";
import { readCollections } from "../../Common/dataAccess/readCollections";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
@@ -13,7 +13,6 @@ 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";
@@ -39,7 +38,6 @@ export default class Database implements ViewModels.Database {
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public junoClient: JunoClient;
public isSampleDB: boolean;
public collectionsContinuationToken?: string;
private isOfferRead: boolean;
constructor(container: Explorer, data: DataModels.Database) {
@@ -142,11 +140,7 @@ export default class Database implements ViewModels.Database {
}
await this.loadOffer();
if (this.collections()?.length === 0) {
await this.loadCollections(true);
}
await this.loadCollections();
this.isDatabaseExpanded(true);
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
description: "Database node",
@@ -168,31 +162,9 @@ export default class Database implements ViewModels.Database {
});
}
public async loadCollections(restart = false) {
public async loadCollections(): Promise<void> {
const collectionVMs: Collection[] = [];
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());
}
const collections: DataModels.Collection[] = 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) {
@@ -227,9 +199,7 @@ export default class Database implements ViewModels.Database {
//merge collections
this.addCollectionsToList(collectionVMs);
if (!containerPaginationEnabled || restart) {
this.deleteCollectionsFromList(deltaCollections.toDelete);
}
this.deleteCollectionsFromList(deltaCollections.toDelete);
useDatabases.getState().updateDatabase(this);
}

View File

@@ -479,18 +479,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ 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))

View File

@@ -80,7 +80,6 @@ const App: React.FunctionComponent = () => {
return (
<div className="flexContainer">
<div id="divExplorer" className="flexContainer hideOverflows">
<div id="freeTierTeachingBubble"> </div>
{/* Main Command Bar - Start */}
<CommandBar container={explorer} />
{/* Collections Tree and Tabs - Begin */}

View File

@@ -4,7 +4,6 @@ import * as SessionStorageUtility from "./SessionStorageUtility";
export { LocalStorageUtility, SessionStorageUtility };
export enum StorageKey {
ActualItemPerPage,
ContainerPaginationEnabled,
CustomItemPerPage,
DatabaseAccountId,
EncryptedKeyToken,

View File

@@ -49,7 +49,7 @@ export function getMsalInstance() {
cacheLocation: "localStorage",
},
auth: {
authority: `${configContext.AAD_ENDPOINT}organizations`,
authority: `${configContext.AAD_ENDPOINT}common`,
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
},
};

View File

@@ -21,23 +21,6 @@ function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEven
return false;
}
export function shouldProcessMessage(event: MessageEvent): boolean {
if (typeof event.data !== "object") {
return false;
}
if (event.data["signature"] !== "pcIframe") {
return false;
}
if (!("data" in event.data)) {
return false;
}
if (typeof event.data["data"] !== "object") {
return false;
}
return true;
}
export function isReadyMessage(event: MessageEvent): boolean {
if (!event?.data?.kind && !event?.data?.data) {
return false;

View File

@@ -10,7 +10,7 @@ const PortalIPs: { [key: string]: string[] } = {
usnat: ["7.28.202.68"],
};
export const getNetworkSettingsWarningMessage = (): string => {
export const getNetworkSettingsWarningMessage = (clientIpAddress: string): string => {
const accountProperties = userContext.databaseAccount?.properties;
if (!accountProperties) {
@@ -40,7 +40,13 @@ export const getNetworkSettingsWarningMessage = (): string => {
if (numberOfMatches !== portalIPs.length) {
return "The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure Portal to proceed.";
}
}
return "";
return "";
} else {
if (!clientIpAddress || ipRules.some((ipRule) => ipRule.ipAddressOrRange === clientIpAddress)) {
return "";
}
return "The Network settings for this account are preventing access from Data Explorer. Please add your current IP to the firewall rules to proceed.";
}
};

View File

@@ -1,4 +1,3 @@
import Explorer from "Explorer/Explorer";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
@@ -11,6 +10,7 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer";
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
import { useDatabases } from "../Explorer/useDatabases";
import {
@@ -33,7 +33,7 @@ import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
import { getMsalInstance } from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
// This hook will create a new instance of Explorer.ts and bind it to the DOM
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
@@ -239,7 +239,6 @@ async function configurePortal(): Promise<Explorer> {
updateUserContext({
authType: AuthType.AAD,
});
let explorer: Explorer;
return new Promise((resolve) => {
// In development mode, try to load the iframe message from session storage.
// This allows webpack hot reload to function properly in the portal
@@ -252,7 +251,7 @@ async function configurePortal(): Promise<Explorer> {
);
console.dir(message);
updateContextsFromPortalMessage(message);
explorer = new Explorer();
const explorer = new Explorer();
// In development mode, save the iframe message from the portal in session storage.
// This allows webpack hot reload to funciton properly
if (process.env.NODE_ENV === "development") {
@@ -288,7 +287,7 @@ async function configurePortal(): Promise<Explorer> {
}
updateContextsFromPortalMessage(inputs);
explorer = new Explorer();
const explorer = new Explorer();
resolve(explorer);
if (openAction) {
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
@@ -301,8 +300,6 @@ async function configurePortal(): Promise<Explorer> {
} else {
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
}
} else if (message?.type === MessageTypes.RefreshResources) {
explorer.onRefreshResourcesClick();
}
},
false
@@ -317,6 +314,23 @@ function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
return messageOrigin === window.document.location.origin && message.type === MessageTypes.TelemetryInfo;
}
function shouldProcessMessage(event: MessageEvent): boolean {
if (typeof event.data !== "object") {
return false;
}
if (event.data["signature"] !== "pcIframe") {
return false;
}
if (!("data" in event.data)) {
return false;
}
if (typeof event.data["data"] !== "object") {
return false;
}
return true;
}
function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (
configContext.BACKEND_ENDPOINT &&
@@ -368,7 +382,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
}
}
const warningMessage = getNetworkSettingsWarningMessage();
const warningMessage = getNetworkSettingsWarningMessage(inputs.clientIpAddress);
useTabs.getState().setNetworkSettingsWarning(warningMessage);
if (inputs.features) {