diff --git a/.eslintignore b/.eslintignore index 39c832215..1b3102a26 100644 --- a/.eslintignore +++ b/.eslintignore @@ -21,16 +21,8 @@ src/Common/MongoUtility.ts src/Common/NotificationsClientBase.ts src/Common/QueriesClient.ts src/Common/Splitter.ts -src/Config.ts -src/Contracts/ActionContracts.ts -src/Contracts/DataModels.ts -src/Contracts/Diagnostics.ts -src/Contracts/ExplorerContracts.ts -src/Contracts/Versions.ts -src/Contracts/ViewModels.ts src/Controls/Heatmap/Heatmap.test.ts src/Controls/Heatmap/Heatmap.ts -src/Controls/Heatmap/HeatmapDatatypes.ts src/Definitions/datatables.d.ts src/Definitions/gif.d.ts src/Definitions/globals.d.ts @@ -44,29 +36,10 @@ src/Definitions/png.d.ts src/Definitions/svg.d.ts src/Explorer/ComponentRegisterer.test.ts src/Explorer/ComponentRegisterer.ts -src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts -src/Explorer/Controls/DynamicList/DynamicList.test.ts -src/Explorer/Controls/DynamicList/DynamicListComponent.ts + src/Explorer/Controls/Editor/EditorComponent.ts -src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts -src/Explorer/Controls/InputTypeahead/InputTypeahead.ts src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts -src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts -src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts -src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts -src/Explorer/Controls/Toolbar/IToolbarAction.ts -src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts -src/Explorer/Controls/Toolbar/IToolbarDropDown.ts -src/Explorer/Controls/Toolbar/IToolbarItem.ts -src/Explorer/Controls/Toolbar/IToolbarSeperator.ts -src/Explorer/Controls/Toolbar/IToolbarToggle.ts -src/Explorer/Controls/Toolbar/KeyCodes.ts -src/Explorer/Controls/Toolbar/Toolbar.ts -src/Explorer/Controls/Toolbar/ToolbarAction.ts -src/Explorer/Controls/Toolbar/ToolbarDropDown.ts -src/Explorer/Controls/Toolbar/ToolbarToggle.ts -src/Explorer/Controls/Toolbar/Utilities.ts src/Explorer/DataSamples/ContainerSampleGenerator.test.ts src/Explorer/DataSamples/ContainerSampleGenerator.ts src/Explorer/DataSamples/DataSamplesUtil.test.ts diff --git a/images/close-black.svg b/images/close-black.svg index 9b87397e1..a17e75636 100644 --- a/images/close-black.svg +++ b/images/close-black.svg @@ -1,16 +1,8 @@ - - + \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 889b544e0..57ec3f489 100644 --- a/jest.config.js +++ b/jest.config.js @@ -37,8 +37,8 @@ module.exports = { global: { branches: 25, functions: 25, - lines: 30, - statements: 30, + lines: 29.5, + statements: 29.5, }, }, diff --git a/less/documentDB.less b/less/documentDB.less index 271f53992..79e9435c3 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2357,6 +2357,8 @@ a:link { height: 100%; flex-grow: 1; overflow: hidden; + min-height: 300px; + overflow-y: scroll; } .tabs { @@ -2832,6 +2834,8 @@ a:link { #explorerNotificationConsole { z-index: 1000; + overflow-y: auto; + overflow-x: clip; } .uniqueIndexesContainer { diff --git a/src/Common/CollapsedResourceTree.tsx b/src/Common/CollapsedResourceTree.tsx index 8a91fd5d3..9a9e1661d 100644 --- a/src/Common/CollapsedResourceTree.tsx +++ b/src/Common/CollapsedResourceTree.tsx @@ -1,6 +1,7 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import { userContext } from "../UserContext"; +import { NormalizedEventKey } from "./Constants"; export interface CollapsedResourceTreeProps { toggleLeftPaneExpanded: () => void; @@ -11,6 +12,21 @@ export const CollapsedResourceTree: FunctionComponent { + const focusButton = useRef() as MutableRefObject; + + useEffect(() => { + if (focusButton.current) { + focusButton.current.focus(); + } + }); + + const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => { + if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) { + toggleLeftPaneExpanded(); + event.stopPropagation(); + } + }; + return (
@@ -21,11 +37,14 @@ export const CollapsedResourceTree: FunctionComponent - + Expand - + {userContext.apiType} API diff --git a/src/Common/Constants/Flights.ts b/src/Common/Constants/Flights.ts index 06138c9d7..423258bcf 100644 --- a/src/Common/Constants/Flights.ts +++ b/src/Common/Constants/Flights.ts @@ -5,3 +5,4 @@ export const MongoIndexing = "mongoindexing"; export const AutoscaleTest = "autoscaletest"; export const PartitionKeyTest = "partitionkeytest"; export const PKPartitionKeyTest = "pkpartitionkeytest"; +export const Phoenix = "phoenix"; \ No newline at end of file diff --git a/src/Common/Constants/index.ts b/src/Common/Constants/index.ts index be271e0f2..65597d688 100644 --- a/src/Common/Constants/index.ts +++ b/src/Common/Constants/index.ts @@ -71,6 +71,12 @@ export { ServerIds, }; +export enum ConnectionStatusType { + Connecting = "Connecting", + Connected = "Connected", + Failed = "Connection Failed", +} + export enum ConflictOperationType { Replace = "replace", Create = "create", diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index 3a5a02365..1c49141a0 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -3,8 +3,16 @@ import { resetConfigContext, updateConfigContext } from "../ConfigContext"; import { DatabaseAccount } from "../Contracts/DataModels"; import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; +import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { updateUserContext } from "../UserContext"; -import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient"; +import { + deleteDocument, + getEndpoint, + getFeatureEndpointOrDefault, + queryDocuments, + readDocument, + updateDocument, +} from "./MongoProxyClient"; const databaseId = "testDB"; @@ -246,4 +254,31 @@ describe("MongoProxyClient", () => { expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); }); }); + describe("getFeatureEndpointOrDefault", () => { + beforeEach(() => { + resetConfigContext(); + updateConfigContext({ + BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", + }); + const params = new URLSearchParams({ + "feature.mongoProxyEndpoint": "https://localhost:12901", + "feature.mongoProxyAPIs": "readDocument|createDocument", + }); + const features = extractFeatures(params); + updateUserContext({ + authType: AuthType.AAD, + features: features, + }); + }); + + it("returns a local endpoint", () => { + const endpoint = getFeatureEndpointOrDefault("readDocument"); + expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer"); + }); + + it("returns a production endpoint", () => { + const endpoint = getFeatureEndpointOrDefault("deleteDocument"); + expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer"); + }); + }); }); diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 3893af473..b99bac4b6 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -6,6 +6,7 @@ import * as DataModels from "../Contracts/DataModels"; import { MessageTypes } from "../Contracts/ExplorerContracts"; import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; +import { hasFlag } from "../Platform/Hosted/extractFeatures"; import { userContext } from "../UserContext"; import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; @@ -78,7 +79,7 @@ export function queryDocuments( : "", }; - const endpoint = getEndpoint() || ""; + const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; const headers: HeadersInit = { ...defaultHeaders, ...authHeaders(), @@ -140,7 +141,8 @@ export function readDocument( : "", }; - const endpoint = getEndpoint(); + const endpoint = getFeatureEndpointOrDefault("readDocument"); + return window .fetch(`${endpoint}?${queryString.stringify(params)}`, { method: "GET", @@ -180,7 +182,7 @@ export function createDocument( pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "", }; - const endpoint = getEndpoint(); + const endpoint = getFeatureEndpointOrDefault("createDocument"); return window .fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, { @@ -224,7 +226,7 @@ export function updateDocument( ? documentId.partitionKeyProperty : "", }; - const endpoint = getEndpoint(); + const endpoint = getFeatureEndpointOrDefault("updateDocument"); return window .fetch(`${endpoint}?${queryString.stringify(params)}`, { @@ -265,7 +267,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum ? documentId.partitionKeyProperty : "", }; - const endpoint = getEndpoint(); + const endpoint = getFeatureEndpointOrDefault("deleteDocument"); return window .fetch(`${endpoint}?${queryString.stringify(params)}`, { @@ -308,7 +310,7 @@ export function createMongoCollectionWithProxy( autoPilotThroughput: params.autoPilotMaxThroughput?.toString(), }; - const endpoint = getEndpoint(); + const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy"); return window .fetch( @@ -332,8 +334,15 @@ export function createMongoCollectionWithProxy( }); } -export function getEndpoint(): string { - let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer"; +export function getFeatureEndpointOrDefault(feature: string): string { + return hasFlag(userContext.features.mongoProxyAPIs, feature) + ? getEndpoint(userContext.features.mongoProxyEndpoint) + : getEndpoint(); +} + +export function getEndpoint(customEndpoint?: string): string { + let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; + url += "/api/mongo/explorer"; if (userContext.authType === AuthType.EncryptedToken) { url = url.replace("api/mongo", "api/guest/mongo"); diff --git a/src/Common/ResourceTreeContainer.tsx b/src/Common/ResourceTreeContainer.tsx index 18a769b12..00693a2c0 100644 --- a/src/Common/ResourceTreeContainer.tsx +++ b/src/Common/ResourceTreeContainer.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import refreshImg from "../../images/refresh-cosmos.svg"; import { AuthType } from "../AuthType"; @@ -6,6 +6,7 @@ import Explorer from "../Explorer/Explorer"; import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; import { ResourceTree } from "../Explorer/Tree/ResourceTree"; import { userContext } from "../UserContext"; +import { NormalizedEventKey } from "./Constants"; export interface ResourceTreeContainerProps { toggleLeftPaneExpanded: () => void; @@ -18,6 +19,22 @@ export const ResourceTreeContainer: FunctionComponent { + const focusButton = useRef() as MutableRefObject; + + useEffect(() => { + if (isLeftPaneExpanded) { + if (focusButton.current) { + focusButton.current.focus(); + } + } + }); + + const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => { + if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) { + toggleLeftPaneExpanded(); + event.stopPropagation(); + } + }; return (
{/* Collections Window - - Start */} @@ -43,9 +60,11 @@ export const ResourceTreeContainer: FunctionComponent Hide diff --git a/src/Common/Tooltip/InfoTooltip.tsx b/src/Common/Tooltip/InfoTooltip.tsx index 480aa9020..3ce33ca93 100644 --- a/src/Common/Tooltip/InfoTooltip.tsx +++ b/src/Common/Tooltip/InfoTooltip.tsx @@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent = ({ children }: return ( - + ); diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index efd5ffb78..a8957c662 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -1,3 +1,5 @@ +import { ConnectionStatusType } from "../Common/Constants"; + export interface DatabaseAccount { id: string; name: string; @@ -496,3 +498,8 @@ export interface MemoryUsageInfo { freeKB: number; totalKB: number; } + +export interface ContainerConnectionInfo { + status: ConnectionStatusType; + //need to add ram and rom info +} diff --git a/src/Controls/Heatmap/Heatmap.test.ts b/src/Controls/Heatmap/Heatmap.test.ts index 473e55225..e5f8f5ef6 100644 --- a/src/Controls/Heatmap/Heatmap.test.ts +++ b/src/Controls/Heatmap/Heatmap.test.ts @@ -22,8 +22,8 @@ describe("The Heatmap Control", () => { }; let heatmap: Heatmap; - let theme: PortalTheme = 1; - const divElement: string = `
`; + const theme: PortalTheme = 1; + const divElement = `
`; describe("drawHeatmap rendering", () => { beforeEach(() => { @@ -100,7 +100,7 @@ describe("iframe rendering when there is no data", () => { }); it("should show a no data message with a dark theme", () => { - let data = { + const data = { data: { signature: "pcIframe", data: { @@ -111,7 +111,7 @@ describe("iframe rendering when there is no data", () => { }, }; - const divElement: string = `
`; + const divElement = `
`; document.body.innerHTML = divElement; handleMessage(data as MessageEvent); @@ -120,7 +120,7 @@ describe("iframe rendering when there is no data", () => { }); it("should show a no data message with a white theme", () => { - let data = { + const data = { data: { signature: "pcIframe", data: { @@ -131,7 +131,7 @@ describe("iframe rendering when there is no data", () => { }, }; - const divElement: string = `
`; + const divElement = `
`; document.body.innerHTML = divElement; handleMessage(data as MessageEvent); diff --git a/src/Controls/Heatmap/Heatmap.ts b/src/Controls/Heatmap/Heatmap.ts index 3cdc4589d..0b9c3b796 100644 --- a/src/Controls/Heatmap/Heatmap.ts +++ b/src/Controls/Heatmap/Heatmap.ts @@ -39,7 +39,7 @@ export class Heatmap { } } - private _getFontStyles(size: number = StyleConstants.MediumFontSize, color: string = "#838383"): FontSettings { + private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings { return { family: StyleConstants.DataExplorerFont, size, @@ -78,9 +78,9 @@ export class Heatmap { // go thru all rows and create 2d matrix for heatmap... for (let i = 0; i < rows.length; i++) { output.yAxisPoints.push(rows[i]); - let dataPoints: number[] = []; + const dataPoints: number[] = []; for (let a = 0; a < output.xAxisPoints.length; a++) { - let row: PartitionTimeStampToData = data[rows[i]]; + const row: PartitionTimeStampToData = data[rows[i]]; dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]); } output.dataPoints.push(dataPoints); @@ -193,7 +193,7 @@ export class Heatmap { this._getLayoutSettings(), this._getChartDisplaySettings() ); - let plotDiv: any = document.getElementById(Heatmap.elementId); + const plotDiv: any = document.getElementById(Heatmap.elementId); plotDiv.on("plotly_click", (data: any) => { let timeSelected: string = data.points[0].x; timeSelected = timeSelected.replace(" ", "T"); @@ -205,7 +205,7 @@ export class Heatmap { break; } } - let output = []; + const output = []; for (let i = 0; i < this._chartData.dataPoints.length; i++) { output.push(this._chartData.dataPoints[i][xAxisIndex]); } diff --git a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx index d662db58b..76d710cb7 100644 --- a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx +++ b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx @@ -1,5 +1,6 @@ import { Icon, Label, Stack } from "@fluentui/react"; import * as React from "react"; +import { NormalizedEventKey } from "../../../Common/Constants"; import { accordionStackTokens } from "../Settings/SettingsRenderUtils"; export interface CollapsibleSectionProps { @@ -30,6 +31,13 @@ export class CollapsibleSectionComponent extends React.Component { + if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) { + this.toggleCollapsed(); + event.stopPropagation(); + } + }; + public render(): JSX.Element { return ( <> @@ -39,6 +47,11 @@ export class CollapsibleSectionComponent extends React.Component diff --git a/src/Explorer/Controls/CollapsiblePanel/__snapshots__/CollapsibleSectionComponent.test.tsx.snap b/src/Explorer/Controls/CollapsiblePanel/__snapshots__/CollapsibleSectionComponent.test.tsx.snap index 95d3c46bf..675a79239 100644 --- a/src/Explorer/Controls/CollapsiblePanel/__snapshots__/CollapsibleSectionComponent.test.tsx.snap +++ b/src/Explorer/Controls/CollapsiblePanel/__snapshots__/CollapsibleSectionComponent.test.tsx.snap @@ -3,9 +3,14 @@ exports[`CollapsibleSectionComponent renders 1`] = ` { text: secondaryButtonText, onClick: onSecondaryButtonClick, } - : {}; - + : undefined; return visible ? ( {choiceGroupProps && } diff --git a/src/Explorer/Controls/InputTypeahead/InputTypeahead.less b/src/Explorer/Controls/InputTypeahead/InputTypeahead.less index 1d68e3b7e..082fd937f 100644 --- a/src/Explorer/Controls/InputTypeahead/InputTypeahead.less +++ b/src/Explorer/Controls/InputTypeahead/InputTypeahead.less @@ -8,6 +8,9 @@ .input-type-head-text-field { width: 100%; } + .input-query-form { + width: 100%; + } textarea { width: 100%; line-height: 1; diff --git a/src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx b/src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx index 2e9a2d104..13b91012a 100644 --- a/src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx +++ b/src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx @@ -160,18 +160,21 @@ export class InputTypeaheadComponent extends React.Component< return (
- this.setState({ isSuggestionVisible: true })} - onChange={(_event, newValue?: string) => this.handleChange(newValue)} - /> +
+ this.setState({ isSuggestionVisible: true })} + onChange={(_event, newValue?: string) => this.handleChange(newValue)} + /> + {this.props.showCancelButton && ( 1`] = ` - +
+ +
`; @@ -28,16 +34,22 @@ exports[`inputTypeahead renders