Compare commits

..

5 Commits

Author SHA1 Message Date
Sung-Hyun Kang
7cf0eb511a Add componentDidMount for initial render 2024-06-17 14:19:23 -05:00
Sung-Hyun Kang
f7b7d135df merge conflict 2024-06-17 13:40:18 -05:00
Sung-Hyun Kang
1ab6bf3d81 Remove dependency on previous prop of deletion id 2024-06-17 13:23:22 -05:00
Sung-Hyun Kang
ac8dbbc0d2 sample db handling 2024-06-17 12:30:28 -05:00
Sung-Hyun Kang
edfd6cfc30 handle sampledb error handling to load the data explorer 2024-06-12 22:41:35 -05:00
212 changed files with 28084 additions and 28057 deletions

5
.npmrc
View File

@@ -1,4 +1 @@
save-exact=true
# Ignore peer dependency conflicts
force=true # TODO: Remove this when we update to React 17 or higher!
save-exact=true

7
canvas/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Why?
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved

1
canvas/index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = {}

11
canvas/package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "canvas",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-f8d4d392-7c12-4bd9-baff-66fbf7814b91" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="m3.802,14.032c.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128v4.073c-.286,0-.574-.078-.824-.234l-4.374-2.734Z" fill="#225086"/>
<path d="m7.853,1.507L.353,9.967c-.579.654-.428,1.642.323,2.111,0,0,2.776,1.735,3.126,1.954.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128-4.364-2.728,4.365-4.924V1s0,0,0,0c-.424,0-.847.169-1.147.507Z" fill="#6df"/>
<polygon points="4.636 10.199 4.688 10.231 9 12.927 9.001 12.927 9.001 12.927 9.001 5.276 9 5.275 4.636 10.199" fill="#cbf8ff"/>
<path d="m17.324,12.078c.751-.469.902-1.457.323-2.111l-4.921-5.551c-.397-.185-.842-.291-1.313-.291-.925,0-1.752.399-2.302,1.026l-.109.123h0s4.364,4.924,4.364,4.924h0s0,0,0,0l-4.365,2.728v4.073c.287,0,.573-.078.823-.234l7.5-4.688Z" fill="#074793"/>
<path d="m9.001,1v4.275s.109-.123.109-.123c.55-.627,1.377-1.026,2.302-1.026.472,0,.916.107,1.313.291l-2.579-2.909c-.299-.338-.723-.507-1.146-.507Z" fill="#0294e4"/>
<polygon points="13.365 10.199 13.365 10.199 13.365 10.199 9.001 5.276 9.001 12.926 13.365 10.199" fill="#96bcc2"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -80,7 +80,6 @@ module.exports = {
"d3-quadtree": "<rootDir>/node_modules/d3-quadtree/dist/d3-quadtree.min.js",
"d3-scale-chromatic": "<rootDir>/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js",
"d3-zoom": "<rootDir>/node_modules/d3-zoom/dist/d3-zoom.min.js",
uuid: require.resolve("uuid"), // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@@ -134,7 +133,7 @@ module.exports = {
snapshotSerializers: ["enzyme-to-json/serializer"],
// The test environment that will be used for testing
testEnvironment: "jsdom",
// testEnvironment: "jest-environment-jsdom",
modulePaths: ["node_modules", "<rootDir>/src"],
// Options that will be passed to the testEnvironment
@@ -158,7 +157,7 @@ module.exports = {
// testResultsProcessor: "./trxProcessor.js",
// This option allows use of a custom test runner
testRunner: "jest-circus/runner",
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
@@ -168,13 +167,13 @@ module.exports = {
// A map from regular expressions to paths to transformers
transform: {
"^.+\\.html?$": "jest-html-loader",
"^.+\\.html?$": "html-loader-jest",
"^.+\\.[t|j]sx?$": "babel-jest",
"^.+\\.svg$": "<rootDir>/jest/svgTransform.js",
"^.+\\.svg$": "<rootDir>/svgTransform.js",
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: ["/node_modules/(?!@fluentui/react-icons)", "/externals/"],
transformIgnorePatterns: ["/node_modules/", "/externals/"],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
@@ -187,7 +186,4 @@ module.exports = {
// Whether to use watchman for file crawling
// watchman: true,
// TODO: toMatchInlineSnapshot() does not work with prettier 3. Remove when fixed: https://github.com/jestjs/jest/issues/14305
prettierPath: null,
};

View File

@@ -168,7 +168,7 @@
@FabricBoxBorderRadius: 8px;
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
@FabricBoxMargin: 4px 8px 4px 8px;
@FabricBoxMargin: 4px 3px 4px 3px;
@FabricAccentMediumHigh: #0c695a;
@FabricAccentMedium: #117865;

View File

@@ -1906,14 +1906,8 @@ input::-webkit-calendar-picker-indicator::after {
}
.nav-tabs-margin {
height: 32px;
padding-top: 8px;
background-color: #f2f2f2;
.nav-tabs {
display: flex;
align-items: flex-end;
height: 100%;
}
}
.navTabHeight {
@@ -2080,6 +2074,14 @@ a:link {
display: inline;
}
.resourceTreeAndTabs {
display: flex;
flex: 1 1 auto;
overflow-x: clip;
overflow-y: auto;
height: 100%;
}
.collectiontitle {
font-size: 14px;
text-transform: uppercase;
@@ -2323,6 +2325,11 @@ td a:hover {
outline: 1px dotted;
}
#content.active .tabdocuments .scrollable {
height: 100%;
overflow-y: auto;
}
.table-fixed thead {
width: 97%;
padding-left: 18px;
@@ -2358,9 +2365,10 @@ a:link {
.tabsManagerContainer {
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
min-height: 300px;
}
.tabs {
@@ -2571,6 +2579,18 @@ a:link {
cursor: pointer;
}
.documentsTab {
.documentsTable {
.documentsTableCell {
border-left: 1px solid @BaseMedium;
height: 100%;
}
.documentsTableHeader {
border-bottom: 1px solid @BaseMedium;
}
}
}
.querydropdown {
border: 1px solid @BaseMedium;
font-style: normal;
@@ -2617,7 +2637,7 @@ a:link {
.tabPanesContainer {
display: flex;
flex-grow: 1;
height: 100%;
overflow: hidden;
}

View File

@@ -38,7 +38,7 @@ a:focus {
}
.nav-tabs-margin {
padding-top: 5px;
padding-top: 8px;
background-color: #ffffff
}

View File

@@ -3,6 +3,19 @@
.dataResourceTree {
margin-left: @MediumSpace;
overflow: auto;
.databaseHeader {
padding: 1px;
font-size: 14px;
}
.collectionHeader {
font-size: 12px;
}
.loadMoreHeader {
color: RGB(5, 99, 193);
}
}
.notebookResourceTree {

19791
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,14 +12,14 @@
"@azure/msal-browser": "2.14.2",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.119.0",
"@fluentui/react-components": "9.54.2",
"@fluentui/react": "8.112.1",
"@fluentui/react-components": "9.34.0",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1",
"@nteract/commutable": "7.5.1",
"@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.9",
"@nteract/core": "15.1.0",
"@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1",
@@ -42,15 +42,15 @@
"@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "6.4.6",
"@testing-library/jest-dom": "5.11.9",
"@types/lodash": "4.14.171",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uiw/react-split": "5.9.3",
"@xmldom/xmldom": "0.7.13",
"allotment": "1.20.2",
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"canvas": "2.11.2",
"canvas": "file:./canvas",
"clean-webpack-plugin": "4.0.0",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "11.0.0",
@@ -67,7 +67,7 @@
"eslint-plugin-react": "7.33.2",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"i18next": "23.11.5",
"i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23",
"iframe-resizer-react": "1.1.0",
@@ -93,13 +93,13 @@
"react-dnd-html5-backend": "14.0.0",
"react-dom": "16.14.0",
"react-hotkeys": "2.0.0",
"react-i18next": "14.1.2",
"react-i18next": "11.8.5",
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1",
"react-window": "1.8.10",
"react-youtube": "9.0.1",
"react-window": "1.8.10",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
"sanitize-html": "2.3.3",
@@ -113,10 +113,10 @@
"zustand": "3.5.0"
},
"devDependencies": {
"@babel/core": "7.24.7",
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.0",
"@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.9.0",
"@playwright/test": "1.44.0",
"@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7",
@@ -129,13 +129,13 @@
"@types/enzyme": "3.10.12",
"@types/enzyme-adapter-react-16": "1.0.9",
"@types/hasher": "0.0.31",
"@types/jest": "29.5.12",
"@types/jest": "26.0.20",
"@types/jquery": "3.5.29",
"@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/q": "1.5.1",
"@types/react": "17.0.44",
"@types/react-dom": "17.0.15",
"@types/react": "17.0.3",
"@types/react-dom": "17.0.3",
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1",
@@ -148,7 +148,7 @@
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"@webpack-cli/serve": "2.0.5",
"babel-jest": "29.7.0",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"buffer": "5.1.0",
"case-sensitive-paths-webpack-plugin": "2.4.0",
@@ -165,15 +165,13 @@
"fast-glob": "3.2.5",
"fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.2",
"html-loader": "5.0.0",
"html-loader": "0.5.5",
"html-loader-jest": "0.2.1",
"html-webpack-plugin": "5.5.3",
"jest": "29.7.0",
"jest-canvas-mock": "2.5.2",
"jest-circus": "29.7.0",
"jest-html-loader": "1.0.0",
"jest": "26.6.3",
"jest-canvas-mock": "2.3.1",
"jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "3.0.2",
"jest-environment-jsdom": "29.7.0",
"jest-trx-results-processor": "0.0.7",
"less": "3.8.1",
"less-loader": "11.1.3",
"less-vars-loader": "1.1.0",
@@ -189,8 +187,8 @@
"sinon": "3.2.1",
"style-loader": "0.23.0",
"ts-loader": "9.2.4",
"typedoc": "0.26.2",
"typescript": "4.9.5",
"typedoc": "0.22.15",
"typescript": "4.3.5",
"url-loader": "4.1.1",
"wait-on": "4.0.2",
"webpack": "5.88.2",
@@ -247,4 +245,4 @@
"printWidth": 120,
"endOfLine": "auto"
}
}
}

View File

@@ -1,13 +0,0 @@
diff --git a/node_modules/@phosphor/virtualdom/lib/index.d.ts b/node_modules/@phosphor/virtualdom/lib/index.d.ts
index 95682b9..73e2daa 100644
--- a/node_modules/@phosphor/virtualdom/lib/index.d.ts
+++ b/node_modules/@phosphor/virtualdom/lib/index.d.ts
@@ -58,7 +58,7 @@ export declare type ElementEventMap = {
ondrop: DragEvent;
ondurationchange: Event;
onemptied: Event;
- onended: MediaStreamErrorEvent;
+ onended: ErrorEvent;
onerror: ErrorEvent;
onfocus: FocusEvent;
oninput: Event;

View File

@@ -0,0 +1,11 @@
diff --git a/node_modules/@uiw/react-split/cjs/index.d.ts b/node_modules/@uiw/react-split/cjs/index.d.ts
index 644bcc3..f794760 100644
--- a/node_modules/@uiw/react-split/cjs/index.d.ts
+++ b/node_modules/@uiw/react-split/cjs/index.d.ts
@@ -56,5 +56,5 @@ export default class Split extends React.Component<SplitProps, SplitState> {
onMouseDown(paneNumber: number, env: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
onDragging(env: Event): void;
onDragEnd(): void;
- render(): import("react/jsx-runtime").JSX.Element;
+ render(): JSX.Element;
}

View File

@@ -1,58 +1,51 @@
import { defineConfig, devices } from "@playwright/test";
import { defineConfig, devices } from '@playwright/test';
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "test",
testDir: 'test',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? "blob" : "html",
reporter: process.env.CI ? 'blob' : 'html',
timeout: 10 * 60 * 1000,
use: {
trace: "off",
video: "off",
screenshot: "on",
testIdAttribute: "data-test",
actionTimeout: 5 * 60 * 1000,
trace: 'off',
video: 'off',
screenshot: 'on',
testIdAttribute: 'data-test',
contextOptions: {
ignoreHTTPSErrors: true,
},
},
expect: {
// Many of our expectations take a little longer than the default 5 seconds.
timeout: 15 * 1000,
timeout: 5 * 60 * 1000,
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
/* Test against branded browsers. */
{
name: "Google Chrome",
use: { ...devices["Desktop Chrome"], channel: "chrome" }, // or 'chrome-beta'
},
{
name: "Microsoft Edge",
use: { ...devices["Desktop Edge"], channel: "msedge" }, // or 'msedge-dev'
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
webServer: {
command: "npm run start",
url: "https://127.0.0.1:1234/_ready",
command: 'npm run start',
url: 'https://127.0.0.1:1234/_ready',
timeout: 120 * 1000,
ignoreHTTPSErrors: true,
reuseExistingServer: !process.env.CI,

View File

@@ -0,0 +1,55 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { NormalizedEventKey } from "./Constants";
export interface CollapsedResourceTreeProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
}
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
}: CollapsedResourceTreeProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
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 (
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
<div className="main-nav nav">
<ul className="nav">
<li
className="resourceTreeCollapse"
id="collapseToggleLeftPaneButton"
role="button"
tabIndex={0}
aria-label={getApiShortDisplayName() + `Expand tree`}
onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded}
ref={focusButton}
>
<span className="leftarrowCollapsed">
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span>
<span className="collectionCollapsed">
<span>{getApiShortDisplayName()}</span>
</span>
</li>
</ul>
</div>
</div>
);
};

View File

@@ -133,9 +133,6 @@ export enum MongoBackendEndpointType {
export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings";
public static readonly AccountRestrictions: string = "AccountRestrictions";
public static readonly RuntimeProxy: string = "RuntimeProxy";
public static readonly DisallowedLocations: string = "DisallowedLocations";
}
export class PortalBackendEndpoints {
@@ -185,12 +182,6 @@ export class CassandraProxyAPIs {
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
}
export class AadEndpoints {
public static readonly Prod: string = "https://login.microsoftonline.com/";
public static readonly Fairfax: string = "https://login.microsoftonline.us/";
public static readonly Mooncake: string = "https://login.partner.microsoftonline.cn/";
}
export class Queries {
public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited";
@@ -205,12 +196,6 @@ export class Queries {
public static readonly DefaultMaxWaitTimeInSeconds = 30;
}
export class RBACOptions {
public static setAutomaticRBACOption: string = "Automatic";
public static setTrueRBACOption: string = "True";
public static setFalseRBACOption: string = "False";
}
export class SavedQueries {
public static readonly CollectionName: string = "___Query";
public static readonly DatabaseName: string = "___Cosmos";

View File

@@ -1,6 +1,47 @@
import { ResourceType } from "@azure/cosmos";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
describe("tokenProvider", () => {
const options = {
verb: "GET" as any,
path: "/",
resourceId: "",
resourceType: "dbs" as ResourceType,
headers: {},
getAuthorizationTokenUsingMasterKey: () => "",
};
beforeEach(() => {
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
});
window.fetch = jest.fn().mockImplementation(() => {
return {
json: () => "{}",
headers: new Map(),
};
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it("calls the auth token service if no master key is set", async () => {
await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(1);
});
it("does not call the auth service if a master key is set", async () => {
updateUserContext({
masterKey: "foo",
});
await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(0);
});
});
describe("getTokenFromAuthService", () => {
beforeEach(() => {

View File

@@ -3,10 +3,8 @@ import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizatio
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import { AuthType } from "../AuthType";
import { BackendApi, PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger";
import { PriorityLevel } from "../Common/Constants";
import { Platform, configContext } from "../ConfigContext";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
@@ -19,18 +17,7 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo;
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
Logger.logInfo(
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
"Explorer/tokenProvider",
);
if (!userContext.aadToken) {
logConsoleError(
`AAD token does not exist. Please click on "Login for Entra ID" button prior to performing Entra ID RBAC operations`,
);
return null;
}
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return authorizationToken;
@@ -84,15 +71,8 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
}
if (userContext.masterKey) {
Logger.logInfo(`Master Key exists`, "Explorer/tokenProvider");
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(
verb,
resourceId,
resourceType,
headers,
userContext.masterKey,
);
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
@@ -124,37 +104,6 @@ export async function getTokenFromAuthService(
verb: string,
resourceType: string,
resourceId?: string,
): Promise<AuthorizationToken> {
if (!useNewPortalBackendEndpoint(BackendApi.RuntimeProxy)) {
return getTokenFromAuthService_ToBeDeprecated(verb, resourceType, resourceId);
}
try {
const host: string = configContext.PORTAL_BACKEND_ENDPOINT;
const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-encrypted-auth-token": userContext.accessToken,
},
body: JSON.stringify({
verb,
resourceType,
resourceId,
}),
});
const result: AuthorizationToken = await response.json();
return result;
} catch (error) {
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
return Promise.reject(error);
}
}
export async function getTokenFromAuthService_ToBeDeprecated(
verb: string,
resourceType: string,
resourceId?: string,
): Promise<AuthorizationToken> {
try {
const host = configContext.BACKEND_ENDPOINT;
@@ -188,11 +137,8 @@ enum SDKSupportedCapabilities {
let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient {
if (_client) {
if (!userContext.hasDataPlaneRbacSettingChanged) {
return _client;
}
}
if (_client) return _client;
let _defaultHeaders: Cosmos.CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
@@ -211,7 +157,7 @@ export function client(): Cosmos.CosmosClient {
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey,
key: userContext.masterKey,
tokenProvider,
userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders,

View File

@@ -53,8 +53,7 @@ const replaceKnownError = (errorMessage: string): string => {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
} else if (
errorMessage?.indexOf("The user aborted a request") >= 0 ||
errorMessage?.indexOf("The operation was aborted") >= 0 ||
errorMessage === "signal is aborted without reason"
errorMessage?.indexOf("The operation was aborted") >= 0
) {
return "User aborted query.";
}

View File

@@ -1,7 +1,6 @@
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import Q from "q";
import * as _ from "underscore";
import * as Logger from "../Common/Logger";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
import * as Constants from "./Constants";
@@ -97,18 +96,10 @@ const _sendMessage = (message: any): void => {
const portalChildWindow = getDataExplorerWindow(window) || window;
if (portalChildWindow === window) {
// Current window is a child of portal, send message to portal window
if (portalChildWindow.document.referrer) {
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer);
} else {
Logger.logError("Iframe failed to send message to portal", "MessageHandler");
}
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
} else {
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
if (portalChildWindow.location.origin) {
portalChildWindow.postMessage(message, portalChildWindow.location.origin);
} else {
Logger.logError("Iframe failed to send message to data explorer", "MessageHandler");
}
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
}
}
};

View File

@@ -550,49 +550,6 @@ export function deleteDocument_ToBeDeprecated(
});
}
export function deleteDocuments(
databaseId: string,
collection: Collection,
documentIds: DocumentId[],
): Promise<{
deletedCount: number;
isAcknowledged: boolean;
}> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const rids = documentIds.map((documentId) => documentId.id());
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}`,
resourceIDs: rids,
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
};
const endpoint = getFeatureEndpointOrDefault("bulkdelete");
return window
.fetch(`${endpoint}/bulkdelete`, {
method: "DELETE",
body: JSON.stringify(params),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
},
})
.then(async (response) => {
if (response.ok) {
const result = await response.json();
return result;
}
return await errorHandling(response, "deleting documents", params);
});
}
export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams,
): Promise<DataModels.Collection> {
@@ -721,10 +678,17 @@ export function useMongoProxyEndpoint(api: string): boolean {
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
);

View File

@@ -1,212 +0,0 @@
import { getErrorMessage } from "Common/ErrorHandlingUtils";
import { monaco } from "Explorer/LazyMonaco";
export enum QueryErrorSeverity {
Error = "Error",
Warning = "Warning",
}
export class QueryErrorLocation {
constructor(
public start: ErrorPosition,
public end: ErrorPosition,
) {}
}
export class ErrorPosition {
constructor(
public offset: number,
public lineNumber?: number,
public column?: number,
) {}
}
// Maps severities to numbers for sorting.
const severityMap: Record<QueryErrorSeverity, number> = {
Error: 1,
Warning: 0,
};
export function compareSeverity(left: QueryErrorSeverity, right: QueryErrorSeverity): number {
return severityMap[left] - severityMap[right];
}
export function createMonacoErrorLocationResolver(
editor: monaco.editor.IStandaloneCodeEditor,
selection?: monaco.Selection,
): (location: { start: number; end: number }) => QueryErrorLocation {
return ({ start, end }) => {
// Start and end are absolute offsets (character index) in the document.
// But we need line numbers and columns for the monaco editor.
// To get those, we use the editor's model to convert the offsets to positions.
const model = editor.getModel();
if (!model) {
return new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end));
}
// If the error was found in a selection, adjust the start and end positions to be relative to the document.
if (selection) {
// Get the character index of the start of the selection.
const selectionStartOffset = model.getOffsetAt(selection.getStartPosition());
// Adjust the start and end positions to be relative to the document.
start = selectionStartOffset + start;
end = selectionStartOffset + end;
// Now, when we resolve the positions, they will be relative to the document and appear in the correct location.
}
const startPos = model.getPositionAt(start);
const endPos = model.getPositionAt(end);
return new QueryErrorLocation(
new ErrorPosition(start, startPos.lineNumber, startPos.column),
new ErrorPosition(end, endPos.lineNumber, endPos.column),
);
};
}
export const createMonacoMarkersForQueryErrors = (errors: QueryError[]) => {
if (!errors) {
return [];
}
return errors
.map((error): monaco.editor.IMarkerData => {
// Validate that we have what we need to make a marker
if (
error.location === undefined ||
error.location.start === undefined ||
error.location.end === undefined ||
error.location.start.lineNumber === undefined ||
error.location.end.lineNumber === undefined ||
error.location.start.column === undefined ||
error.location.end.column === undefined
) {
return null;
}
return {
message: error.message,
severity: error.getMonacoSeverity(),
startLineNumber: error.location.start.lineNumber,
startColumn: error.location.start.column,
endLineNumber: error.location.end.lineNumber,
endColumn: error.location.end.column,
};
})
.filter((marker) => !!marker);
};
export default class QueryError {
constructor(
public message: string,
public severity: QueryErrorSeverity,
public code?: string,
public location?: QueryErrorLocation,
) {}
getMonacoSeverity(): monaco.MarkerSeverity {
// It's very difficult to use the monaco.MarkerSeverity enum from here, so we'll just use the numbers directly.
// See: https://microsoft.github.io/monaco-editor/typedoc/enums/MarkerSeverity.html
switch (this.severity) {
case QueryErrorSeverity.Error:
return 8;
case QueryErrorSeverity.Warning:
return 4;
default:
return 2; // Info
}
}
/** Attempts to parse a query error from a string or object.
*
* @param error The error to parse.
* @returns An array of query errors if the error could be parsed, or null otherwise.
*/
static tryParse(
error: unknown,
locationResolver?: (location: { start: number; end: number }) => QueryErrorLocation,
): QueryError[] {
locationResolver =
locationResolver ||
(({ start, end }) => new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end)));
const errors = QueryError.tryParseObject(error, locationResolver);
if (errors !== null) {
return errors;
}
const errorMessage = getErrorMessage(error as string | Error);
// Map some well known messages to richer errors
const knownError = knownErrors[errorMessage];
if (knownError) {
return [knownError];
} else {
return [new QueryError(errorMessage, QueryErrorSeverity.Error)];
}
}
static read(
error: unknown,
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
): QueryError | null {
if (typeof error !== "object" || error === null) {
return null;
}
const message = "message" in error && typeof error.message === "string" ? error.message : undefined;
if (!message) {
return null; // Invalid error (no message).
}
const severity =
"severity" in error && typeof error.severity === "string" ? (error.severity as QueryErrorSeverity) : undefined;
const location =
"location" in error && typeof error.location === "object"
? locationResolver(error.location as { start: number; end: number })
: undefined;
const code = "code" in error && typeof error.code === "string" ? error.code : undefined;
return new QueryError(message, severity, code, location);
}
private static tryParseObject(
error: unknown,
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
): QueryError[] | null {
if (typeof error === "object" && "message" in error) {
error = error.message;
}
if (typeof error !== "string") {
return null;
}
// Assign to a new variable because of a TypeScript flow typing quirk, see below.
let message = error;
if (message.startsWith("Message: ")) {
// Reassigning this to 'error' restores the original type of 'error', which is 'unknown'.
// So we use a separate variable to avoid this.
message = message.substring("Message: ".length);
}
const lines = message.split("\n");
message = lines[0].trim();
let parsed: unknown;
try {
parsed = JSON.parse(message);
} catch (e) {
// Not a query error.
return null;
}
if (typeof parsed === "object" && "errors" in parsed && Array.isArray(parsed.errors)) {
return parsed.errors.map((e) => QueryError.read(e, locationResolver)).filter((e) => e !== null);
}
return null;
}
}
const knownErrors: Record<string, QueryError> = {
"User aborted query.": new QueryError("User aborted query.", QueryErrorSeverity.Warning),
};

View File

@@ -0,0 +1,82 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg";
import Explorer from "../Explorer/Explorer";
import { ResourceTree } from "../Explorer/Tree/ResourceTree";
import { userContext } from "../UserContext";
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { NormalizedEventKey } from "./Constants";
export interface ResourceTreeContainerProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
container: Explorer;
}
export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
container,
}: ResourceTreeContainerProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
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 (
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
{/* Collections Window - - Start */}
<div id="mainslide" className="flexContainer">
{/* Collections Window Title/Command Bar - Start */}
<div className="collectiontitle">
<div className="coltitle">
<span className="titlepadcol">{getApiShortDisplayName()}</span>
<div className="float-right">
<span
className="padimgcolrefresh"
data-test="refreshTree"
role="button"
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabIndex={0}
aria-label={getApiShortDisplayName() + `Refresh tree`}
title="Refresh tree"
>
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
</span>
<span
className="padimgcolrefresh1"
id="expandToggleLeftPaneButton"
role="button"
onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded}
tabIndex={0}
aria-label={getApiShortDisplayName() + `Collapse Tree`}
title="Collapse Tree"
ref={focusButton}
>
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
</span>
</div>
</div>
</div>
{userContext.features.enableKoResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : (
<ResourceTree container={container} />
)}
</div>
{/* Collections Window - End */}
</div>
);
};

View File

@@ -3,12 +3,11 @@ import * as React from "react";
export interface TooltipProps {
children: string;
className?: string;
}
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => {
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
return (
<span className={className}>
<span>
<TooltipHost content={children}>
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
</TooltipHost>

View File

@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
{
Object {
"endpoint": "http://localhost/proxy",
"headers": {
"headers": Object {
"x-ms-proxy-target": "http://localhost",
},
"path": "/dbs/foo",
@@ -11,9 +11,9 @@ exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`]
`;
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
{
Object {
"endpoint": "http://localhost/proxy",
"headers": {
"headers": Object {
"x-ms-proxy-target": "baz",
},
"path": "/dbs/foo",

View File

@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`nextPage returns results for the next page 1`] = `
{
Object {
"activityId": "foo",
"documents": [],
"documents": Array [],
"firstItemIndex": 11,
"hasMoreResults": false,
"headers": {},
"headers": Object {},
"itemCount": 0,
"lastItemIndex": 10,
"requestCharge": 1,

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getCommonQueryOptions builds the correct default options objects 1`] = `
{
Object {
"disableNonStreamingOrderByQuery": true,
"enableScanInQuery": true,
"forceQueryPlan": true,
@@ -12,7 +12,7 @@ exports[`getCommonQueryOptions builds the correct default options objects 1`] =
`;
exports[`getCommonQueryOptions reads from localStorage 1`] = `
{
Object {
"disableNonStreamingOrderByQuery": true,
"enableScanInQuery": true,
"forceQueryPlan": true,

View File

@@ -269,7 +269,6 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
indexingPolicy: params.indexingPolicy || undefined,
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
analyticalStorageTtl: params.analyticalStorageTtl,
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
const collectionOptions: RequestOptions = {};
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };

View File

@@ -4,7 +4,6 @@ import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { userContext } from "../../UserContext";
import { getDatabaseName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
@@ -16,6 +15,7 @@ import {
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
@@ -152,18 +152,8 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
createBody.throughput = params.offerThroughput;
}
}
let response: DatabaseResponse;
try {
response = await client().databases.create(createBody);
} catch (error) {
if (error.message.includes("Shared throughput database creation is not supported for serverless accounts")) {
createBody.maxThroughput = undefined;
createBody.throughput = undefined;
response = await client().databases.create(createBody);
} else {
throw error;
}
}
const response: DatabaseResponse = await client().databases.create(createBody);
return response.resource;
}

View File

@@ -2,6 +2,7 @@ import { CosmosClient } from "@azure/cosmos";
import { sampleDataClient } from "Common/SampleDataClient";
import { userContext } from "UserContext";
import * as DataModels from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
@@ -30,6 +31,7 @@ export async function readCollectionInternal(
collectionId: string,
): Promise<DataModels.Collection> {
let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
try {
const response = await cosmosClient.database(databaseId).container(collectionId).read();
collection = response.resource as DataModels.Collection;
@@ -37,5 +39,6 @@ export async function readCollectionInternal(
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
throw error;
}
clearMessage();
return collection;
}

View File

@@ -53,8 +53,10 @@ export interface ConfigContext {
NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT?: string;
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string;
JUNO_ENDPOINT: string;
@@ -85,7 +87,7 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/.*\\.analysis-df\\.net$`,
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
`^https:\\/\\/.*\\.azure-test\\.net$`,
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net$`,
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`,
], // Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
@@ -115,10 +117,11 @@ let configContext: Readonly<ConfigContext> = {
"deleteDocument",
"createCollectionWithProxy",
"legacyMongoShell",
"bulkdelete",
],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false,
isPhoenixEnabled: false,
};

View File

@@ -1,8 +1,7 @@
export interface QueryRequestOptions {
$skipToken?: string;
$top?: number;
$allowPartialScopes: boolean;
subscriptions?: string[];
subscriptions: string[];
}
export interface QueryResponse {

View File

@@ -36,21 +36,21 @@ describe("The Heatmap Control", () => {
});
it("should call _getChartSettings when drawHeatmap is invoked", () => {
const _getChartSettings = jest.spyOn(heatmap, "_getChartSettings");
const _getChartSettings = spyOn<any>(heatmap, "_getChartSettings");
heatmap.drawHeatmap();
expect(_getChartSettings).toHaveBeenCalled();
expect(_getChartSettings.calls.any()).toBe(true);
});
it("should call _getLayoutSettings when drawHeatmap is invoked", () => {
const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings");
const _getLayoutSettings = spyOn<any>(heatmap, "_getLayoutSettings");
heatmap.drawHeatmap();
expect(_getLayoutSettings).toHaveBeenCalled();
expect(_getLayoutSettings.calls.any()).toBe(true);
});
it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => {
const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings");
const _getChartDisplaySettings = spyOn<any>(heatmap, "_getChartDisplaySettings");
heatmap.drawHeatmap();
expect(_getChartDisplaySettings).toHaveBeenCalled();
expect(_getChartDisplaySettings.calls.any()).toBe(true);
});
it("drawHeatmap should render a Heatmap inside the div element", () => {

View File

@@ -96,8 +96,7 @@ export class Heatmap {
return output;
}
// public for testing purposes
public _getChartSettings(): ChartSettings[] {
private _getChartSettings(): ChartSettings[] {
return [
{
z: this._chartData.dataPoints,
@@ -132,8 +131,7 @@ export class Heatmap {
];
}
// public for testing purposes
public _getLayoutSettings(): LayoutSettings {
private _getLayoutSettings(): LayoutSettings {
return {
margin: {
l: 40,
@@ -179,8 +177,7 @@ export class Heatmap {
};
}
// public for testing purposes
public _getChartDisplaySettings(): DisplaySettings {
private _getChartDisplaySettings(): DisplaySettings {
return {
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
responsive: true,*/

View File

@@ -41,10 +41,6 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS)
*/
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
return undefined;
}
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
@@ -104,16 +100,6 @@ export const createCollectionContextMenuButton = (
});
}
if (useNotebook.getState().isShellEnabled && userContext.apiType === "Cassandra") {
items.push({
iconSrc: HostedTerminalIcon,
onClick: () => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
},
label: "Open Cassandra Shell",
});
}
if (
configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")

View File

@@ -11,7 +11,7 @@ exports[`CollapsibleSectionComponent renders 1`] = `
role="button"
tabIndex={0}
tokens={
{
Object {
"childrenGap": 10,
}
}

View File

@@ -3,37 +3,6 @@ import * as React from "react";
import { loadMonaco, monaco } from "../../LazyMonaco";
// import "./EditorReact.less";
// In development, add a function to window to allow us to get the editor instance for a given element
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any;
win._monaco_getEditorForElement =
win._monaco_getEditorForElement ||
((element: HTMLElement) => {
const editorId = element.dataset["monacoEditorId"];
if (!editorId || !win.__monaco_editors || typeof win.__monaco_editors !== "object") {
return null;
}
return win.__monaco_editors[editorId];
});
win._monaco_getEditorContentForElement =
win._monaco_getEditorContentForElement ||
((element: HTMLElement) => {
const editor = win._monaco_getEditorForElement(element);
return editor ? editor.getValue() : null;
});
win._monaco_setEditorContentForElement =
win._monaco_setEditorContentForElement ||
((element: HTMLElement, text: string) => {
const editor = win._monaco_getEditorForElement(element);
if (editor) {
editor.setValue(text);
}
});
}
interface EditorReactStates {
showEditor: boolean;
}
@@ -42,7 +11,7 @@ export interface EditorReactProps {
content: string;
isReadOnly: boolean;
ariaLabel: string; // Sets what will be read to the user to define the control
onContentSelected?: (selectedContent: string, selection: monaco.Selection) => void; // Called when text is selected
onContentSelected?: (selectedContent: string) => void; // Called when text is selected
onContentChanged?: (newContent: string) => void; // Called when text is changed
theme?: string; // Monaco editor theme
wordWrap?: monaco.editor.IEditorOptions["wordWrap"];
@@ -55,34 +24,12 @@ export interface EditorReactProps {
monacoContainerStyles?: React.CSSProperties;
className?: string;
spinnerClassName?: string;
modelMarkers?: monaco.editor.IMarkerData[];
enableWordWrapContextMenuItem?: boolean; // Enable/Disable "Word Wrap" context menu item
onWordWrapChanged?: (wordWrap: "on" | "off") => void; // Called when word wrap is changed
}
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
private static readonly VIEWING_OPTIONS_GROUP_ID = "viewingoptions"; // Group ID for the context menu group
private rootNode: HTMLElement;
public editor: monaco.editor.IStandaloneCodeEditor;
private editor: monaco.editor.IStandaloneCodeEditor;
private selectionListener: monaco.IDisposable;
monacoApi: {
default: typeof monaco;
Emitter: typeof monaco.Emitter;
MarkerTag: typeof monaco.MarkerTag;
MarkerSeverity: typeof monaco.MarkerSeverity;
CancellationTokenSource: typeof monaco.CancellationTokenSource;
Uri: typeof monaco.Uri;
KeyCode: typeof monaco.KeyCode;
KeyMod: typeof monaco.KeyMod;
Position: typeof monaco.Position;
Range: typeof monaco.Range;
Selection: typeof monaco.Selection;
SelectionDirection: typeof monaco.SelectionDirection;
Token: typeof monaco.Token;
editor: typeof monaco.editor;
languages: typeof monaco.languages;
};
public constructor(props: EditorReactProps) {
super(props);
@@ -111,7 +58,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
if (this.props.content !== existingContent) {
if (this.props.isReadOnly) {
this.editor.setValue(this.props.content || ""); // Monaco throws an error if you set the value to undefined.
this.editor.setValue(this.props.content);
} else {
this.editor.pushUndoStop();
this.editor.executeEdits("", [
@@ -122,8 +69,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
]);
}
}
this.monacoApi.editor.setModelMarkers(this.editor.getModel(), "owner", this.props.modelMarkers || []);
}
public componentWillUnmount(): void {
@@ -137,7 +82,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
)}
<div
data-test="EditorReact/Host/Unloaded"
className={this.props.className || "jsonEditor"}
style={this.props.monacoContainerStyles}
ref={(elt: HTMLElement) => this.setRef(elt)}
@@ -148,18 +92,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor;
this.rootNode.dataset["test"] = "EditorReact/Host/Loaded";
// In development, we want to be able to access the editor instance from the console
if (process.env.NODE_ENV === "development") {
this.rootNode.dataset["monacoEditorId"] = this.editor.getId();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any;
win["__monaco_editors"] = win["__monaco_editors"] || {};
win["__monaco_editors"][this.editor.getId()] = this.editor;
}
if (!this.props.isReadOnly && this.props.onContentChanged) {
// Hooking the model's onDidChangeContent event because of some event ordering issues.
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
@@ -177,27 +109,10 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.selectionListener = this.editor.onDidChangeCursorSelection(
(event: monaco.editor.ICursorSelectionChangedEvent) => {
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection);
this.props.onContentSelected(selectedContent, event.selection);
this.props.onContentSelected(selectedContent);
},
);
}
if (this.props.enableWordWrapContextMenuItem) {
editor.addAction({
// An unique identifier of the contributed action.
id: "wordwrap",
label: "Toggle Word Wrap",
contextMenuGroupId: EditorReact.VIEWING_OPTIONS_GROUP_ID,
contextMenuOrder: 1,
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: (ed) => {
const newOption = ed.getOption(this.monacoApi.editor.EditorOption.wordWrap) === "on" ? "off" : "on";
ed.updateOptions({ wordWrap: newOption });
this.props.onWordWrapChanged(newOption);
},
});
}
}
/**
@@ -218,14 +133,12 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
lineDecorationsWidth: this.props.lineDecorationsWidth,
minimap: this.props.minimap,
scrollBeyondLastLine: this.props.scrollBeyondLastLine,
fixedOverflowWidgets: true,
};
this.rootNode.innerHTML = "";
this.monacoApi = await loadMonaco();
const monaco = await loadMonaco();
try {
createCallback(this.monacoApi.editor.create(this.rootNode, options));
createCallback(monaco?.editor?.create(this.rootNode, options));
} catch (error) {
// This could happen if the parent node suddenly disappears during create()
console.error("Unable to create EditorReact", error);

View File

@@ -18,7 +18,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack
className="options"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -27,7 +27,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true}
horizontalAlign="space-between"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -52,7 +52,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true}
horizontalAlign="start"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -61,16 +61,16 @@ exports[`Feature panel renders all flags 1`] = `
label="Base Url"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "https://localhost:1234/explorer.html",
"text": "localhost:1234",
},
{
Object {
"key": "https://cosmos.azure.com/explorer.html",
"text": "cosmos.azure.com",
},
{
Object {
"key": "https://portal.azure.com",
"text": "portal",
},
@@ -78,8 +78,8 @@ exports[`Feature panel renders all flags 1`] = `
}
selectedKey="https://localhost:1234/explorer.html"
styles={
{
"dropdown": {
Object {
"dropdown": Object {
"width": 200,
},
}
@@ -89,20 +89,20 @@ exports[`Feature panel renders all flags 1`] = `
label="Platform"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "Hosted",
"text": "Hosted",
},
{
Object {
"key": "Portal",
"text": "Portal",
},
{
Object {
"key": "Emulator",
"text": "Emulator",
},
{
Object {
"key": "",
"text": "None",
},
@@ -110,8 +110,8 @@ exports[`Feature panel renders all flags 1`] = `
}
selectedKey="Hosted"
styles={
{
"dropdown": {
Object {
"dropdown": Object {
"width": 200,
},
}
@@ -208,7 +208,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -222,8 +222,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder="https://notebookserver"
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -235,8 +235,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder=""
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -248,8 +248,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder=""
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -265,8 +265,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder=""
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -279,8 +279,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder="https://localhost:1234/explorer.html"
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -292,8 +292,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]}
placeholder=""
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}

View File

@@ -1,37 +0,0 @@
import { ProgressBar, makeStyles } from "@fluentui/react-components";
import React from "react";
const useStyles = makeStyles({
indeterminateProgressBarRoot: {
"@media screen and (prefers-reduced-motion: reduce)": {
animationIterationCount: "infinite",
animationDuration: "3s",
animationName: {
"0%": {
opacity: ".2", // matches indeterminate bar width
},
"50%": {
opacity: "1",
},
"100%": {
opacity: ".2",
},
},
},
},
indeterminateProgressBarBar: {
"@media screen and (prefers-reduced-motion: reduce)": {
maxWidth: "100%",
},
},
});
export const IndeterminateProgressBar: React.FC = () => {
const styles = useStyles();
return (
<ProgressBar
bar={{ className: styles.indeterminateProgressBarBar }}
className={styles.indeterminateProgressBarRoot}
/>
);
};

View File

@@ -1,68 +0,0 @@
import { Button, MessageBar, MessageBarActions, MessageBarBody } from "@fluentui/react-components";
import { DismissRegular } from "@fluentui/react-icons";
import React, { useState } from "react";
export enum MessageBannerState {
/** The banner should be visible if the triggering conditions are met. */
Allowed = "allowed",
/** The banner has been dismissed by the user and will not be shown until the component is recreated, even if the visibility condition is true. */
Dismissed = "dismissed",
/** The banner has been supressed by the user and will not be shown at all, even if the visibility condition is true. */
Suppressed = "suppressed",
}
export type MessageBannerProps = {
/** A CSS class for the root MessageBar component */
className: string;
/** A unique ID for the message that will be used to store it's dismiss/suppress state across sessions. */
messageId: string;
/** The current visibility state for the banner IGNORING the user's dimiss/suppress preference
*
* If this value is true but the user has dismissed the banner, the banner will NOT be shown.
*/
visible: boolean;
};
/** A component that shows a message banner which can be dismissed by the user.
*
* In the future, this can also support persisting the dismissed state in local storage without requiring changes to all the components that use it.
*
* A message banner can be in three "states":
* - Allowed: The banner should be visible if the triggering conditions are met.
* - Dismissed: The banner has been dismissed by the user and will not be shown until the component is recreated, even if the visibility condition is true.
* - Suppressed: The banner has been supressed by the user and will not be shown at all, even if the visibility condition is true.
*
* The "Dismissed" state represents the user clicking the "x" in the banner to dismiss it.
* The "Suppressed" state represents the user clicking "Don't show this again".
*/
export const MessageBanner: React.FC<MessageBannerProps> = ({ visible, className, children }) => {
const [state, setState] = useState<MessageBannerState>(MessageBannerState.Allowed);
if (state !== MessageBannerState.Allowed) {
return null;
}
if (!visible) {
return null;
}
return (
<MessageBar className={className}>
<MessageBarBody>{children}</MessageBarBody>
<MessageBarActions
containerAction={
<Button
aria-label="dismiss"
appearance="transparent"
icon={<DismissRegular />}
onClick={() => setState(MessageBannerState.Dismissed)}
/>
}
></MessageBarActions>
</MessageBar>
);
};

View File

@@ -4,8 +4,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardBase
aria-label="name"
styles={
{
"root": {
Object {
"root": Object {
"display": "inline-block",
"marginRight": 20,
"width": 256,
@@ -16,8 +16,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardActivityBase
activity="Invalid Date"
people={
[
{
Array [
Object {
"name": "author",
"profileImageSrc": false,
},
@@ -26,8 +26,8 @@ exports[`GalleryCardComponent renders 1`] = `
/>
<StyledDocumentCardPreviewBase
previewImages={
[
{
Array [
Object {
"height": 144,
"imageFit": 2,
"previewImageSrc": "thumbnailUrl",
@@ -40,8 +40,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Text
nowrap={true}
styles={
{
"root": {
Object {
"root": Object {
"height": 18,
"padding": "2px 16px",
},
@@ -69,15 +69,15 @@ exports[`GalleryCardComponent renders 1`] = `
/>
<span
style={
{
Object {
"padding": "8px 16px",
}
}
>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "#605E5C",
"paddingRight": 8,
},
@@ -88,8 +88,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon
iconName="RedEye"
styles={
{
"root": {
Object {
"root": Object {
"verticalAlign": "middle",
},
}
@@ -100,8 +100,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "#605E5C",
"paddingRight": 8,
},
@@ -112,8 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon
iconName="Download"
styles={
{
"root": {
Object {
"root": Object {
"verticalAlign": "middle",
},
}
@@ -124,8 +124,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "#605E5C",
"paddingRight": 8,
},
@@ -136,8 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon
iconName="Heart"
styles={
{
"root": {
Object {
"root": Object {
"verticalAlign": "middle",
},
}
@@ -151,8 +151,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardDetailsBase>
<Separator
styles={
{
"root": {
Object {
"root": Object {
"height": 1,
"padding": 0,
},
@@ -161,22 +161,22 @@ exports[`GalleryCardComponent renders 1`] = `
/>
<span
style={
{
Object {
"padding": "0px 16px",
}
}
>
<StyledTooltipHostBase
calloutProps={
{
Object {
"gapSpace": 0,
}
}
content="Favorite"
id="TooltipHost-IconButton-Heart"
styles={
{
"root": {
Object {
"root": Object {
"display": "inline-block",
"float": "left",
},
@@ -186,7 +186,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton
ariaLabel="Favorite"
iconProps={
{
Object {
"iconName": "Heart",
}
}
@@ -196,15 +196,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
{
Object {
"gapSpace": 0,
}
}
content="Download"
id="TooltipHost-IconButton-Download"
styles={
{
"root": {
Object {
"root": Object {
"display": "inline-block",
"float": "left",
},
@@ -214,7 +214,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton
ariaLabel="Download"
iconProps={
{
Object {
"iconName": "Download",
}
}
@@ -224,15 +224,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
{
Object {
"gapSpace": 0,
}
}
content="Remove"
id="TooltipHost-IconButton-Delete"
styles={
{
"root": {
Object {
"root": Object {
"display": "inline-block",
"float": "right",
},
@@ -242,7 +242,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton
ariaLabel="Remove"
iconProps={
{
Object {
"iconName": "Delete",
}
}

View File

@@ -3,7 +3,7 @@
exports[`CodeOfConduct renders 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -11,7 +11,7 @@ exports[`CodeOfConduct renders 1`] = `
<StackItem>
<Text
style={
{
Object {
"fontSize": "20px",
"fontWeight": 500,
}
@@ -41,12 +41,12 @@ exports[`CodeOfConduct renders 1`] = `
label="I have read and accept the code of conduct."
onChange={[Function]}
styles={
{
"label": {
Object {
"label": Object {
"margin": 0,
"padding": "2 0 2 0",
},
"text": {
"text": Object {
"fontSize": 12,
},
}

View File

@@ -4,7 +4,7 @@ exports[`InfoComponent renders 1`] = `
<StyledHoverCardBase
instantOpenOnClick={true}
plainCardProps={
{
Object {
"onRenderPlainCard": [Function],
}
}
@@ -18,8 +18,8 @@ exports[`InfoComponent renders 1`] = `
className="infoIconMain"
iconName="Help"
styles={
{
"root": {
Object {
"root": Object {
"verticalAlign": "middle",
},
}

View File

@@ -13,14 +13,14 @@ exports[`GalleryViewerComponent renders 1`] = `
itemKey="OfficialSamples"
key="OfficialSamples"
style={
{
Object {
"marginTop": 20,
}
}
>
<Stack
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -28,7 +28,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 20,
"padding": 10,
}
@@ -50,8 +50,8 @@ exports[`GalleryViewerComponent renders 1`] = `
</StackItem>
<StackItem
styles={
{
"root": {
Object {
"root": Object {
"minWidth": 200,
},
}
@@ -60,20 +60,20 @@ exports[`GalleryViewerComponent renders 1`] = `
<Dropdown
onChange={[Function]}
options={
[
{
Array [
Object {
"key": 0,
"text": "Most viewed",
},
{
Object {
"key": 1,
"text": "Most downloaded",
},
{
Object {
"key": 3,
"text": "Most recent",
},
{
Object {
"key": 2,
"text": "Most favorited",
},

View File

@@ -3,7 +3,7 @@
exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -11,7 +11,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 30,
}
}
@@ -29,7 +29,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Text>
<CustomizedIconButton
iconProps={
{
Object {
"iconName": "HeartFill",
}
}
@@ -53,7 +53,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -96,8 +96,8 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"fontWeight": 600,
},
}
@@ -115,7 +115,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -123,7 +123,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 30,
}
}
@@ -141,7 +141,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Text>
<CustomizedIconButton
iconProps={
{
Object {
"iconName": "Heart",
}
}
@@ -165,7 +165,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -208,8 +208,8 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"fontWeight": 600,
},
}

View File

@@ -6,8 +6,8 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},

View File

@@ -3,7 +3,7 @@
exports[`AddMongoIndexComponent renders 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 5,
}
}
@@ -11,7 +11,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -21,8 +21,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
componentRef={[Function]}
onChange={[Function]}
styles={
{
"root": {
Object {
"root": Object {
"paddingLeft": 10,
"width": 210,
},
@@ -34,12 +34,12 @@ exports[`AddMongoIndexComponent renders 1`] = `
ariaLabel="Index Type 1"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "Single",
"text": "Single Field",
},
{
Object {
"key": "Wildcard",
"text": "Wildcard",
},
@@ -48,8 +48,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
placeholder="Select an index type"
selectedKey="Single"
styles={
{
"dropdown": {
Object {
"dropdown": Object {
"paddingleft": 10,
"width": 202,
},
@@ -60,7 +60,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
ariaLabel="Undo Button 1"
disabled={false}
iconProps={
{
Object {
"iconName": "Undo",
}
}
@@ -70,8 +70,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
<StyledMessageBar
messageBarType={1}
styles={
{
"root": {
Object {
"root": Object {
"marginLeft": 10,
},
}

View File

@@ -9,7 +9,7 @@ exports[`MongoIndexingPolicyComponent error shown for collection with compound i
exports[`MongoIndexingPolicyComponent renders 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -29,14 +29,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Text>
<Stack
styles={
{
"root": {
Object {
"root": Object {
"width": 600,
},
}
}
tokens={
{
Object {
"childrenGap": 5,
}
}
@@ -47,8 +47,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
>
<StyledWithViewportComponent
columns={
[
{
Array [
Object {
"fieldName": "definition",
"isResizable": true,
"key": "definition",
@@ -56,7 +56,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
"minWidth": 100,
"name": "Definition",
},
{
Object {
"fieldName": "type",
"isResizable": true,
"key": "type",
@@ -64,7 +64,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
"minWidth": 100,
"name": "Type",
},
{
Object {
"fieldName": "actionButton",
"isResizable": true,
"key": "actionButton",
@@ -75,15 +75,15 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
]
}
disableSelectionZone={true}
items={[]}
items={Array []}
layoutMode={1}
onRenderRow={[Function]}
selectionMode={0}
styles={
{
"root": {
"selectors": {
".ms-FocusZone": {
Object {
"root": Object {
"selectors": Object {
".ms-FocusZone": Object {
"paddingTop": 0,
},
},
@@ -93,14 +93,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
/>
<Stack
styles={
{
"root": {
Object {
"root": Object {
"width": 600,
},
}
}
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -117,11 +117,11 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Stack>
<Separator
styles={
{
"root": [
{
"selectors": {
"::before": {
Object {
"root": Array [
Object {
"selectors": Object {
"::before": Object {
"background": undefined,
},
},
@@ -132,8 +132,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
/>
<Stack
styles={
{
"root": {
Object {
"root": Object {
"width": 600,
},
}

View File

@@ -47,7 +47,7 @@ describe("ScaleComponent", () => {
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(`${targetThroughput}`);
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
const newCollection = { ...collection };
const maxThroughput = 5000;
@@ -66,7 +66,7 @@ describe("ScaleComponent", () => {
wrapper = shallow(<ScaleComponent {...newProps} />);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(`${maxThroughput}`);
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput);
});
it("autoScale disabled", () => {

View File

@@ -3,14 +3,14 @@
exports[`ComputedPropertiesComponent renders 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 5,
}
}
>
<Text
style={
{
Object {
"marginBottom": "10px",
"marginLeft": "30px",
}

View File

@@ -3,7 +3,7 @@
exports[`ConflictResolutionComponent Path text field displayed 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -12,12 +12,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
label="Mode"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "LastWriterWins",
"text": "Last Write Wins (default)",
},
{
Object {
"key": "Custom",
"text": "Merge Procedure (custom)",
},
@@ -25,19 +25,19 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
}
selectedKey="LastWriterWins"
styles={
{
"flexContainer": [
{
Object {
"flexContainer": Array [
Object {
"columnGap": "default",
"display": "default",
"selectors": {
".ms-ChoiceField-field.is-checked::after": {
"selectors": Object {
".ms-ChoiceField-field.is-checked::after": Object {
"borderColor": undefined,
},
".ms-ChoiceField-field.is-checked::before": {
".ms-ChoiceField-field.is-checked::before": Object {
"borderColor": undefined,
},
".ms-ChoiceField-wrapper label": {
".ms-ChoiceField-wrapper label": Object {
"fontFamily": undefined,
"fontSize": 14,
"padding": "2px 5px",
@@ -55,12 +55,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
onChange={[Function]}
onRenderLabel={[Function]}
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},
@@ -77,7 +77,7 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -86,12 +86,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
label="Mode"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "LastWriterWins",
"text": "Last Write Wins (default)",
},
{
Object {
"key": "Custom",
"text": "Merge Procedure (custom)",
},
@@ -99,19 +99,19 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
}
selectedKey="Custom"
styles={
{
"flexContainer": [
{
Object {
"flexContainer": Array [
Object {
"columnGap": "default",
"display": "default",
"selectors": {
".ms-ChoiceField-field.is-checked::after": {
"selectors": Object {
".ms-ChoiceField-field.is-checked::after": Object {
"borderColor": "",
},
".ms-ChoiceField-field.is-checked::before": {
".ms-ChoiceField-field.is-checked::before": Object {
"borderColor": "",
},
".ms-ChoiceField-wrapper label": {
".ms-ChoiceField-wrapper label": Object {
"fontFamily": undefined,
"fontSize": 14,
"padding": "2px 5px",
@@ -129,12 +129,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
onChange={[Function]}
onRenderLabel={[Function]}
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"selectors": Object {
":disabled": Object {
"backgroundColor": undefined,
"borderColor": undefined,
},

View File

@@ -3,7 +3,7 @@
exports[`IndexingPolicyComponent renders 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 5,
}
}

View File

@@ -3,7 +3,7 @@
exports[`ScaleComponent renders with correct initial notification 1`] = `
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -14,8 +14,8 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
<Text
id="throughputApplyLongDelayMessage"
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -30,7 +30,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
</StyledMessageBar>
<Stack
tokens={
{
Object {
"childrenGap": 20,
}
}

View File

@@ -5,7 +5,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 6,
}
}
@@ -13,7 +13,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
>
<Text
style={
{
Object {
"fontWeight": 600,
}
}
@@ -22,7 +22,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
</Text>
<StyledTooltipHostBase
calloutProps={
{
Object {
"gapSpace": 0,
}
}
@@ -33,8 +33,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
}
directionalHint={12}
styles={
{
"root": {
Object {
"root": Object {
"display": "inline-block",
"float": "right",
},
@@ -45,8 +45,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
ariaLabel="Info"
iconName="Info"
styles={
{
"root": {
Object {
"root": Object {
"marginBottom": -3,
},
}

View File

@@ -16,14 +16,14 @@ exports[`SettingsComponent renders 1`] = `
itemKey="ScaleTab"
key="ScaleTab"
style={
{
Object {
"marginTop": 20,
}
}
>
<ScaleComponent
collection={
{
Object {
"analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function],
"computedProperties": [Function],
@@ -36,7 +36,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": {
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
@@ -60,16 +60,16 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function],
"indexingPolicy": [Function],
"offer": [Function],
"partitionKey": {
"partitionKey": Object {
"kind": "hash",
"paths": [],
"paths": Array [],
"version": 2,
},
"partitionKeyProperties": [
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": {},
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],
}
}
@@ -90,7 +90,7 @@ exports[`SettingsComponent renders 1`] = `
itemKey="SubSettingsTab"
key="SubSettingsTab"
style={
{
Object {
"marginTop": 20,
}
}
@@ -100,7 +100,7 @@ exports[`SettingsComponent renders 1`] = `
changeFeedPolicyBaseline="Off"
changeFeedPolicyVisible={false}
collection={
{
Object {
"analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function],
"computedProperties": [Function],
@@ -113,7 +113,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": {
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
@@ -137,16 +137,16 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function],
"indexingPolicy": [Function],
"offer": [Function],
"partitionKey": {
"partitionKey": Object {
"kind": "hash",
"paths": [],
"paths": Array [],
"version": 2,
},
"partitionKeyProperties": [
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": {},
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],
}
}
@@ -174,25 +174,25 @@ exports[`SettingsComponent renders 1`] = `
itemKey="IndexingPolicyTab"
key="IndexingPolicyTab"
style={
{
Object {
"marginTop": 20,
}
}
>
<IndexingPolicyComponent
indexingPolicyContent={
{
Object {
"automatic": true,
"excludedPaths": [],
"includedPaths": [],
"excludedPaths": Array [],
"includedPaths": Array [],
"indexingMode": "consistent",
}
}
indexingPolicyContentBaseline={
{
Object {
"automatic": true,
"excludedPaths": [],
"includedPaths": [],
"excludedPaths": Array [],
"includedPaths": Array [],
"indexingMode": "consistent",
}
}
@@ -210,14 +210,14 @@ exports[`SettingsComponent renders 1`] = `
itemKey="PartitionKeyTab"
key="PartitionKeyTab"
style={
{
Object {
"marginTop": 20,
}
}
>
<PartitionKeyComponent
collection={
{
Object {
"analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function],
"computedProperties": [Function],
@@ -230,7 +230,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": {
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
@@ -254,16 +254,16 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function],
"indexingPolicy": [Function],
"offer": [Function],
"partitionKey": {
"partitionKey": Object {
"kind": "hash",
"paths": [],
"paths": Array [],
"version": 2,
},
"partitionKeyProperties": [
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": {},
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],
}
}
@@ -276,7 +276,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": {
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
@@ -301,23 +301,23 @@ exports[`SettingsComponent renders 1`] = `
itemKey="ComputedPropertiesTab"
key="ComputedPropertiesTab"
style={
{
Object {
"marginTop": 20,
}
}
>
<ComputedPropertiesComponent
computedPropertiesContent={
[
{
Array [
Object {
"name": "queryName",
"query": "query",
},
]
}
computedPropertiesContentBaseline={
[
{
Array [
Object {
"name": "queryName",
"query": "query",
},

View File

@@ -5,7 +5,7 @@ exports[`SettingsUtils functions render 1`] = `
<Stack>
<Text
style={
{
Object {
"fontWeight": 600,
}
}
@@ -14,7 +14,7 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
style={
{
Object {
"fontWeight": 600,
"marginTop": 15,
}
@@ -25,7 +25,7 @@ exports[`SettingsUtils functions render 1`] = `
<Stack
id="throughputSpendElement"
style={
{
Object {
"marginTop": 5,
}
}
@@ -49,7 +49,7 @@ exports[`SettingsUtils functions render 1`] = `
</Stack>
<Text
style={
{
Object {
"marginTop": 15,
}
}
@@ -63,8 +63,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text
id="manualToAutoscaleDisclaimerElement"
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -81,8 +81,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -102,8 +102,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text
id="updateThroughputDelayedApplyWarningMessage"
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -114,8 +114,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -134,8 +134,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text
id="throughputApplyShortDelayMessage"
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -150,8 +150,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text
id="throughputApplyLongDelayMessage"
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -165,8 +165,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -179,8 +179,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -191,8 +191,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -211,8 +211,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -247,15 +247,15 @@ exports[`SettingsUtils functions render 1`] = `
<Stack
horizontal={true}
tokens={
{
Object {
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -270,8 +270,8 @@ exports[`SettingsUtils functions render 1`] = `
</Stack>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
@@ -287,8 +287,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text
styles={
{
"root": {
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},

View File

@@ -14,6 +14,7 @@ import {
TextField,
Toggle,
} from "@fluentui/react";
import { TFunction } from "i18next";
import * as React from "react";
import {
ChoiceItem,
@@ -99,7 +100,7 @@ export interface SmartUiComponentProps {
onInputChange: (input: AnyDisplay, newValue: InputType) => void;
onError: (hasError: boolean) => void;
disabled: boolean;
getTranslation: (messageKey: string, namespace?: string) => string;
getTranslation: TFunction;
}
interface SmartUiComponentState {

View File

@@ -4,7 +4,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -16,7 +16,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -28,7 +28,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
aria-labelledby="description-label"
id="description-text-display"
style={
{
Object {
"whiteSpace": "pre-line",
}
}
@@ -53,7 +53,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -70,14 +70,14 @@ exports[`SmartUiComponent disable all inputs 1`] = `
</StyledLabelBase>
<Stack
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
{
Object {
"childrenGap": 2,
}
}
@@ -107,7 +107,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -133,11 +133,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
onChange={[Function]}
step={10}
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
"valueLabel": {
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
@@ -157,7 +157,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -178,7 +178,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -199,8 +199,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
id="containerId-textField-input"
onChange={[Function]}
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
@@ -219,7 +219,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -243,8 +243,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
onChange={[Function]}
onText="Enabled"
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
@@ -261,7 +261,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -282,16 +282,16 @@ exports[`SmartUiComponent disable all inputs 1`] = `
id="database-dropdown-input"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "db1",
"text": "Database 1",
},
{
Object {
"key": "db2",
"text": "Database 2",
},
{
Object {
"key": "db3",
"text": "Database 3",
},
@@ -299,13 +299,13 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
selectedKey="db2"
styles={
{
"dropdown": {
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": {
"root": Object {
"width": 400,
},
}
@@ -323,7 +323,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -335,7 +335,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -347,7 +347,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
aria-labelledby="description-label"
id="description-text-display"
style={
{
Object {
"whiteSpace": "pre-line",
}
}
@@ -372,7 +372,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -389,14 +389,14 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
</StyledLabelBase>
<Stack
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
{
Object {
"childrenGap": 2,
}
}
@@ -425,7 +425,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -450,11 +450,11 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
onChange={[Function]}
step={10}
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
"valueLabel": {
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
@@ -474,7 +474,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -495,7 +495,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -515,8 +515,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
id="containerId-textField-input"
onChange={[Function]}
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
@@ -535,7 +535,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -558,8 +558,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
onChange={[Function]}
onText="Enabled"
styles={
{
"root": {
Object {
"root": Object {
"width": 400,
},
}
@@ -576,7 +576,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack
className="widgetRendererContainer"
tokens={
{
Object {
"childrenGap": 10,
}
}
@@ -596,16 +596,16 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
id="database-dropdown-input"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "db1",
"text": "Database 1",
},
{
Object {
"key": "db2",
"text": "Database 2",
},
{
Object {
"key": "db3",
"text": "Database 3",
},
@@ -613,13 +613,13 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
selectedKey="db2"
styles={
{
"dropdown": {
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": {
"root": Object {
"width": 400,
},
}

View File

@@ -1,16 +1,28 @@
@import "../../../../less/Common/Constants";
.tabComponentContainer {
height: 100%;
.flex-display();
.flex-direction();
height: 100%;
.flex-display();
.flex-direction();
.tabSwitch {
margin-left: @LargeSpace;
margin-bottom: 20px;
.tabSwitch {
margin-left: @LargeSpace;
margin-bottom: 20px;
.tab {
margin-right: @MediumSpace;
}
}
.tab {
margin-right: @MediumSpace;
}
.toggleSwitch {
.toggleSwitch();
}
.selectedToggle {
.selectedToggle();
}
.unselectedToggle {
.unselectedToggle();
}
}
}

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Pivot, PivotItem } from "@fluentui/react";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import "./TabComponent.less";
export interface TabContent {
@@ -35,36 +35,58 @@ export class TabComponent extends React.Component<TabComponentProps> {
}
private setActiveTab(index: number): void {
this.setState({ activeTabIndex: index });
this.props.onTabIndexChange(index);
}
private renderTabTitles(): JSX.Element[] {
return this.props.tabs.map((tab: Tab, index: number) => {
if (!tab.isVisible()) {
return <React.Fragment key={index} />;
}
let className = "toggleSwitch";
let ariaselected;
if (index === this.props.currentTabIndex) {
className += " selectedToggle";
ariaselected = true;
} else {
className += " unselectedToggle";
ariaselected = false;
}
return (
<div className="tab" key={index}>
<AccessibleElement
as="span"
className={className}
role="tab"
onActivated={() => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`}
aria-selected={ariaselected}
>
{tab.title}
</AccessibleElement>
</div>
);
});
}
public render(): JSX.Element {
const { tabs, currentTabIndex, hideHeader } = this.props;
const currentTabContent = tabs[currentTabIndex].content;
const currentTabContent = this.props.tabs[this.props.currentTabIndex].content;
let className = "tabComponentContent";
if (currentTabContent.className) {
className += ` ${currentTabContent.className}`;
}
return (
<div className="tabComponentContainer">
<div className="tabs tabSwitch">
{!hideHeader && (
<Pivot
aria-label="Tab navigation"
selectedKey={currentTabIndex.toString()}
linkSize="normal"
onLinkClick={(item) => this.setActiveTab(parseInt(item?.props.itemKey || ""))}
>
{tabs.map((tab: Tab, index: number) => {
if (!tab.isVisible()) {
return null; // Skip rendering invisible tabs
}
return <PivotItem key={index} headerText={tab.title} itemKey={index.toString()} />;
})}
</Pivot>
)}
</div>
<div className={className}>{tabs[currentTabIndex].content.render()}</div>
{!this.props.hideHeader && (
<div className="tabs tabSwitch" role="tablist">
{this.renderTabTitles()}
</div>
)}
<div className={className}>{currentTabContent.render()}</div>
</div>
);
}

View File

@@ -30,7 +30,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header"
key=".0:$.$.1"
style={
{
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
@@ -41,7 +41,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header"
className="css-110"
style={
{
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
@@ -62,9 +62,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1}
styles={[Function]}
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -73,92 +73,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -167,7 +167,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -220,7 +220,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -325,7 +325,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
@@ -357,9 +357,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]}
tabIndex={0}
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -368,92 +368,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -462,7 +462,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -515,7 +515,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -620,7 +620,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
@@ -645,7 +645,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true}
id="tooltip0"
style={
{
Object {
"border": 0,
"height": 1,
"margin": -1,
@@ -744,9 +744,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]}
target="_blank"
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -755,92 +755,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -849,7 +849,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -902,7 +902,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -1007,7 +1007,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
@@ -1042,7 +1042,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription"
key=".0:$.$.0"
style={
{
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
@@ -1053,7 +1053,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription"
className="css-110"
style={
{
Object {
"fontWeight": 600,
"lineHeight": "20px",
}
@@ -1075,9 +1075,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1}
styles={[Function]}
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1086,92 +1086,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1180,7 +1180,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1233,7 +1233,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -1338,7 +1338,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
@@ -1370,9 +1370,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]}
tabIndex={0}
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1381,92 +1381,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1475,7 +1475,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1528,7 +1528,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -1633,7 +1633,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",
@@ -1658,7 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true}
id="tooltip1"
style={
{
Object {
"border": 0,
"height": 1,
"margin": -1,
@@ -1690,11 +1690,11 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
required={true}
step={1000}
styles={
{
"field": {
Object {
"field": Object {
"fontSize": 12,
},
"fieldGroup": {
"fieldGroup": Object {
"height": 27,
"width": 300,
},
@@ -1716,9 +1716,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
step={1000}
styles={[Function]}
theme={
{
Object {
"disableGlobalClassNames": false,
"effects": {
"effects": Object {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1727,92 +1727,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": {
"large": {
"fonts": Object {
"large": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px",
"fontWeight": 400,
},
"medium": {
"medium": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px",
"fontWeight": 400,
},
"mediumPlus": {
"mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px",
"fontWeight": 400,
},
"mega": {
"mega": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px",
"fontWeight": 600,
},
"small": {
"small": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"smallPlus": {
"smallPlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px",
"fontWeight": 400,
},
"superLarge": {
"superLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px",
"fontWeight": 600,
},
"tiny": {
"tiny": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xLarge": {
"xLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px",
"fontWeight": 600,
},
"xLargePlus": {
"xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px",
"fontWeight": 600,
},
"xSmall": {
"xSmall": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px",
"fontWeight": 400,
},
"xxLarge": {
"xxLarge": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px",
"fontWeight": 600,
},
"xxLargePlus": {
"xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1821,7 +1821,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
},
},
"isInverted": false,
"palette": {
"palette": Object {
"accent": "#0078d4",
"black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1874,7 +1874,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100",
},
"rtl": undefined,
"semanticColors": {
"semanticColors": Object {
"accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff",
"actionLink": "#323130",
@@ -1979,7 +1979,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775",
"warningText": "#323130",
},
"spacing": {
"spacing": Object {
"l1": "20px",
"l2": "32px",
"m": "16px",

View File

@@ -1,66 +0,0 @@
import { makeStyles, shorthands, treeItemLevelToken } from "@fluentui/react-components";
import { cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
export type TreeStyleName = keyof ReturnType<typeof useTreeStyles>;
const treeIconWidth = "--cosmos-Tree--iconWidth" as const;
const leafNodeSpacing = "--cosmos-Tree--leafNodeSpacing" as const;
const actionButtonBackground = "--cosmos-Tree--actionButtonBackground" as const;
export const useTreeStyles = makeStyles({
treeContainer: {
height: "100%",
...shorthands.overflow("auto"),
},
tree: {
width: "fit-content",
minWidth: "100%",
rowGap: "0px",
paddingTop: "0px",
[treeIconWidth]: "16px",
[leafNodeSpacing]: "24px",
},
nodeIcon: {
width: `var(${treeIconWidth})`,
height: `var(${treeIconWidth})`,
},
treeItem: {},
nodeLabel: {
whiteSpace: "nowrap", // Don't wrap text, there will be a scrollbar.
},
treeItemLayout: {
fontSize: tokens.fontSizeBase300,
height: tokens.layoutRowHeight,
...cosmosShorthands.borderBottom(),
// Some sneaky CSS variables stuff to change the background color of the action button on hover.
[actionButtonBackground]: tokens.colorNeutralBackground1,
"&:hover": {
[actionButtonBackground]: tokens.colorNeutralBackground1Hover,
},
},
actionsButtonContainer: {
position: "sticky",
right: 0,
},
actionsButton: {
backgroundColor: `var(${actionButtonBackground})`,
},
treeItemLayoutNoIcon: {
// Pad the text out by the level, the width of the icon, AND the usual spacing between the icon and the level.
// It would be nice to see if we can use Grid layout or something here, but that would require overriding a lot of the existing Tree component behavior.
paddingLeft: `calc((var(${treeItemLevelToken}, 1) * ${tokens.spacingHorizontalXXL}) + var(${leafNodeSpacing}))`,
},
selectedItem: {
backgroundColor: tokens.colorNeutralBackground1Selected,
},
databaseNode: {
fontWeight: tokens.fontWeightSemibold,
},
collectionNode: {
fontWeight: tokens.fontWeightSemibold,
},
loadMoreNode: {
color: tokens.colorBrandForegroundLink,
},
});

View File

@@ -1,4 +1,4 @@
import { TreeItem, TreeItemLayout } from "@fluentui/react-components";
import { TreeItem, TreeItemLayout, tokens } from "@fluentui/react-components";
import PromiseSource from "Utils/PromiseSource";
import { mount, shallow } from "enzyme";
import React from "react";
@@ -9,7 +9,7 @@ function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): Tree
const node: TreeNode = {
id,
label: `${id}Label`,
className: "nodeIcon",
className: `${id}Class`,
iconSrc: `${id}Icon`,
onClick: jest.fn().mockName(`${id}Click`),
...additionalProps,
@@ -20,7 +20,7 @@ function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): Tree
describe("TreeNodeComponent", () => {
it("renders a single node", () => {
const node = generateTestNode("root");
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
// The "click" handler is actually attached to onOpenChange, with a type of "Click".
@@ -45,7 +45,7 @@ describe("TreeNodeComponent", () => {
},
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
@@ -54,7 +54,7 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", {
onExpanded: () => loading.promise,
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
act(() => {
component
@@ -72,7 +72,10 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", {
isSelected: () => true,
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component.find(TreeItemLayout).props().style?.backgroundColor).toStrictEqual(
tokens.colorNeutralBackground1Selected,
);
expect(component).toMatchSnapshot();
});
@@ -86,7 +89,10 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component.find(TreeItemLayout).props().style?.backgroundColor).toStrictEqual(
tokens.colorNeutralBackground1Selected,
);
expect(component).toMatchSnapshot();
});
@@ -105,7 +111,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component.find(TreeItemLayout).props().style?.backgroundColor).toBeUndefined();
expect(component).toMatchSnapshot();
});
@@ -114,7 +120,7 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", {
iconSrc: "the-icon.svg",
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
@@ -128,7 +134,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
@@ -142,7 +148,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
@@ -169,7 +175,7 @@ describe("TreeNodeComponent", () => {
}),
],
});
const component = mount(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
const component = mount(<TreeNodeComponent node={node} treeNodeId={node.id} />);
// Find and expand the child3Expanding node
const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel");

View File

@@ -11,13 +11,11 @@ import {
Tree,
TreeItem,
TreeItemLayout,
TreeItemValue,
TreeOpenChangeData,
TreeOpenChangeEvent,
mergeClasses,
} from "@fluentui/react-components";
import { ChevronDown20Regular, ChevronRight20Regular, MoreHorizontal20Regular } from "@fluentui/react-icons";
import { TreeStyleName, useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
import { tokens } from "@fluentui/react-theme";
import * as React from "react";
import { useCallback } from "react";
@@ -34,14 +32,15 @@ export interface TreeNode {
id?: string;
children?: TreeNode[];
contextMenu?: TreeNodeMenuItem[];
iconSrc?: string | JSX.Element;
iconSrc?: string;
isExpanded?: boolean;
className?: TreeStyleName;
className?: string;
isAlphaSorted?: boolean;
// data?: any; // Piece of data corresponding to this node
timestamp?: number;
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
isLoading?: boolean;
isScrollable?: boolean;
isSelected?: () => boolean;
onClick?: () => void; // Only if a leaf, other click will expand/collapse
onExpanded?: () => Promise<void>;
@@ -53,7 +52,6 @@ export interface TreeNodeComponentProps {
node: TreeNode;
className?: string;
treeNodeId: string;
openItems: TreeItemValue[];
}
/** Function that returns true if any descendant (at any depth) of this node is selected. */
@@ -68,13 +66,13 @@ function isAnyDescendantSelected(node: TreeNode): boolean {
);
}
const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt="" style={{ width: 16, height: 16 }} />;
export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
node,
treeNodeId,
openItems,
}: TreeNodeComponentProps): JSX.Element => {
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const treeStyles = useTreeStyles();
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
if (!treeNode || !treeNode.children) {
@@ -147,73 +145,45 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
</MenuItem>
));
// We use the expandIcon slot to hold the node icon too.
// We only show a node icon for leaf nodes, even if a branch node has an iconSrc.
const treeIcon =
node.iconSrc === undefined ? undefined : typeof node.iconSrc === "string" ? (
<img src={node.iconSrc} className={treeStyles.nodeIcon} alt="" />
) : (
node.iconSrc
);
const expandIcon = isLoading ? (
<Spinner size="extra-tiny" />
) : !isBranch ? undefined : openItems.includes(treeNodeId) ? (
<ChevronDown20Regular data-test="TreeNode/CollapseIcon" />
) : (
<ChevronRight20Regular data-text="TreeNode/ExpandIcon" />
);
const treeItem = (
<TreeItem
data-test={`TreeNodeContainer:${treeNodeId}`}
value={treeNodeId}
itemType={isBranch ? "branch" : "leaf"}
onOpenChange={onOpenChange}
className={treeStyles.treeItem}
>
<TreeItemLayout
className={mergeClasses(
treeStyles.treeItemLayout,
shouldShowAsSelected && treeStyles.selectedItem,
node.className && treeStyles[node.className],
)}
className={node.className}
data-test={`TreeNode:${treeNodeId}`}
actions={
contextMenuItems.length > 0 && {
className: treeStyles.actionsButtonContainer,
children: (
<Menu onOpenChange={onMenuOpenChange}>
<MenuTrigger disableButtonEnhancement>
<Button
aria-label="More options"
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
data-test="TreeNode/ContextMenuTrigger"
appearance="subtle"
icon={<MoreHorizontal20Regular />}
/>
</MenuTrigger>
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
<MenuList>{contextMenuItems}</MenuList>
</MenuPopover>
</Menu>
),
}
contextMenuItems.length > 0 && (
<Menu onOpenChange={onMenuOpenChange}>
<MenuTrigger disableButtonEnhancement>
<Button
aria-label="More options"
data-test="TreeNode/ContextMenuTrigger"
appearance="subtle"
icon={<MoreHorizontal20Regular />}
/>
</MenuTrigger>
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
<MenuList>{contextMenuItems}</MenuList>
</MenuPopover>
</Menu>
)
}
iconBefore={treeIcon}
expandIcon={expandIcon}
expandIcon={isLoading ? <Spinner size="extra-tiny" /> : undefined}
iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)}
style={{
backgroundColor: shouldShowAsSelected ? tokens.colorNeutralBackground1Selected : undefined,
}}
>
<span className={treeStyles.nodeLabel}>{node.label}</span>
{node.label}
</TreeItemLayout>
{!node.isLoading && node.children?.length > 0 && (
<Tree data-test={`Tree:${treeNodeId}`} className={treeStyles.tree}>
<Tree style={{ overflow: node.isScrollable ? "auto" : undefined }}>
{getSortedChildren(node).map((childNode: TreeNode) => (
<TreeNodeComponent
openItems={openItems}
key={childNode.label}
node={childNode}
treeNodeId={`${treeNodeId}/${childNode.label}`}
/>
<TreeNodeComponent key={childNode.label} node={childNode} treeNodeId={`${treeNodeId}/${childNode.label}`} />
))}
</Tree>
)}

View File

@@ -8,20 +8,20 @@ exports[`LegacyTreeComponent renders a simple tree 1`] = `
<LegacyTreeNodeComponent
generation={0}
node={
{
"children": [
{
"children": [
{
Object {
"children": Array [
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
"label": "Bchild1",
},
{
Object {
"label": "2child2",
},
],
@@ -45,7 +45,7 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
className="treeNodeHeader "
data-test="Tree/TreeNode/Header:label"
style={
{
Object {
"paddingLeft": 9,
}
}
@@ -56,7 +56,7 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src={{}}
src={Object {}}
tabIndex={0}
/>
<span
@@ -78,7 +78,7 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -96,7 +96,7 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
duration={200}
easing="ease"
height={0}
style={{}}
style={Object {}}
>
<div
className="nodeChildren"
@@ -107,12 +107,12 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
generation={3}
key="Bchild1-3-undefined"
node={
{
"children": [
{
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
@@ -125,7 +125,7 @@ exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
generation={3}
key="2child2-3-undefined"
node={
{
Object {
"label": "2child2",
}
}
@@ -149,7 +149,7 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
className="treeNodeHeader "
data-test="Tree/TreeNode/Header:label"
style={
{
Object {
"paddingLeft": 23,
}
}
@@ -160,7 +160,7 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src={{}}
src={Object {}}
tabIndex={0}
/>
<span
@@ -177,10 +177,10 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
ariaLabel="More options"
className="treeMenuEllipsis"
menuIconProps={
{
Object {
"iconName": "More",
"styles": {
"root": {
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
@@ -188,13 +188,13 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
}
}
menuProps={
{
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": [
{
"items": Array [
Object {
"className": undefined,
"disabled": true,
"key": "menuLabel",
@@ -209,8 +209,8 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
}
name="More"
styles={
{
"rootFocused": {
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
@@ -231,7 +231,7 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -249,7 +249,7 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
duration={200}
easing="ease"
height="auto"
style={{}}
style={Object {}}
>
<div
className="nodeChildren"
@@ -260,7 +260,7 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
generation={13}
key="2child2-13-undefined"
node={
{
Object {
"label": "2child2",
}
}
@@ -270,12 +270,12 @@ exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expande
generation={13}
key="Bchild1-13-undefined"
node={
{
"children": [
{
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
@@ -301,7 +301,7 @@ exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
className="treeNodeHeader "
data-test="Tree/TreeNode/Header:label"
style={
{
Object {
"paddingLeft": 9,
}
}
@@ -312,7 +312,7 @@ exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src={{}}
src={Object {}}
tabIndex={0}
/>
<span
@@ -334,7 +334,7 @@ exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -352,7 +352,7 @@ exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
duration={200}
easing="ease"
height="auto"
style={{}}
style={Object {}}
>
<div
className="nodeChildren"
@@ -376,7 +376,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
className="treeNodeHeader "
data-test="Tree/TreeNode/Header:label"
style={
{
Object {
"paddingLeft": 23,
}
}
@@ -387,7 +387,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src={{}}
src={Object {}}
tabIndex={0}
/>
<span
@@ -404,10 +404,10 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
ariaLabel="More options"
className="treeMenuEllipsis"
menuIconProps={
{
Object {
"iconName": "More",
"styles": {
"root": {
"styles": Object {
"root": Object {
"fontSize": "18px",
"fontWeight": "bold",
},
@@ -415,20 +415,20 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
}
}
menuProps={
{
Object {
"contextualMenuItemAs": [Function],
"coverTarget": true,
"directionalHint": 3,
"isBeakVisible": false,
"items": [],
"items": Array [],
"onMenuDismissed": [Function],
"onMenuOpened": [Function],
}
}
name="More"
styles={
{
"rootFocused": {
Object {
"rootFocused": Object {
"outline": "1px dashed undefined",
},
}
@@ -449,7 +449,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -467,7 +467,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
duration={200}
easing="ease"
height="auto"
style={{}}
style={Object {}}
>
<div
className="nodeChildren"
@@ -478,12 +478,12 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
generation={13}
key="bchild-13-undefined"
node={
{
"children": [
{
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
@@ -496,12 +496,12 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
generation={13}
key="dchild-13-undefined"
node={
{
"children": [
{
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
@@ -514,7 +514,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
generation={13}
key="aChild-13-undefined"
node={
{
Object {
"label": "aChild",
}
}
@@ -524,7 +524,7 @@ exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and p
generation={13}
key="cchild-13-undefined"
node={
{
Object {
"label": "cchild",
}
}
@@ -547,7 +547,7 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
className="treeNodeHeader "
data-test="Tree/TreeNode/Header:label"
style={
{
Object {
"paddingLeft": 9,
}
}
@@ -558,7 +558,7 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
className="expandCollapseIcon"
onKeyPress={[Function]}
role="button"
src={{}}
src={Object {}}
tabIndex={0}
/>
<span
@@ -580,7 +580,7 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -598,7 +598,7 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
duration={200}
easing="ease"
height="auto"
style={{}}
style={Object {}}
>
<div
className="nodeChildren"
@@ -609,12 +609,12 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
generation={3}
key="Bchild1-3-undefined"
node={
{
"children": [
{
Object {
"children": Array [
Object {
"label": "ZgrandChild11",
},
{
Object {
"label": "AgrandChild12",
},
],
@@ -627,7 +627,7 @@ exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
generation={3}
key="2child2-3-undefined"
node={
{
Object {
"label": "2child2",
}
}

View File

@@ -1,17 +1,13 @@
import * as msal from "@azure/msal-browser";
import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout";
import React from "react";
@@ -34,13 +30,14 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { isAccountNewerThanThresholdInMs, updateUserContext, userContext } from "../UserContext";
import { isAccountNewerThanThresholdInMs, userContext } from "../UserContext";
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer";
@@ -254,48 +251,12 @@ export default class Explorer {
};
useDialog.getState().openDialog(addSynapseLinkDialogProps);
TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
}
public async openLoginForEntraIDPopUp(): Promise<void> {
if (userContext.databaseAccount.properties?.documentEndpoint) {
const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(
/\/$/,
"/.default",
);
const msalInstance = await getMsalInstance();
try {
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
scopes: [],
});
localStorage.setItem("cachedTenantId", response.tenantId);
const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount);
const aadToken = await acquireTokenWithMsal(msalInstance, {
forceRefresh: true,
scopes: [hrefEndpoint],
authority: `${configContext.AAD_ENDPOINT}${localStorage.getItem("cachedTenantId")}`,
});
updateUserContext({ aadToken: aadToken });
useDataPlaneRbac.setState({ aadTokenUpdated: true });
} catch (error) {
if (error instanceof msal.AuthError && error.errorCode === msal.BrowserAuthErrorMessage.popUpWindowError.code) {
logConsoleError(
"We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and try again",
);
} else {
const errorJson = JSON.stringify(error);
logConsoleError(
`Failed to perform authorization for this account, due to the following error: \n${errorJson}`,
);
}
}
}
// TODO: return result
}
public openNPSSurveyDialog(): void {
if (!Platform.Portal || !["Postgres", "SQL", "Mongo"].includes(userContext.apiType)) {
if (!Platform.Portal) {
return;
}
@@ -393,16 +354,16 @@ export default class Explorer {
return true;
};
public onRefreshResourcesClick = async (): Promise<void> => {
public onRefreshResourcesClick = (): void => {
if (configContext.platform === Platform.Fabric) {
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
return;
}
await (userContext.authType === AuthType.ResourceToken
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases());
await this.refreshNotebookList();
: this.refreshAllDatabases();
this.refreshNotebookList();
};
// Facade
@@ -1197,7 +1158,7 @@ export default class Explorer {
public async refreshSampleData(): Promise<void> {
try {
if (!userContext.sampleDataConnectionInfo) {
if (!userContext.sampleDataConnectionInfo || useDatabases.getState().sampleDataResourceTokenCollection) {
return;
}
const collection: DataModels.Collection = await readSampleCollection();

View File

@@ -737,17 +737,17 @@ export class D3ForceGraph implements GraphRenderer {
.on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) {
// https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation)
// this is the <g> element
self.onNodeClicked(this.parentNode as BaseType, d);
self.onNodeClicked(this.parentNode, d);
})
.on("click", function (this: Element, _: MouseEvent, d: D3Node) {
// this is the <g> element
self.onNodeClicked(this.parentNode as BaseType, d);
self.onNodeClicked(this.parentNode, d);
})
.on("keypress", function (this: Element, event: KeyboardEvent, d: D3Node) {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
event.stopPropagation();
// this is the <g> element
self.onNodeClicked(this.parentNode as BaseType, d);
self.onNodeClicked(this.parentNode, d);
}
});
var nodeSize = this.igraphConfig.nodeSize;

View File

@@ -1,4 +1,4 @@
import { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData";
describe("Graph Data", () => {
it("should set only one node as root", () => {
@@ -54,12 +54,12 @@ describe("Graph Data", () => {
// in edge
graphData.removeEdge("e1", false);
expect(graphData.edges.length).toBe(1);
expect(graphData).not.toContainEqual({ id: "e1" });
expect(graphData).not.toContain(jasmine.objectContaining({ id: "e1" }));
// out edge
graphData.removeEdge("e2", false);
expect(graphData.edges.length).toBe(0);
expect(graphData).not.toContainEqual({ id: "e2" });
expect(graphData).not.toContain(jasmine.objectContaining({ id: "e2" }));
});
it("should get string node property", () => {

View File

@@ -342,7 +342,7 @@ describe("GraphExplorer", () => {
});
describe("Load Graph button", () => {
beforeEach((done) => {
beforeEach(async (done) => {
const backendResponses: BackendResponses = {};
backendResponses["g.V()"] = backendResponses["g.V('1')"] = {
response: [{ id: "1", type: "vertex" }],
@@ -632,15 +632,24 @@ describe("GraphExplorer", () => {
it("should display RU consumption", () => {
// Find link for query stats
const queryStatsTab = wrapper.find(`button[name="${GraphExplorer.QUERY_STATS_BUTTON_LABEL}"]`);
queryStatsTab.simulate("click");
const values = wrapper.find(".queryMetricsSummary td");
const links = wrapper.find(".toggleSwitch");
let isRUDisplayed = false;
values.forEach((value) => {
if (Number(value.text()) === gVRU) {
isRUDisplayed = true;
for (let i = 0; i < links.length; i++) {
const link = links.at(i);
if (link.text() === GraphExplorer.QUERY_STATS_BUTTON_LABEL) {
link.simulate("click");
const values = wrapper.find(".queryMetricsSummary td");
for (let j = 0; j < values.length; j++) {
if (Number(values.at(j).text()) === gVRU) {
isRUDisplayed = true;
break;
}
}
break;
}
});
}
expect(isRUDisplayed).toBe(true);
});
});

View File

@@ -162,7 +162,7 @@ export const addRootChildToGraph = (
* @param value
*/
export const escapeDoubleQuotes = (value: string): string => {
return value === undefined ? value : value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
return value === undefined ? value : value.replace(/"/g, '\\"');
};
/**
@@ -186,5 +186,5 @@ export const getQuotedPropValue = (ip: ViewModels.InputPropertyValue): string =>
* @param value
*/
export const escapeSingleQuotes = (value: string): string => {
return value === undefined ? value : value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
return value === undefined ? value : value.replace(/'/g, "\\'");
};

View File

@@ -348,9 +348,8 @@ export class NodePropertiesComponent extends React.Component<
as="span"
onActivated={this.setIsDeleteConfirm.bind(this, true)}
aria-label="Delete this vertex"
role="button"
>
<img src={DeleteIcon} alt="Delete" aria-label="hidden" />
<img src={DeleteIcon} alt="Delete" role="button" />
</AccessibleElement>
);
} else {
@@ -406,9 +405,8 @@ export class NodePropertiesComponent extends React.Component<
as="span"
aria-label="Edit properties"
onActivated={expandClickHandler}
role="button"
>
<img src={EditIcon} alt="Edit" aria-label="hidden" />
<img src={EditIcon} alt="Edit" role="button" />
</AccessibleElement>
)}

View File

@@ -128,7 +128,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
>
<img
alt="Delete"
src={{}}
src={Object {}}
/>
</AccessibleElement>
</td>
@@ -185,7 +185,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
>
<img
alt="Delete"
src={{}}
src={Object {}}
/>
</AccessibleElement>
</td>
@@ -203,7 +203,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
>
<img
alt="Add"
src={{}}
src={Object {}}
/>
Add Property
</AccessibleElement>
@@ -317,7 +317,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
>
<img
alt="Delete"
src={{}}
src={Object {}}
/>
</AccessibleElement>
</td>
@@ -379,7 +379,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
>
<img
alt="Delete"
src={{}}
src={Object {}}
/>
</AccessibleElement>
</td>
@@ -397,7 +397,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
>
<img
alt="Add"
src={{}}
src={Object {}}
/>
Add Property
</AccessibleElement>

View File

@@ -1,13 +1,15 @@
import { KeyboardAction } from "KeyboardShortcuts";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import { useEffect, useState } from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import EntraIDIcon from "../../../../images/EntraID.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import HomeIcon from "../../../../images/Home_16.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
@@ -18,15 +20,17 @@ import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen";
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane, useDataPlaneRbac } from "../../Panes/SettingsPane/SettingsPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { useDatabases } from "../../useDatabases";
import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode";
@@ -49,31 +53,26 @@ export function createStaticCommandBarButtons(
}
};
if (
configContext.platform !== Platform.Fabric &&
userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra"
) {
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
addDivider();
buttons.push(addSynapseLink);
if (configContext.platform !== Platform.Fabric) {
const homeBtn = createHomeButton();
buttons.push(homeBtn);
const newCollectionBtn = createNewCollectionGroup(container);
newCollectionBtn.keyboardAction = KeyboardAction.NEW_COLLECTION; // Just for the root button, not the child version we create below.
buttons.push(newCollectionBtn);
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
addDivider();
buttons.push(addSynapseLink);
}
}
}
if (userContext.apiType === "SQL") {
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
useEffect(() => {
const buttonProps = createLoginForEntraIDButton(container);
setLoginButtonProps(buttonProps);
}, [dataPlaneRbacEnabled, aadTokenUpdated, container]);
if (loginButtonProps) {
addDivider();
buttons.push(loginButtonProps);
if (userContext.apiType !== "Tables") {
newCollectionBtn.children = [createNewCollectionGroup(container)];
const newDatabaseBtn = createNewDatabase(container);
newCollectionBtn.children.push(newDatabaseBtn);
}
}
@@ -144,41 +143,26 @@ export function createContextCommandBarButtons(
buttons.push(newMongoShellBtn);
}
if (
useNotebook.getState().isShellEnabled &&
!selectedNodeState.isDatabaseNodeOrNoneSelected() &&
userContext.apiType === "Cassandra"
) {
const label: string = "Open Cassandra Shell";
const newCassandraShellButton: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
};
buttons.push(newCassandraShellButton);
}
return buttons;
}
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
hasPopup: true,
disabled: false,
},
];
const buttons: CommandButtonComponentProps[] =
configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly
? []
: [
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () =>
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
hasPopup: true,
disabled: false,
},
];
const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
@@ -239,6 +223,31 @@ function areScriptsSupported(): boolean {
);
}
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
const label = `New ${getCollectionName()}`;
return {
iconSrc: AddCollectionIcon,
iconAlt: label,
onCommandClick: () => container.onNewCollectionClicked(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
id: "createNewContainerCommandButton",
};
}
function createHomeButton(): CommandButtonComponentProps {
const label = "Home";
return {
iconSrc: HomeIcon,
iconAlt: label,
onCommandClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Home),
commandButtonLabel: label,
hasPopup: false,
ariaLabel: label,
};
}
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) {
return undefined;
@@ -266,28 +275,22 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
};
}
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) {
return undefined;
}
const handleCommandClick = async () => {
await container.openLoginForEntraIDPopUp();
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
};
if (!userContext.dataPlaneRbacEnabled || userContext.aadToken) {
return undefined;
}
const label = "Login for Entra ID RBAC";
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
const label = "New " + getDatabaseName();
return {
iconSrc: EntraIDIcon,
iconSrc: AddDatabaseIcon,
iconAlt: label,
onCommandClick: handleCommandClick,
keyboardAction: KeyboardAction.NEW_DATABASE,
onCommandClick: async () => {
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
if (throughputCap && throughputCap !== -1) {
await useDatabases.getState().loadAllOffers();
}
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />);
},
commandButtonLabel: label,
hasPopup: true,
ariaLabel: label,
hasPopup: true,
};
}

View File

@@ -57,6 +57,10 @@ export class NotificationConsoleComponent extends React.Component<
this.prevHeaderStatus = undefined;
}
public componentDidMount() {
this.componentDidUpdate(this.props, this.state);
}
public componentDidUpdate(
prevProps: NotificationConsoleComponentProps,
prevState: NotificationConsoleComponentState,
@@ -131,7 +135,6 @@ export class NotificationConsoleComponent extends React.Component<
</div>
<div
className="expandCollapseButton"
data-test="NotificationConsole/ExpandCollapseButton"
role="button"
tabIndex={0}
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
@@ -148,7 +151,7 @@ export class NotificationConsoleComponent extends React.Component<
height={this.props.isConsoleExpanded ? "auto" : 0}
onAnimationEnd={this.onConsoleWasExpanded}
>
<div data-test="NotificationConsole/Contents" className="notificationConsoleContents">
<div className="notificationConsoleContents">
<div className="notificationConsoleControls">
<Dropdown
label="Filter:"
@@ -266,20 +269,29 @@ export class NotificationConsoleComponent extends React.Component<
};
private updateConsoleData = (prevProps: NotificationConsoleComponentProps): void => {
let updatedConsoleData: ConsoleData[] = [...this.state.allConsoleData];
let refresh = false;
if (!this.areConsoleDataEqual(this.props.consoleData, prevProps.consoleData)) {
this.setState({ allConsoleData: [this.props.consoleData, ...this.state.allConsoleData] });
updatedConsoleData = [this.props.consoleData, ...updatedConsoleData];
refresh = true;
}
if (
this.props.inProgressConsoleDataIdToBeDeleted &&
prevProps.inProgressConsoleDataIdToBeDeleted !== this.props.inProgressConsoleDataIdToBeDeleted
) {
const allConsoleData = this.state.allConsoleData.filter(
if (this.props.inProgressConsoleDataIdToBeDeleted) {
const hasMatchingItem = updatedConsoleData.some(
(data: ConsoleData) =>
!(data.type === ConsoleDataType.InProgress && data.id === this.props.inProgressConsoleDataIdToBeDeleted),
data.type === ConsoleDataType.InProgress && data.id === this.props.inProgressConsoleDataIdToBeDeleted,
);
this.setState({ allConsoleData });
if (hasMatchingItem) {
updatedConsoleData = updatedConsoleData.filter(
(data: ConsoleData) =>
!(data.type === ConsoleDataType.InProgress && data.id === this.props.inProgressConsoleDataIdToBeDeleted),
);
refresh = true;
}
}
refresh && this.setState({ allConsoleData: updatedConsoleData });
};
private areConsoleDataEqual = (currentData: ConsoleData, prevData: ConsoleData): boolean => {

View File

@@ -22,7 +22,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
>
<img
alt="in progress items"
src={{}}
src={Object {}}
/>
<span
className="numInProgress"
@@ -35,7 +35,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
>
<img
alt="error items"
src={{}}
src={Object {}}
/>
<span
className="numErroredItems"
@@ -48,7 +48,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
>
<img
alt="info items"
src={{}}
src={Object {}}
/>
<span
className="numInfoItems"
@@ -74,7 +74,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
aria-expanded={true}
aria-label="console button collapsed"
className="expandCollapseButton"
data-test="NotificationConsole/ExpandCollapseButton"
role="button"
tabIndex={0}
>
@@ -87,7 +86,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -106,11 +105,10 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
easing="ease"
height={0}
onAnimationEnd={[Function]}
style={{}}
style={Object {}}
>
<div
className="notificationConsoleContents"
data-test="NotificationConsole/Contents"
>
<div
className="notificationConsoleControls"
@@ -119,20 +117,20 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
label="Filter:"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "All",
"text": "All",
},
{
Object {
"key": "In Progress",
"text": "In progress",
},
{
Object {
"key": "Info",
"text": "Info",
},
{
Object {
"key": "Error",
"text": "Error",
},
@@ -149,7 +147,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
onKeyDown={[Function]}
role="button"
style={
{
Object {
"border": "1px solid black",
"borderRadius": "2px",
}
@@ -158,7 +156,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
>
<img
alt="clear notifications image"
src={{}}
src={Object {}}
/>
Clear Notifications
</span>
@@ -193,7 +191,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
>
<img
alt="in progress items"
src={{}}
src={Object {}}
/>
<span
className="numInProgress"
@@ -206,7 +204,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
>
<img
alt="error items"
src={{}}
src={Object {}}
/>
<span
className="numErroredItems"
@@ -219,7 +217,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
>
<img
alt="info items"
src={{}}
src={Object {}}
/>
<span
className="numInfoItems"
@@ -247,7 +245,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
aria-expanded={true}
aria-label="console button collapsed"
className="expandCollapseButton"
data-test="NotificationConsole/ExpandCollapseButton"
role="button"
tabIndex={0}
>
@@ -260,7 +257,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
<AnimateHeight
animateOpacity={false}
animationStateClasses={
{
Object {
"animating": "rah-animating",
"animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -279,11 +276,10 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
easing="ease"
height={0}
onAnimationEnd={[Function]}
style={{}}
style={Object {}}
>
<div
className="notificationConsoleContents"
data-test="NotificationConsole/Contents"
>
<div
className="notificationConsoleControls"
@@ -292,20 +288,20 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
label="Filter:"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "All",
"text": "All",
},
{
Object {
"key": "In Progress",
"text": "In progress",
},
{
Object {
"key": "Info",
"text": "Info",
},
{
Object {
"key": "Error",
"text": "Error",
},
@@ -322,7 +318,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
onKeyDown={[Function]}
role="button"
style={
{
Object {
"border": "1px solid black",
"borderRadius": "2px",
}
@@ -331,7 +327,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
>
<img
alt="clear notifications image"
src={{}}
src={Object {}}
/>
Clear Notifications
</span>
@@ -346,7 +342,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
<img
alt="info"
className="infoIcon"
src={{}}
src={Object {}}
/>
<span
className="date"

View File

@@ -2,13 +2,22 @@
import { Link } from "@fluentui/react";
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
// Vendor modules
import { actions, AppState, ContentRef, KernelRef, NotebookContentRecord, selectors } from "@nteract/core";
import {
actions,
AppState,
ContentRef,
DocumentRecordProps,
KernelRef,
NotebookContentRecord,
selectors,
} from "@nteract/core";
import "@nteract/styles/editor-overrides.css";
import "@nteract/styles/global-variables.css";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/lib/codemirror.css";
import { Notebook } from "Common/Constants";
import { useDialog } from "Explorer/Controls/Dialog";
import * as Immutable from "immutable";
import * as React from "react";
import { Provider } from "react-redux";
import "react-table/react-table.css";
@@ -306,8 +315,7 @@ export class NotebookComponentBootstrapper {
return false;
}
// TODO Fix this typing here
return selectors.notebook.isDirty(content.model as never);
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
}
public isNotebookUntrusted(): boolean {

View File

@@ -111,8 +111,7 @@ const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps
} else if (kernel?.kernelSpecName) {
kernelSpecDisplayName = kernel.kernelSpecName;
} else if (content && content.type === "notebook") {
// TODO Fix typing here
kernelSpecDisplayName = selectors.notebook.displayName(content.model as never) || " ";
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";
}
return {

View File

@@ -4,7 +4,7 @@ exports[`PromptContent renders for busy status 1`] = `
<div
className="greyStopButton"
style={
{
Object {
"left": 0,
"maxHeight": "100%",
"position": "sticky",
@@ -18,12 +18,12 @@ exports[`PromptContent renders for busy status 1`] = `
ariaLabel="Stop cell execution"
className="runCellButton"
iconProps={
{
Object {
"iconName": "CircleStopSolid",
}
}
style={
{
Object {
"position": "absolute",
}
}
@@ -32,7 +32,7 @@ exports[`PromptContent renders for busy status 1`] = `
<StyledSpinnerBase
size={3}
style={
{
Object {
"paddingTop": 5,
"position": "absolute",
"width": "100%",
@@ -50,7 +50,7 @@ exports[`PromptContent renders when hovered 1`] = `
ariaLabel="Run cell"
className="runCellButton"
iconProps={
{
Object {
"iconName": "MSNVideosSolid",
}
}

View File

@@ -1,6 +1,5 @@
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { PhoenixClient } from "Phoenix/PhoenixClient";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import { cloneDeep } from "lodash";
import create, { UseStore } from "zustand";
import { AuthType } from "../../AuthType";
@@ -128,9 +127,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
? databaseAccount?.location
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
const disallowedLocationsUri: string = useNewPortalBackendEndpoint(Constants.BackendApi.DisallowedLocations)
? `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`
: `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
const authorizationHeader = getAuthorizationHeader();
try {
const response = await fetch(disallowedLocationsUri, {

View File

@@ -576,7 +576,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</TooltipHost>
</Stack>
<Text variant="small">{this.getPartitionKeySubtext()}</Text>
<Text variant="small" aria-label="pkDescription">
{this.getPartitionKeySubtext()}
</Text>
<input
type="text"

View File

@@ -212,9 +212,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
onChange={handleonChangeDBId}
autoFocus
styles={getTextFieldStyles()}
// We've seen password managers prompt to autofill this field, which is not desired.
data-lpignore={true}
data-1p-ignore={true}
/>
{!isServerlessAccount() && (

View File

@@ -35,29 +35,27 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
aria-required="true"
autoComplete="off"
autoFocus={true}
data-1p-ignore={true}
data-lpignore={true}
id="database-id"
onChange={[Function]}
pattern="[^/?#\\\\]*[^/?# \\\\]"
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new database id"
size={40}
styles={
{
"field": {
Object {
"field": Object {
"fontSize": 12,
"selectors": {
"::placeholder": {
"selectors": Object {
"::placeholder": Object {
"fontSize": 12,
},
},
},
"root": {
"root": Object {
"width": 300,
},
}
}
title="May not end with space nor contain characters '\\' '/' '#' '?'"
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
type="text"
value=""
/>
@@ -69,16 +67,16 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
label="Provision throughput"
onChange={[Function]}
styles={
{
"checkbox": {
Object {
"checkbox": Object {
"height": 12,
"width": 12,
},
"label": {
"label": Object {
"alignItems": "center",
"padding": 0,
},
"text": {
"text": Object {
"fontSize": 12,
},
}

View File

@@ -4,8 +4,8 @@ exports[`Browse queries panel Should render Default properly 1`] = `
<BrowseQueriesPane
closePanel={[Function]}
explorer={
{
"queriesClient": {
Object {
"queriesClient": Object {
"getQueries": [Function],
},
}
@@ -21,7 +21,7 @@ exports[`Browse queries panel Should render Default properly 1`] = `
containerVisible={true}
onQuerySelect={[Function]}
queriesClient={
{
Object {
"getQueries": [Function],
}
}
@@ -41,7 +41,7 @@ exports[`Browse queries panel Should render Default properly 1`] = `
alt="Save query helper banner"
src=""
style={
{
Object {
"border": "1px solid undefined",
"height": "150px",
"marginTop": "20px",

View File

@@ -264,7 +264,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
</TooltipHost>
</Stack>
<Text variant="small">
<Text variant="small" aria-label="pkDescription">
{getPartitionKeySubtext(userContext.features.partitionKeyDefault, userContext.apiType)}
</Text>

View File

@@ -7,7 +7,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
<div
className="panelMainContent"
style={
{
Object {
"display": "flex",
"flexDirection": "column",
}
@@ -15,7 +15,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
>
<GitHubReposComponent
addRepoProps={
{
Object {
"container": Explorer {
"_isInitializingNotebooks": false,
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -24,7 +24,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": {
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
@@ -46,7 +46,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
}
}
authorizeAccessProps={
{
Object {
"authorizeAccess": [Function],
"scope": "public_repo",
}
@@ -54,18 +54,18 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
onCancelClick={[Function]}
onOkClick={[Function]}
reposListProps={
{
"branchesProps": {},
Object {
"branchesProps": Object {},
"pinRepo": [Function],
"pinnedReposProps": {
"repos": [],
"pinnedReposProps": Object {
"repos": Array [],
},
"unpinRepo": [Function],
"unpinnedReposProps": {
"unpinnedReposProps": Object {
"hasMore": true,
"isLoading": true,
"loadMore": [Function],
"repos": [],
"repos": Array [],
},
}
}

View File

@@ -22,8 +22,8 @@ exports[`Load Query Pane should render Default properly 1`] = `
label="Select a query document"
readOnly={true}
styles={
{
"fieldGroup": {
Object {
"fieldGroup": Object {
"width": 300,
},
}
@@ -39,7 +39,7 @@ exports[`Load Query Pane should render Default properly 1`] = `
className="fileIcon"
height={20}
imageFit={4}
src={{}}
src={Object {}}
width={20}
/>
<input

View File

@@ -7,7 +7,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
<Stack
className="panelMainContent"
tokens={
{
Object {
"childrenGap": 20,
}
}
@@ -55,12 +55,12 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
label="Cover image"
onChange={[Function]}
options={
[
{
Array [
Object {
"key": "Custom Image",
"text": "Custom Image",
},
{
Object {
"key": "URL",
"text": "URL",
},
@@ -85,7 +85,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
<StackItem>
<GalleryCardComponent
data={
{
Object {
"author": "CosmosDB",
"created": "2020-07-17T00:00:00Z",
"description": "sample description",
@@ -98,7 +98,7 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
"newCellId": undefined,
"pendingScanJobIds": undefined,
"policyViolations": undefined,
"tags": [
"tags": Array [
"tag1",
" tag2",
],

View File

@@ -2,7 +2,7 @@
exports[`Save Query Pane should render Default properly 1`] = `
<RightPaneForm
footerStyle={{}}
footerStyle={Object {}}
formError=""
isExecuting={false}
onSubmit={[Function]}
@@ -11,7 +11,7 @@ exports[`Save Query Pane should render Default properly 1`] = `
<div
className="panelFormWrapper"
style={
{
Object {
"flexGrow": 1,
}
}

View File

@@ -1,25 +1,18 @@
import {
Checkbox,
ChoiceGroup,
DefaultButton,
IChoiceGroupOption,
ISpinButtonStyles,
IToggleStyles,
MessageBar,
MessageBarType,
Position,
SpinButton,
Toggle,
} from "@fluentui/react";
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, makeStyles } from "@fluentui/react-components";
import { AuthType } from "AuthType";
import * as Constants from "Common/Constants";
import { SplitterDirection } from "Common/Splitter";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { Platform, configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog";
import { configContext } from "ConfigContext";
import { useDatabases } from "Explorer/useDatabases";
import { deleteAllStates } from "Shared/AppStatePersistenceUtility";
import {
DefaultRUThreshold,
LocalStorageUtility,
@@ -29,66 +22,15 @@ import {
ruThresholdEnabled as isRUThresholdEnabled,
} from "Shared/StorageUtility";
import * as StringUtility from "Shared/StringUtility";
import { updateUserContext, userContext } from "UserContext";
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
import { userContext } from "UserContext";
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react";
import create, { UseStore } from "zustand";
import Explorer from "../../Explorer";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface DataPlaneRbacState {
dataPlaneRbacEnabled: boolean;
aadTokenUpdated: boolean;
getState?: () => DataPlaneRbacState;
setDataPlaneRbacEnabled: (dataPlaneRbacEnabled: boolean) => void;
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
}
type DataPlaneRbacStore = UseStore<Partial<DataPlaneRbacState>>;
const useStyles = makeStyles({
bulletList: {
listStyleType: "disc",
paddingLeft: "20px",
},
container: {
display: "flex",
flexDirection: "column",
height: "100%",
},
firstItem: {
flex: "1",
},
header: {
marginRight: "5px",
},
headerIcon: {
paddingTop: "4px",
cursor: "pointer",
},
settingsSectionContainer: {
paddingLeft: "15px",
},
settingsSectionDescription: {
paddingBottom: "10px",
fontSize: "12px",
},
subHeader: {
marginRight: "5px",
fontSize: "12px",
},
});
export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({
dataPlaneRbacEnabled: false,
}));
export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
explorer,
}: {
@@ -102,14 +44,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
? Constants.Queries.UnlimitedPageOption
: Constants.Queries.CustomPageOption,
);
const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState<string>(
LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)
? LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled)
: Constants.RBACOptions.setAutomaticRBACOption,
);
const [showDataPlaneRBACWarning, setShowDataPlaneRBACWarning] = useState<boolean>(false);
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
@@ -168,9 +102,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
const [copilotSampleDBEnabled, setCopilotSampleDBEnabled] = useState<boolean>(
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
);
const styles = useStyles();
const explorerVersion = configContext.gitSha;
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin";
@@ -188,51 +119,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
StorageKey.ActualItemPerPage,
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
);
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
if (configContext.platform !== Platform.Fabric) {
LocalStorageUtility.setEntryString(StorageKey.DataPlaneRbacEnabled, enableDataPlaneRBACOption);
if (
enableDataPlaneRBACOption === Constants.RBACOptions.setTrueRBACOption ||
(enableDataPlaneRBACOption === Constants.RBACOptions.setAutomaticRBACOption &&
userContext.databaseAccount.properties.disableLocalAuth)
) {
updateUserContext({
dataPlaneRbacEnabled: true,
hasDataPlaneRbacSettingChanged: true,
});
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
} else {
updateUserContext({
dataPlaneRbacEnabled: false,
hasDataPlaneRbacSettingChanged: true,
});
const { databaseAccount: account, subscriptionId, resourceGroup } = userContext;
if (!userContext.features.enableAadDataPlane && !userContext.masterKey) {
let keys;
try {
keys = await listKeys(subscriptionId, resourceGroup, account.name);
updateUserContext({
masterKey: keys.primaryMasterKey,
});
} catch (error) {
// if listKeys fail because of permissions issue, then make call to get ReadOnlyKeys
if (error.code === "AuthorizationFailed") {
keys = await getReadOnlyKeys(subscriptionId, resourceGroup, account.name);
updateUserContext({
masterKey: keys.primaryReadonlyMasterKey,
});
} else {
logConsoleError(`Error occurred fetching keys for the account." ${error.message}`);
throw error;
}
}
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: false });
}
}
}
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
@@ -320,12 +207,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
{ key: Constants.PriorityLevel.High, text: "High" },
];
const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [
{ key: Constants.RBACOptions.setAutomaticRBACOption, text: "Automatic" },
{ key: Constants.RBACOptions.setTrueRBACOption, text: "True" },
{ key: Constants.RBACOptions.setFalseRBACOption, text: "False" },
];
const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [
{ key: SplitterDirection.Vertical, text: "Vertical" },
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
@@ -342,20 +223,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
setPageOption(option.key);
};
const handleOnDataPlaneRBACOptionChange = (
ev: React.FormEvent<HTMLInputElement>,
option: IChoiceGroupOption,
): void => {
setEnableDataPlaneRBACOption(option.key);
const shouldShowWarning =
(option.key === Constants.RBACOptions.setTrueRBACOption ||
(option.key === Constants.RBACOptions.setAutomaticRBACOption &&
userContext.databaseAccount.properties.disableLocalAuth === true)) &&
!useDataPlaneRbac.getState().aadTokenUpdated;
setShowDataPlaneRBACWarning(shouldShowWarning);
};
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
setRUThresholdEnabled(checked);
};
@@ -468,457 +335,359 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
return (
<RightPaneForm {...genericPaneProps}>
<div className={`paneMainContent ${styles.container}`}>
<Accordion className={styles.firstItem}>
{shouldShowQueryPageOptions && (
<AccordionItem value="1">
<AccordionHeader>
<div className={styles.header}>Page Options</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
many query results per page.
</div>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
</div>
<div className={`tabs ${styles.settingsSectionContainer}`}>
{isCustomPageOptionSelected() && (
<div className="tabcontent">
<div className={styles.settingsSectionDescription}>
Query results per page{" "}
<InfoTooltip className={styles.headerIcon}>
Enter the number of query results that should be shown per page.
</InfoTooltip>
</div>
<SpinButton
ariaLabel="Custom query items per page"
value={"" + customItemPerPage}
onIncrement={(newValue) => {
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
}}
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
min={1}
step={1}
className="textfontclr"
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
/>
</div>
)}
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" &&
userContext.authType === AuthType.AAD &&
configContext.platform !== Platform.Fabric && (
<AccordionItem value="2">
<AccordionHeader>
<div className={styles.header}>Enable Entra ID RBAC</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
{showDataPlaneRBACWarning && configContext.platform === Platform.Portal && (
<MessageBar
messageBarType={MessageBarType.warning}
isMultiline={true}
onDismiss={() => setShowDataPlaneRBACWarning(false)}
dismissButtonAriaLabel="Close"
>
Please click on &quot;Login for Entra ID RBAC&quot; button prior to performing Entra ID RBAC
operations
</MessageBar>
)}
<div className={styles.settingsSectionDescription}>
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
ID RBAC.
<a
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
target="_blank"
rel="noopener noreferrer"
>
{" "}
Learn more{" "}
</a>
</div>
<ChoiceGroup
ariaLabelledBy="enableDataPlaneRBACOptions"
options={dataPlaneRBACOptionsList}
styles={choiceButtonStyles}
selectedKey={enableDataPlaneRBACOption}
onChange={handleOnDataPlaneRBACOptionChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && (
<>
<AccordionItem value="3">
<AccordionHeader>
<div className={styles.header}>Query Timeout</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
When a query reaches a specified time limit, a popup with an option to cancel the query will show
unless automatic cancellation has been enabled.
</div>
<Toggle
styles={toggleStyles}
label="Enable query timeout"
onChange={handleOnQueryTimeoutToggleChange}
defaultChecked={queryTimeoutEnabled}
/>
</div>
{queryTimeoutEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="Query timeout (ms)"
labelPosition={Position.top}
defaultValue={(queryTimeout || 5000).toString()}
min={100}
step={1000}
onChange={handleOnQueryTimeoutSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
<Toggle
label="Automatically cancel query after timeout"
styles={toggleStyles}
onChange={handleOnAutomaticallyCancelQueryToggleChange}
defaultChecked={automaticallyCancelQueryAfterTimeout}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="4">
<AccordionHeader>
<div className={styles.header}>RU Threshold</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
If a query exceeds a configured RU threshold, the query will be aborted.
</div>
<Toggle
styles={toggleStyles}
label="Enable RU threshold"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
{ruThresholdEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="RU Threshold (RU)"
labelPosition={Position.top}
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
min={1}
step={1000}
onChange={handleOnRUThresholdSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="5">
<AccordionHeader>
<div className={styles.header}>Default Query Results View</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Select the default view to use when displaying query results.
</div>
<ChoiceGroup
ariaLabelledBy="defaultQueryResultsView"
selectedKey={defaultQueryResultsView}
options={defaultQueryResultsViewOptionList}
styles={choiceButtonStyles}
onChange={handleOnDefaultQueryResultsViewChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
</>
)}
<AccordionItem value="6">
<AccordionHeader>
<div className={styles.header}>Retry Settings</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Retry policy associated with throttled requests during CosmosDB queries.
</div>
<div>
<span className={styles.subHeader}>Max retry attempts</span>
<InfoTooltip className={styles.headerIcon}>
Max number of retries to be performed for a request. Default value 9.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + retryAttempts}
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
styles={spinButtonStyles}
<div className="paneMainContent">
{shouldShowQueryPageOptions && (
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="pageOptions" className="settingsSectionLabel legendLabel">
Page Options
</legend>
<InfoTooltip>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
query results per page.
</InfoTooltip>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
<div>
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
<InfoTooltip className={styles.headerIcon}>
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as
part of the response. Default value is 0 milliseconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1000}
step={1000}
value={"" + retryInterval}
onChange={handleOnRetryIntervalSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Max wait time (s)</span>
<InfoTooltip className={styles.headerIcon}>
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
seconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + MaxWaitTimeInSeconds}
onChange={handleOnMaxWaitTimeSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)}
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)}
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
styles={spinButtonStyles}
/>
</div>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="7">
<AccordionHeader>
<div className={styles.header}>Enable container pagination</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable container pagination"
checked={containerPaginationEnabled}
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
label="Enable container pagination"
/>
</div>
</AccordionPanel>
</AccordionItem>
{shouldShowCrossPartitionOption && (
<AccordionItem value="8">
<AccordionHeader>
<div className={styles.header}>Enable cross-partition query</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Send more than one request while executing a query. More than one request is necessary if the query
is not scoped to single partition key value.
</fieldset>
</div>
<div className="tabs settingsSectionPart">
{isCustomPageOptionSelected() && (
<div className="tabcontent">
<div className="settingsSectionLabel">
Query results per page
<InfoTooltip>Enter the number of query results that should be shown per page.</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable cross partition query"
checked={crossPartitionQueryEnabled}
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
label="Enable cross-partition query"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowParallelismOption && (
<AccordionItem value="9">
<AccordionHeader>
<div className={styles.header}>Max degree of parallelism</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Gets or sets the number of concurrent operations run client side during parallel query execution. A
positive property value limits the number of concurrent operations to the set value. If it is set to
less than 0, the system automatically decides the number of concurrent operations to run.
</div>
<SpinButton
min={-1}
ariaLabel="Custom query items per page"
value={"" + customItemPerPage}
onIncrement={(newValue) => {
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
}}
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
min={1}
step={1}
className="textfontclr"
role="textbox"
id="max-degree"
value={"" + maxDegreeOfParallelism}
onIncrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)
}
onDecrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
}
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
ariaLabel="Max degree of parallelism"
label="Max degree of parallelism"
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowPriorityLevelOption && (
<AccordionItem value="10">
<AccordionHeader>
<div className={styles.header}>Priority Level</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
Execution. If &quot;None&quot; is selected, Data Explorer will not specify priority level, and the
server-side default priority level will be used.
)}
</div>
</div>
)}
{userContext.apiType === "SQL" && (
<>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="ruThresholdLabel" className="settingsSectionLabel legendLabel">
RU Threshold
</legend>
<InfoTooltip>If a query exceeds a configured RU threshold, the query will be aborted.</InfoTooltip>
</div>
<div>
<Toggle
styles={toggleStyles}
label="Enable RU threshold"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
{ruThresholdEnabled && (
<div>
<SpinButton
label="RU Threshold (RU)"
labelPosition={Position.top}
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
min={1}
step={1000}
onChange={handleOnRUThresholdSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
</div>
)}
</div>
</div>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="queryTimeoutLabel" className="settingsSectionLabel legendLabel">
Query Timeout
</legend>
<InfoTooltip>
When a query reaches a specified time limit, a popup with an option to cancel the query will show
unless automatic cancellation has been enabled
</InfoTooltip>
</div>
<div>
<Toggle
styles={toggleStyles}
label="Enable query timeout"
onChange={handleOnQueryTimeoutToggleChange}
defaultChecked={queryTimeoutEnabled}
/>
</div>
{queryTimeoutEnabled && (
<div>
<SpinButton
label="Query timeout (ms)"
labelPosition={Position.top}
defaultValue={(queryTimeout || 5000).toString()}
min={100}
step={1000}
onChange={handleOnQueryTimeoutSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
<Toggle
label="Automatically cancel query after timeout"
styles={toggleStyles}
onChange={handleOnAutomaticallyCancelQueryToggleChange}
defaultChecked={automaticallyCancelQueryAfterTimeout}
/>
</div>
)}
</div>
</div>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="defaultQueryResultsView" className="settingsSectionLabel legendLabel">
Default Query Results View
</legend>
<InfoTooltip>Select the default view to use when displaying query results.</InfoTooltip>
</div>
<div>
<ChoiceGroup
ariaLabelledBy="priorityLevel"
selectedKey={priorityLevel}
options={priorityLevelOptionList}
ariaLabelledBy="defaultQueryResultsView"
selectedKey={defaultQueryResultsView}
options={defaultQueryResultsViewOptionList}
styles={choiceButtonStyles}
onChange={handleOnPriorityLevelOptionChange}
onChange={handleOnDefaultQueryResultsViewChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowGraphAutoVizOption && (
<AccordionItem value="11">
<AccordionHeader>
<div className={styles.header}>Display Gremlin query results as:&nbsp;</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Select Graph to automatically visualize the query results as a Graph or JSON to display the results
as JSON.
</div>
<ChoiceGroup
selectedKey={graphAutoVizDisabled}
options={graphAutoOptionList}
onChange={handleOnGremlinChange}
aria-label="Graph Auto-visualization"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowCopilotSampleDBOption && (
<AccordionItem value="12">
<AccordionHeader>
<div className={styles.header}>Enable sample database</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
This is a sample database and collection with synthetic product data you can use to explore using
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
is created by, and maintained by Microsoft at no cost to you.
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable sample db for Query Advisor"
checked={copilotSampleDBEnabled}
onChange={handleSampleDatabaseChange}
label="Enable sample database"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
</Accordion>
</div>
</div>
</>
)}
<div className="settingsSection">
<div className="settingsSectionPart">
<DefaultButton
onClick={() => {
useDialog.getState().showOkCancelModalDialog(
"Clear History",
undefined,
"Are you sure you want to proceed?",
() => deleteAllStates(),
"Cancel",
undefined,
<>
<span>
This action will clear the all customizations for this account in this browser, including:
</span>
<ul className={styles.bulletList}>
<li>Reset your customized tab layout, including the splitter positions</li>
<li>Erase your table column preferences, including any custom columns</li>
<li>Clear your filter history</li>
</ul>
</>,
);
}}
>
Clear History
</DefaultButton>
<div className="settingsSectionLabel">
Retry Settings
<InfoTooltip>Retry policy associated with throttled requests during CosmosDB queries.</InfoTooltip>
</div>
<div>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Max retry attempts
</legend>
<InfoTooltip>Max number of retries to be performed for a request. Default value 9.</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + retryAttempts}
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
styles={spinButtonStyles}
/>
<div>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Fixed retry interval (ms)
</legend>
<InfoTooltip>
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part
of the response. Default value is 0 milliseconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1000}
step={1000}
value={"" + retryInterval}
onChange={handleOnRetryIntervalSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
styles={spinButtonStyles}
/>
<div>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Max wait time (s)
</legend>
<InfoTooltip>
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
seconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + MaxWaitTimeInSeconds}
onChange={handleOnMaxWaitTimeSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)}
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)}
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
styles={spinButtonStyles}
/>
</div>
</div>
<div className="settingsSection">
<div className="settingsSectionPart settingsSectionInlineCheckbox">
<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 settingsSectionInlineCheckbox">
<div className="settingsSectionLabel">
Enable cross-partition query
<InfoTooltip>
Send more than one request while executing a query. More than one request is necessary if the query is
not scoped to single partition key value.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable cross partition query"
checked={crossPartitionQueryEnabled}
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
/>
</div>
</div>
)}
{shouldShowParallelismOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Max degree of parallelism
<InfoTooltip>
Gets or sets the number of concurrent operations run client side during parallel query execution. A
positive property value limits the number of concurrent operations to the set value. If it is set to
less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
</div>
<SpinButton
min={-1}
step={1}
className="textfontclr"
role="textbox"
id="max-degree"
value={"" + maxDegreeOfParallelism}
onIncrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)}
onDecrement={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)}
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
ariaLabel="Max degree of parallelism"
/>
</div>
</div>
)}
{shouldShowPriorityLevelOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="priorityLevel" className="settingsSectionLabel legendLabel">
Priority Level
</legend>
<InfoTooltip>
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
Execution. If &quot;None&quot; is selected, Data Explorer will not specify priority level, and the
server-side default priority level will be used.
</InfoTooltip>
<ChoiceGroup
ariaLabelledBy="priorityLevel"
selectedKey={priorityLevel}
options={priorityLevelOptionList}
styles={choiceButtonStyles}
onChange={handleOnPriorityLevelOptionChange}
/>
</fieldset>
</div>
</div>
)}
{shouldShowGraphAutoVizOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Display Gremlin query results as:&nbsp;
<InfoTooltip>
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
JSON.
</InfoTooltip>
</div>
<ChoiceGroup
selectedKey={graphAutoVizDisabled}
options={graphAutoOptionList}
onChange={handleOnGremlinChange}
aria-label="Graph Auto-visualization"
/>
</div>
</div>
)}
{shouldShowCopilotSampleDBOption && (
<div className="settingsSection">
<div className="settingsSectionPart settingsSectionInlineCheckbox">
<div className="settingsSectionLabel">
Enable sample database
<InfoTooltip>
This is a sample database and collection with synthetic product data you can use to explore using
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is
created by, and maintained by Microsoft at no cost to you.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable sample db for Query Advisor"
checked={copilotSampleDBEnabled}
onChange={handleSampleDatabaseChange}
/>
</div>
</div>
)}
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">Explorer Version</div>

Some files were not shown because too many files have changed in this diff Show More