Compare commits

..

1 Commits

Author SHA1 Message Date
Asier Isayas
12442fadfb Mongo Shell and MS Graph changes (no RBAC functionality) 2024-07-11 17:12:28 -04:00
252 changed files with 30238 additions and 30231 deletions

View File

@@ -1,16 +0,0 @@
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
# Install pre-reqs for gyp, and 'canvas' npm module
RUN apt-get update && \
apt-get install -y \
make \
gcc \
g++ \
python3-minimal \
libcairo2-dev \
libpango1.0-dev \
&& \
rm -rf /var/lib/apt/lists/*
# Install node-gyp to build native modules
RUN npm install -g node-gyp

View File

@@ -1,32 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Azure Cosmos DB Explorer",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "Dockerfile"
},
"onCreateCommand": ".devcontainer/oncreate",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "latest"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"installDirectlyFromGitHubRelease": true,
"version": "latest"
},
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
# Install packages once, to prime the node_modules directory.
npm ci

5
.npmrc
View File

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

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-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-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", "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 // 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"], snapshotSerializers: ["enzyme-to-json/serializer"],
// The test environment that will be used for testing // The test environment that will be used for testing
testEnvironment: "jsdom", // testEnvironment: "jest-environment-jsdom",
modulePaths: ["node_modules", "<rootDir>/src"], modulePaths: ["node_modules", "<rootDir>/src"],
// Options that will be passed to the testEnvironment // Options that will be passed to the testEnvironment
@@ -158,7 +157,7 @@ module.exports = {
// testResultsProcessor: "./trxProcessor.js", // testResultsProcessor: "./trxProcessor.js",
// This option allows use of a custom test runner // 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 // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost", // testURL: "http://localhost",
@@ -168,13 +167,13 @@ module.exports = {
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
transform: { transform: {
"^.+\\.html?$": "jest-html-loader", "^.+\\.html?$": "html-loader-jest",
"^.+\\.[t|j]sx?$": "babel-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 // 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 // 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, // unmockedModulePathPatterns: undefined,
@@ -187,7 +186,4 @@ module.exports = {
// Whether to use watchman for file crawling // Whether to use watchman for file crawling
// watchman: true, // 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; @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; @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; @FabricAccentMediumHigh: #0c695a;
@FabricAccentMedium: #117865; @FabricAccentMedium: #117865;

View File

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

View File

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

View File

@@ -3,6 +3,19 @@
.dataResourceTree { .dataResourceTree {
margin-left: @MediumSpace; margin-left: @MediumSpace;
overflow: auto; overflow: auto;
.databaseHeader {
padding: 1px;
font-size: 14px;
}
.collectionHeader {
font-size: 12px;
}
.loadMoreHeader {
color: RGB(5, 99, 193);
}
} }
.notebookResourceTree { .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", "@azure/msal-browser": "2.14.2",
"@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.119.0", "@fluentui/react": "8.112.1",
"@fluentui/react-components": "9.54.2", "@fluentui/react-components": "9.34.0",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3", "@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1", "@microsoft/applicationinsights-web": "2.6.1",
"@nteract/commutable": "7.5.1", "@nteract/commutable": "7.5.1",
"@nteract/connected-components": "6.8.2", "@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.9", "@nteract/core": "15.1.0",
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6", "@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1", "@nteract/dropdown-menu": "1.0.1",
@@ -42,15 +42,15 @@
"@nteract/transform-vega": "7.0.6", "@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2", "@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3", "@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/lodash": "4.14.171",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"@uiw/react-split": "5.9.3",
"@xmldom/xmldom": "0.7.13", "@xmldom/xmldom": "0.7.13",
"allotment": "1.20.2",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "2.11.2", "canvas": "file:./canvas",
"clean-webpack-plugin": "4.0.0", "clean-webpack-plugin": "4.0.0",
"clipboard-copy": "4.0.1", "clipboard-copy": "4.0.1",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
@@ -67,7 +67,7 @@
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.33.2",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.0.0-rc.5",
"i18next": "23.11.5", "i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1", "i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23", "i18next-http-backend": "1.0.23",
"iframe-resizer-react": "1.1.0", "iframe-resizer-react": "1.1.0",
@@ -93,13 +93,13 @@
"react-dnd-html5-backend": "14.0.0", "react-dnd-html5-backend": "14.0.0",
"react-dom": "16.14.0", "react-dom": "16.14.0",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-i18next": "14.1.2", "react-i18next": "11.8.5",
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.1.3",
"react-splitter-layout": "4.0.0", "react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1", "react-string-format": "1.0.1",
"react-window": "1.8.10",
"react-youtube": "9.0.1", "react-youtube": "9.0.1",
"react-window": "1.8.10",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
"sanitize-html": "2.3.3", "sanitize-html": "2.3.3",
@@ -113,10 +113,10 @@
"zustand": "3.5.0" "zustand": "3.5.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.7", "@babel/core": "7.9.0",
"@babel/preset-env": "7.24.7", "@babel/preset-env": "7.9.0",
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.24.7", "@babel/preset-typescript": "7.9.0",
"@playwright/test": "1.44.0", "@playwright/test": "1.44.0",
"@testing-library/react": "11.2.3", "@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
@@ -129,13 +129,13 @@
"@types/enzyme": "3.10.12", "@types/enzyme": "3.10.12",
"@types/enzyme-adapter-react-16": "1.0.9", "@types/enzyme-adapter-react-16": "1.0.9",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "29.5.12", "@types/jest": "26.0.20",
"@types/jquery": "3.5.29", "@types/jquery": "3.5.29",
"@types/node": "12.11.1", "@types/node": "12.11.1",
"@types/post-robot": "10.0.1", "@types/post-robot": "10.0.1",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "17.0.44", "@types/react": "17.0.3",
"@types/react-dom": "17.0.15", "@types/react-dom": "17.0.3",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1", "@types/react-splitter-layout": "3.0.1",
@@ -148,7 +148,7 @@
"@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4", "@typescript-eslint/parser": "6.7.4",
"@webpack-cli/serve": "2.0.5", "@webpack-cli/serve": "2.0.5",
"babel-jest": "29.7.0", "babel-jest": "24.9.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"buffer": "5.1.0", "buffer": "5.1.0",
"case-sensitive-paths-webpack-plugin": "2.4.0", "case-sensitive-paths-webpack-plugin": "2.4.0",
@@ -165,15 +165,13 @@
"fast-glob": "3.2.5", "fast-glob": "3.2.5",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.2", "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", "html-webpack-plugin": "5.5.3",
"jest": "29.7.0", "jest": "26.6.3",
"jest-canvas-mock": "2.5.2", "jest-canvas-mock": "2.3.1",
"jest-circus": "29.7.0",
"jest-html-loader": "1.0.0",
"jest-react-hooks-shallow": "1.5.1", "jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "3.0.2", "jest-trx-results-processor": "0.0.7",
"jest-environment-jsdom": "29.7.0",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "11.1.3", "less-loader": "11.1.3",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
@@ -189,8 +187,8 @@
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"ts-loader": "9.2.4", "ts-loader": "9.2.4",
"typedoc": "0.26.2", "typedoc": "0.22.15",
"typescript": "4.9.5", "typescript": "4.3.5",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"wait-on": "4.0.2", "wait-on": "4.0.2",
"webpack": "5.88.2", "webpack": "5.88.2",
@@ -247,4 +245,4 @@
"printWidth": 120, "printWidth": 120,
"endOfLine": "auto" "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. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
testDir: "test", testDir: 'test',
fullyParallel: true, fullyParallel: true,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 0, retries: process.env.CI ? 3 : 0,
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? "blob" : "html", reporter: process.env.CI ? 'blob' : 'html',
timeout: 10 * 60 * 1000, timeout: 10 * 60 * 1000,
use: { use: {
trace: "off", actionTimeout: 5 * 60 * 1000,
video: "off", trace: 'off',
screenshot: "on", video: 'off',
testIdAttribute: "data-test", screenshot: 'on',
testIdAttribute: 'data-test',
contextOptions: { contextOptions: {
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
}, },
}, },
expect: { expect: {
// Many of our expectations take a little longer than the default 5 seconds. timeout: 5 * 60 * 1000,
timeout: 15 * 1000,
}, },
projects: [ projects: [
{ {
name: "chromium", name: 'chromium',
use: { ...devices["Desktop Chrome"] }, use: { ...devices['Desktop Chrome'] },
}, },
{ {
name: "firefox", name: 'firefox',
use: { ...devices["Desktop Firefox"] }, use: { ...devices['Desktop Firefox'] },
}, },
{ {
name: "webkit", name: 'webkit',
use: { ...devices["Desktop Safari"] }, 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'
}, },
], ],
webServer: { webServer: {
command: "npm run start", command: 'npm run start',
url: "https://127.0.0.1:1234/_ready", url: 'https://127.0.0.1:1234/_ready',
timeout: 120 * 1000, timeout: 120 * 1000,
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
reuseExistingServer: !process.env.CI, 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

@@ -134,9 +134,6 @@ export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken"; public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings"; public static readonly PortalSettings: string = "PortalSettings";
public static readonly AccountRestrictions: string = "AccountRestrictions"; public static readonly AccountRestrictions: string = "AccountRestrictions";
public static readonly RuntimeProxy: string = "RuntimeProxy";
public static readonly DisallowedLocations: string = "DisallowedLocations";
public static readonly SampleData: string = "SampleData";
} }
export class PortalBackendEndpoints { export class PortalBackendEndpoints {
@@ -186,12 +183,6 @@ export class CassandraProxyAPIs {
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema"; 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 { export class Queries {
public static CustomPageOption: string = "custom"; public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited"; public static UnlimitedPageOption: string = "unlimited";
@@ -206,12 +197,6 @@ export class Queries {
public static readonly DefaultMaxWaitTimeInSeconds = 30; 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 { export class SavedQueries {
public static readonly CollectionName: string = "___Query"; public static readonly CollectionName: string = "___Query";
public static readonly DatabaseName: string = "___Cosmos"; public static readonly DatabaseName: string = "___Cosmos";
@@ -293,7 +278,6 @@ export class HttpStatusCodes {
public static readonly Accepted: number = 202; public static readonly Accepted: number = 202;
public static readonly NoContent: number = 204; public static readonly NoContent: number = 204;
public static readonly NotModified: number = 304; public static readonly NotModified: number = 304;
public static readonly BadRequest: number = 400;
public static readonly Unauthorized: number = 401; public static readonly Unauthorized: number = 401;
public static readonly Forbidden: number = 403; public static readonly Forbidden: number = 403;
public static readonly NotFound: number = 404; public static readonly NotFound: number = 404;
@@ -505,7 +489,7 @@ export class PriorityLevel {
public static readonly Default = "low"; public static readonly Default = "low";
} }
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB"; export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
export const QueryCopilotSampleContainerId = "SampleContainer"; export const QueryCopilotSampleContainerId = "SampleContainer";
export const QueryCopilotSampleContainerSchema = { export const QueryCopilotSampleContainerSchema = {

View File

@@ -1,6 +1,47 @@
import { ResourceType } from "@azure/cosmos";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext"; import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext"; 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", () => { describe("getTokenFromAuthService", () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -3,10 +3,8 @@ import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizatio
import { AuthorizationToken } from "Contracts/FabricMessageTypes"; import { AuthorizationToken } from "Contracts/FabricMessageTypes";
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { BackendApi, PriorityLevel } from "../Common/Constants"; import { PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger";
import { Platform, configContext } from "../ConfigContext"; import { Platform, configContext } from "../ConfigContext";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
@@ -19,18 +17,7 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo; const { verb, resourceId, resourceType, headers } = requestInfo;
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL"; if (userContext.features.enableAadDataPlane && userContext.aadToken) {
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;
}
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`; const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`; const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return authorizationToken; return authorizationToken;
@@ -84,15 +71,8 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
} }
if (userContext.masterKey) { 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. // TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey( await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
verb,
resourceId,
resourceType,
headers,
userContext.masterKey,
);
return decodeURIComponent(headers.authorization); return decodeURIComponent(headers.authorization);
} }
@@ -124,37 +104,6 @@ export async function getTokenFromAuthService(
verb: string, verb: string,
resourceType: string, resourceType: string,
resourceId?: 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> { ): Promise<AuthorizationToken> {
try { try {
const host = configContext.BACKEND_ENDPOINT; const host = configContext.BACKEND_ENDPOINT;
@@ -188,11 +137,8 @@ enum SDKSupportedCapabilities {
let _client: Cosmos.CosmosClient; let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient { export function client(): Cosmos.CosmosClient {
if (_client) { if (_client) return _client;
if (!userContext.hasDataPlaneRbacSettingChanged) {
return _client;
}
}
let _defaultHeaders: Cosmos.CosmosHeaders = {}; let _defaultHeaders: Cosmos.CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] = _defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge; SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
@@ -211,7 +157,7 @@ export function client(): Cosmos.CosmosClient {
const options: Cosmos.CosmosClientOptions = { 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 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, tokenProvider,
userAgentSuffix: "Azure Portal", userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders, defaultHeaders: _defaultHeaders,

View File

@@ -1,5 +1,3 @@
import { PortalBackendEndpoints } from "Common/Constants";
import { updateConfigContext } from "ConfigContext";
import * as EnvironmentUtility from "./EnvironmentUtility"; import * as EnvironmentUtility from "./EnvironmentUtility";
describe("Environment Utility Test", () => { describe("Environment Utility Test", () => {
@@ -13,18 +11,4 @@ describe("Environment Utility Test", () => {
const expectedResult = "test/"; const expectedResult = "test/";
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult); expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
}); });
it("Detect environment is Mpac", () => {
updateConfigContext({
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
});
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Mpac);
});
it("Detect environment is Development", () => {
updateConfigContext({
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Development,
});
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Development);
});
}); });

View File

@@ -1,29 +1,6 @@
import { PortalBackendEndpoints } from "Common/Constants";
import { configContext } from "ConfigContext";
export function normalizeArmEndpoint(uri: string): string { export function normalizeArmEndpoint(uri: string): string {
if (uri && uri.slice(-1) !== "/") { if (uri && uri.slice(-1) !== "/") {
return `${uri}/`; return `${uri}/`;
} }
return uri; return uri;
} }
export enum Environment {
Development = "Development",
Mpac = "MPAC",
Prod = "Prod",
Fairfax = "Fairfax",
Mooncake = "Mooncake",
}
export const getEnvironment = (): Environment => {
const environmentMap: { [key: string]: Environment } = {
[PortalBackendEndpoints.Development]: Environment.Development,
[PortalBackendEndpoints.Mpac]: Environment.Mpac,
[PortalBackendEndpoints.Prod]: Environment.Prod,
[PortalBackendEndpoints.Fairfax]: Environment.Fairfax,
[PortalBackendEndpoints.Mooncake]: Environment.Mooncake,
};
return environmentMap[configContext.PORTAL_BACKEND_ENDPOINT];
};

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."; return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
} else if ( } else if (
errorMessage?.indexOf("The user aborted a request") >= 0 || errorMessage?.indexOf("The user aborted a request") >= 0 ||
errorMessage?.indexOf("The operation was aborted") >= 0 || errorMessage?.indexOf("The operation was aborted") >= 0
errorMessage === "signal is aborted without reason"
) { ) {
return "User aborted query."; return "User aborted query.";
} }

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( export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams, params: DataModels.CreateCollectionParams,
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
@@ -721,21 +678,22 @@ export function useMongoProxyEndpoint(api: string): boolean {
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, 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 ( return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) && configContext.NEW_MONGO_APIS?.includes(api) &&
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT) activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
); );
} }
export class ThrottlingError extends Error {
constructor(message: string) {
super(message);
}
}
// TODO: This function throws most of the time except on Forbidden which is a bit strange // TODO: This function throws most of the time except on Forbidden which is a bit strange
// It causes problems for TypeScript understanding the types // It causes problems for TypeScript understanding the types
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> { async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
@@ -745,14 +703,6 @@ async function errorHandling(response: Response, action: string, params: unknown
if (response.status === HttpStatusCodes.Forbidden) { if (response.status === HttpStatusCodes.Forbidden) {
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage }); sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return; return;
} else if (
response.status === HttpStatusCodes.BadRequest &&
errorMessage.includes("Error=16500") &&
errorMessage.includes("RetryAfterMs=")
) {
// If throttling is happening, Cosmos DB will return a 400 with a body of:
// A write operation resulted in an error. Error=16500, RetryAfterMs=4, Details='Batch write error.
throw new ThrottlingError(errorMessage);
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }

View File

@@ -0,0 +1,39 @@
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
const notificationsPath = () => {
switch (configContext.platform) {
case Platform.Hosted:
return "/api/guest/notifications";
case Platform.Portal:
return "/api/notifications";
default:
throw new Error(`Unknown platform: ${configContext.platform}`);
}
};
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) {
return [];
}
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, {
headers,
});
if (!response.ok) {
throw new Error(await response.text());
}
return (await response.json()) as DataModels.Notification[];
};

View File

@@ -1,94 +0,0 @@
import QueryError, { QueryErrorLocation, QueryErrorSeverity } from "Common/QueryError";
describe("QueryError.tryParse", () => {
const testErrorLocationResolver = ({ start, end }: { start: number; end: number }) =>
new QueryErrorLocation(
{ offset: start, lineNumber: start, column: start },
{ offset: end, lineNumber: end, column: end },
);
it("handles a string error", () => {
const error = "error";
const result = QueryError.tryParse(error, testErrorLocationResolver);
expect(result).toEqual([new QueryError("error", QueryErrorSeverity.Error)]);
});
it("handles an error object", () => {
const error = {
message: "error",
severity: "Warning",
location: { start: 0, end: 1 },
code: "code",
};
const result = QueryError.tryParse(error, testErrorLocationResolver);
expect(result).toEqual([
new QueryError(
"error",
QueryErrorSeverity.Warning,
"code",
new QueryErrorLocation({ offset: 0, lineNumber: 0, column: 0 }, { offset: 1, lineNumber: 1, column: 1 }),
),
]);
});
it("handles a JSON message without syntax errors", () => {
const innerError = {
code: "BadRequest",
message: "Your query is bad, and you should feel bad",
};
const message = JSON.stringify(innerError);
const outerError = {
code: "BadRequest",
message,
};
const result = QueryError.tryParse(outerError, testErrorLocationResolver);
expect(result).toEqual([
new QueryError("Your query is bad, and you should feel bad", QueryErrorSeverity.Error, "BadRequest"),
]);
});
// Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message.
it("handles single-nested error", () => {
const errors = [
{
message: "error1",
severity: "Warning",
location: { start: 0, end: 1 },
code: "code1",
},
{
message: "error2",
severity: "Error",
location: { start: 2, end: 3 },
code: "code2",
},
];
const innerError = {
code: "BadRequest",
message: "Your query is bad, and you should feel bad",
errors,
};
const message = JSON.stringify(innerError);
const outerError = {
code: "BadRequest",
message,
};
const result = QueryError.tryParse(outerError, testErrorLocationResolver);
expect(result).toEqual([
new QueryError(
"error1",
QueryErrorSeverity.Warning,
"code1",
new QueryErrorLocation({ offset: 0, lineNumber: 0, column: 0 }, { offset: 1, lineNumber: 1, column: 1 }),
),
new QueryError(
"error2",
QueryErrorSeverity.Error,
"code2",
new QueryErrorLocation({ offset: 2, lineNumber: 2, column: 2 }, { offset: 3, lineNumber: 3, column: 3 }),
),
]);
});
});

View File

@@ -1,247 +0,0 @@
import { monaco } from "Explorer/LazyMonaco";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
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 interface ErrorEnrichment {
title?: string;
message: string;
learnMoreUrl?: string;
}
const REPLACEMENT_MESSAGES: Record<string, (original: string) => string> = {
OPERATION_RU_LIMIT_EXCEEDED: (original) => {
if (ruThresholdEnabled()) {
const threshold = getRUThreshold();
return `Query exceeded the Request Unit (RU) limit of ${threshold} RUs. You can change this limit in Data Explorer settings.`;
}
return original;
},
};
const HELP_LINKS: Record<string, string> = {
OPERATION_RU_LIMIT_EXCEEDED:
"https://learn.microsoft.com/en-us/azure/cosmos-db/data-explorer#configure-request-unit-threshold",
};
export default class QueryError {
message: string;
helpLink?: string;
constructor(
message: string,
public severity: QueryErrorSeverity,
public code?: string,
public location?: QueryErrorLocation,
helpLink?: string,
) {
// Automatically replace the message with a more Data Explorer-specific message if we have for this error code.
this.message = REPLACEMENT_MESSAGES[code] ? REPLACEMENT_MESSAGES[code](message) : message;
// Automatically set the help link if we have one for this error code.
this.helpLink = helpLink ?? HELP_LINKS[code];
}
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 = error as string;
// 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)
: QueryErrorSeverity.Error;
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 {
let message: string | undefined;
if (typeof error === "object" && "message" in error && typeof error.message === "string") {
message = error.message;
} else {
// Unsupported error format.
return null;
}
// Assign to a new variable because of a TypeScript flow typing quirk, see below.
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) {
// The message doesn't contain a nested error.
return [QueryError.read(error, locationResolver)];
}
if (typeof parsed === "object") {
if ("errors" in parsed && Array.isArray(parsed.errors)) {
return parsed.errors.map((e) => QueryError.read(e, locationResolver)).filter((e) => e !== null);
}
return [QueryError.read(parsed, locationResolver)];
}
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 { export interface TooltipProps {
children: string; children: string;
className?: string;
} }
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => { export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
return ( return (
<span className={className}> <span>
<TooltipHost content={children}> <TooltipHost content={children}>
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
</TooltipHost> </TooltipHost>

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases"; import { useDatabases } from "../../Explorer/useDatabases";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getDatabaseName } from "../../Utils/APITypeUtils"; import { getDatabaseName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
@@ -16,6 +15,7 @@ import {
MongoDBDatabaseCreateUpdateParameters, MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters, SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -152,18 +152,8 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
createBody.throughput = params.offerThroughput; createBody.throughput = params.offerThroughput;
} }
} }
let response: DatabaseResponse;
try { const response: DatabaseResponse = await client().databases.create(createBody);
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;
}
}
return response.resource; return response.resource;
} }

View File

@@ -26,23 +26,14 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
} }
}; };
export interface IBulkDeleteResult {
documentId: DocumentId;
requestCharge: number;
statusCode: number;
retryAfterMilliseconds?: number;
}
/** /**
* Bulk delete documents * Bulk delete documents
* @param collection * @param collection
* @param documentId * @param documentId
* @returns array of results and status codes * @returns array of ids that were successfully deleted
*/ */
export const deleteDocuments = async ( export const deleteDocuments = async (collection: CollectionBase, documentIds: DocumentId[]): Promise<DocumentId[]> => {
collection: CollectionBase, const nbDocuments = documentIds.length;
documentIds: DocumentId[],
): Promise<IBulkDeleteResult[]> => {
const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`); const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`);
try { try {
const v2Container = await client().database(collection.databaseId).container(collection.id()); const v2Container = await client().database(collection.databaseId).container(collection.id());
@@ -65,17 +56,18 @@ export const deleteDocuments = async (
operationType: BulkOperationType.Delete, operationType: BulkOperationType.Delete,
})); }));
const promise = v2Container.items.bulk(operations).then((bulkResults) => { const promise = v2Container.items.bulk(operations).then((bulkResult) => {
return bulkResults.map((bulkResult, index) => { return documentIdsChunk.filter((_, index) => bulkResult[index].statusCode === 204);
const documentId = documentIdsChunk[index];
return { ...bulkResult, documentId };
});
}); });
promiseArray.push(promise); promiseArray.push(promise);
} }
const allResult = await Promise.all(promiseArray); const allResult = await Promise.all(promiseArray);
const flatAllResult = Array.prototype.concat.apply([], allResult); const flatAllResult = Array.prototype.concat.apply([], allResult);
logConsoleInfo(
`Successfully deleted ${getEntityName(flatAllResult.length > 1)}: ${flatAllResult.length} out of ${nbDocuments}`,
);
// TODO: handle case result.length != nbDocuments
return flatAllResult; return flatAllResult;
} catch (error) { } catch (error) {
handleError( handleError(

View File

@@ -49,12 +49,14 @@ export interface ConfigContext {
ARCADIA_ENDPOINT: string; ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string; BACKEND_ENDPOINT?: string;
PORTAL_BACKEND_ENDPOINT: string; PORTAL_BACKEND_ENDPOINT?: string;
NEW_BACKEND_APIS?: BackendApi[]; NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string; MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT: string; MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[]; NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT: string; CASSANDRA_PROXY_ENDPOINT?: string;
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
NEW_CASSANDRA_APIS?: string[]; NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string; PROXY_PATH?: string;
JUNO_ENDPOINT: string; JUNO_ENDPOINT: string;
@@ -85,7 +87,7 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/.*\\.analysis-df\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.net$`,
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
`^https:\\/\\/.*\\.azure-test\\.net$`, `^https:\\/\\/.*\\.azure-test\\.net$`,
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net$`, `^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`,
], // Webpack injects this at build time ], // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/", hostedExplorerURL: "https://cosmos.azure.com/",
@@ -107,18 +109,19 @@ let configContext: Readonly<ConfigContext> = {
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [ NEW_MONGO_APIS: [
"resourcelist", // "resourcelist",
"queryDocuments", // "queryDocuments",
"createDocument", // "createDocument",
"readDocument", // "readDocument",
"updateDocument", // "updateDocument",
"deleteDocument", // "deleteDocument",
"createCollectionWithProxy", // "createCollectionWithProxy",
"legacyMongoShell", // "legacyMongoShell",
// "bulkdelete",
], ],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"], NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false, isTerminalEnabled: false,
isPhoenixEnabled: false, isPhoenixEnabled: false,
}; };

View File

@@ -98,6 +98,7 @@ export interface Database extends TreeNode {
openAddCollection(database: Database, event: MouseEvent): void; openAddCollection(database: Database, event: MouseEvent): void;
onSettingsClick: () => void; onSettingsClick: () => void;
loadOffer(): Promise<void>; loadOffer(): Promise<void>;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
} }
export interface CollectionBase extends TreeNode { export interface CollectionBase extends TreeNode {
@@ -190,6 +191,8 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>; uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
} }
/** /**

View File

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

View File

@@ -96,8 +96,7 @@ export class Heatmap {
return output; return output;
} }
// public for testing purposes private _getChartSettings(): ChartSettings[] {
public _getChartSettings(): ChartSettings[] {
return [ return [
{ {
z: this._chartData.dataPoints, z: this._chartData.dataPoints,
@@ -132,8 +131,7 @@ export class Heatmap {
]; ];
} }
// public for testing purposes private _getLayoutSettings(): LayoutSettings {
public _getLayoutSettings(): LayoutSettings {
return { return {
margin: { margin: {
l: 40, l: 40,
@@ -179,8 +177,7 @@ export class Heatmap {
}; };
} }
// public for testing purposes private _getChartDisplaySettings(): DisplaySettings {
public _getChartDisplaySettings(): DisplaySettings {
return { 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 /* 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,*/ responsive: true,*/

View File

@@ -41,10 +41,6 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS) * New resource tree (in ReactJS)
*/ */
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => { export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
return undefined;
}
const items: TreeNodeMenuItem[] = [ const items: TreeNodeMenuItem[] = [
{ {
iconSrc: AddCollectionIcon, 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 ( if (
configContext.platform !== Platform.Fabric && configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin") (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")

View File

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

View File

@@ -1,14 +1,23 @@
/** /**
* React component for Command button component. * React component for Command button component.
*/ */
import Explorer from "Explorer/Explorer";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import * as React from "react"; import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as StringUtils from "../../../Utils/StringUtils";
/** /**
* Options for this component * Options for this component
*/ */
export interface CommandButtonComponentProps { export interface CommandButtonComponentProps {
/**
* font icon name for the button
*/
iconName?: string;
/** /**
* image source for the button icon * image source for the button icon
*/ */
@@ -22,7 +31,7 @@ export interface CommandButtonComponentProps {
/** /**
* Click handler for command button click * Click handler for command button click
*/ */
onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent, container: Explorer) => void; onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void;
/** /**
* Label for the button * Label for the button
@@ -111,3 +120,157 @@ export interface CommandButtonComponentProps {
*/ */
keyboardAction?: KeyboardAction; keyboardAction?: KeyboardAction;
} }
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
private dropdownElt: HTMLElement;
private expandButtonElt: HTMLElement;
public componentDidUpdate(): void {
if (!this.dropdownElt || !this.expandButtonElt) {
return;
}
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
}
private onKeyPress(event: React.KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
this.commandClickCallback && this.commandClickCallback(event);
event.stopPropagation();
return false;
}
return true;
}
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide();
$(this.dropdownElt).show().focus();
event.stopPropagation();
return false;
}
if (event.keyCode === KeyCodes.UpArrow) {
$(this.dropdownElt).hide();
event.stopPropagation();
return false;
}
return true;
}
private getCommandButtonId(): string {
if (this.props.id) {
return this.props.id;
} else {
return `commandButton-${StringUtils.stripSpacesFromString(this.props.commandButtonLabel)}`;
}
}
public static renderButton(options: CommandButtonComponentProps, key?: string): JSX.Element {
return <CommandButtonComponent key={key} {...options} />;
}
private commandClickCallback(e: React.SyntheticEvent): void {
if (this.props.disabled) {
return;
}
// TODO Query component's parent, not document
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
if (el) {
el.style.display = "none";
}
this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel,
});
}
private renderChildren(): JSX.Element {
if (!this.props.children || this.props.children.length < 1) {
return <React.Fragment />;
}
return (
<div
className="commandExpand"
tabIndex={0}
ref={(ref: HTMLElement) => {
this.expandButtonElt = ref;
}}
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
>
<div className="commandDropdownLauncher">
<span className="partialSplitter" />
<span className="expandDropdown">
<img src={CollapseChevronDownIcon} />
</span>
</div>
<div
className="commandDropdownContainer"
ref={(ref: HTMLElement) => {
this.dropdownElt = ref;
}}
>
<div className="commandDropdown">
{this.props.children.map((c: CommandButtonComponentProps, index: number): JSX.Element => {
return CommandButtonComponent.renderButton(c, `${index}`);
})}
</div>
</div>
</div>
);
}
public static renderLabel(
props: CommandButtonComponentProps,
key?: string,
refct?: (input: HTMLElement) => void,
): JSX.Element {
if (!props.commandButtonLabel) {
return <React.Fragment />;
}
return (
<span className="commandLabel" key={key} ref={refct}>
{props.commandButtonLabel}
</span>
);
}
public render(): JSX.Element {
let mainClassName = "commandButtonComponent";
if (this.props.disabled) {
mainClassName += " commandDisabled";
}
if (this.props.isSelected) {
mainClassName += " selectedButton";
}
let contentClassName = "commandContent";
if (this.props.children && this.props.children.length > 0) {
contentClassName += " hasHiddenItems";
}
return (
<div className="commandButtonReact">
<span
className={mainClassName}
role="menuitem"
tabIndex={this.props.tabIndex}
onKeyPress={(e: React.KeyboardEvent<HTMLSpanElement>) => this.onKeyPress(e)}
title={this.props.tooltipText}
id={this.getCommandButtonId()}
aria-disabled={this.props.disabled}
aria-haspopup={this.props.hasPopup}
aria-label={this.props.ariaLabel}
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
>
<div className={contentClassName}>
<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />
{CommandButtonComponent.renderLabel(this.props)}
</div>
</span>
{this.props.children && this.renderChildren()}
</div>
);
}
}

View File

@@ -35,7 +35,7 @@ export interface DialogState {
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean, primaryButtonDisabled?: boolean,
) => void; ) => void;
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void; showOkModalDialog: (title: string, subText: string) => void;
} }
export const useDialog: UseStore<DialogState> = create((set, get) => ({ export const useDialog: UseStore<DialogState> = create((set, get) => ({
@@ -83,7 +83,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
textFieldProps, textFieldProps,
primaryButtonDisabled, primaryButtonDisabled,
}), }),
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps): void => showOkModalDialog: (title: string, subText: string): void =>
get().openDialog({ get().openDialog({
isModal: true, isModal: true,
title, title,
@@ -94,7 +94,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
get().closeDialog(); get().closeDialog();
}, },
onSecondaryButtonClick: undefined, onSecondaryButtonClick: undefined,
linkProps,
}), }),
})); }));

View File

@@ -3,37 +3,6 @@ import * as React from "react";
import { loadMonaco, monaco } from "../../LazyMonaco"; import { loadMonaco, monaco } from "../../LazyMonaco";
// import "./EditorReact.less"; // 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 { interface EditorReactStates {
showEditor: boolean; showEditor: boolean;
} }
@@ -42,7 +11,7 @@ export interface EditorReactProps {
content: string; content: string;
isReadOnly: boolean; isReadOnly: boolean;
ariaLabel: string; // Sets what will be read to the user to define the control 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 onContentChanged?: (newContent: string) => void; // Called when text is changed
theme?: string; // Monaco editor theme theme?: string; // Monaco editor theme
wordWrap?: monaco.editor.IEditorOptions["wordWrap"]; wordWrap?: monaco.editor.IEditorOptions["wordWrap"];
@@ -55,34 +24,12 @@ export interface EditorReactProps {
monacoContainerStyles?: React.CSSProperties; monacoContainerStyles?: React.CSSProperties;
className?: string; className?: string;
spinnerClassName?: 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> { 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; private rootNode: HTMLElement;
public editor: monaco.editor.IStandaloneCodeEditor; private editor: monaco.editor.IStandaloneCodeEditor;
private selectionListener: monaco.IDisposable; 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) { public constructor(props: EditorReactProps) {
super(props); super(props);
@@ -111,7 +58,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
if (this.props.content !== existingContent) { if (this.props.content !== existingContent) {
if (this.props.isReadOnly) { 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 { } else {
this.editor.pushUndoStop(); this.editor.pushUndoStop();
this.editor.executeEdits("", [ 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 { public componentWillUnmount(): void {
@@ -137,7 +82,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} /> <Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
)} )}
<div <div
data-test="EditorReact/Host/Unloaded"
className={this.props.className || "jsonEditor"} className={this.props.className || "jsonEditor"}
style={this.props.monacoContainerStyles} style={this.props.monacoContainerStyles}
ref={(elt: HTMLElement) => this.setRef(elt)} ref={(elt: HTMLElement) => this.setRef(elt)}
@@ -148,18 +92,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor; 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) { if (!this.props.isReadOnly && this.props.onContentChanged) {
// Hooking the model's onDidChangeContent event because of some event ordering issues. // 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), // 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( this.selectionListener = this.editor.onDidChangeCursorSelection(
(event: monaco.editor.ICursorSelectionChangedEvent) => { (event: monaco.editor.ICursorSelectionChangedEvent) => {
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection); 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, lineDecorationsWidth: this.props.lineDecorationsWidth,
minimap: this.props.minimap, minimap: this.props.minimap,
scrollBeyondLastLine: this.props.scrollBeyondLastLine, scrollBeyondLastLine: this.props.scrollBeyondLastLine,
fixedOverflowWidgets: true,
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";
this.monacoApi = await loadMonaco(); const monaco = await loadMonaco();
try { try {
createCallback(this.monacoApi.editor.create(this.rootNode, options)); createCallback(monaco?.editor?.create(this.rootNode, options));
} catch (error) { } catch (error) {
// This could happen if the parent node suddenly disappears during create() // This could happen if the parent node suddenly disappears during create()
console.error("Unable to create EditorReact", error); console.error("Unable to create EditorReact", error);

View File

@@ -18,7 +18,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack <Stack
className="options" className="options"
tokens={ tokens={
{ Object {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -27,7 +27,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true} horizontal={true}
horizontalAlign="space-between" horizontalAlign="space-between"
tokens={ tokens={
{ Object {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -52,7 +52,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true} horizontal={true}
horizontalAlign="start" horizontalAlign="start"
tokens={ tokens={
{ Object {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -61,16 +61,16 @@ exports[`Feature panel renders all flags 1`] = `
label="Base Url" label="Base Url"
onChange={[Function]} onChange={[Function]}
options={ options={
[ Array [
{ Object {
"key": "https://localhost:1234/explorer.html", "key": "https://localhost:1234/explorer.html",
"text": "localhost:1234", "text": "localhost:1234",
}, },
{ Object {
"key": "https://cosmos.azure.com/explorer.html", "key": "https://cosmos.azure.com/explorer.html",
"text": "cosmos.azure.com", "text": "cosmos.azure.com",
}, },
{ Object {
"key": "https://portal.azure.com", "key": "https://portal.azure.com",
"text": "portal", "text": "portal",
}, },
@@ -78,8 +78,8 @@ exports[`Feature panel renders all flags 1`] = `
} }
selectedKey="https://localhost:1234/explorer.html" selectedKey="https://localhost:1234/explorer.html"
styles={ styles={
{ Object {
"dropdown": { "dropdown": Object {
"width": 200, "width": 200,
}, },
} }
@@ -89,20 +89,20 @@ exports[`Feature panel renders all flags 1`] = `
label="Platform" label="Platform"
onChange={[Function]} onChange={[Function]}
options={ options={
[ Array [
{ Object {
"key": "Hosted", "key": "Hosted",
"text": "Hosted", "text": "Hosted",
}, },
{ Object {
"key": "Portal", "key": "Portal",
"text": "Portal", "text": "Portal",
}, },
{ Object {
"key": "Emulator", "key": "Emulator",
"text": "Emulator", "text": "Emulator",
}, },
{ Object {
"key": "", "key": "",
"text": "None", "text": "None",
}, },
@@ -110,8 +110,8 @@ exports[`Feature panel renders all flags 1`] = `
} }
selectedKey="Hosted" selectedKey="Hosted"
styles={ styles={
{ Object {
"dropdown": { "dropdown": Object {
"width": 200, "width": 200,
}, },
} }
@@ -208,7 +208,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
{ Object {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -222,8 +222,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="https://notebookserver" placeholder="https://notebookserver"
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "width": 300,
}, },
} }
@@ -235,8 +235,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "width": 300,
}, },
} }
@@ -248,8 +248,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "width": 300,
}, },
} }
@@ -265,8 +265,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "width": 300,
}, },
} }
@@ -279,8 +279,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="https://localhost:1234/explorer.html" placeholder="https://localhost:1234/explorer.html"
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "width": 300,
}, },
} }
@@ -292,8 +292,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
{ Object {
"fieldGroup": { "fieldGroup": Object {
"width": 300, "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 <StyledDocumentCardBase
aria-label="name" aria-label="name"
styles={ styles={
{ Object {
"root": { "root": Object {
"display": "inline-block", "display": "inline-block",
"marginRight": 20, "marginRight": 20,
"width": 256, "width": 256,
@@ -16,8 +16,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardActivityBase <StyledDocumentCardActivityBase
activity="Invalid Date" activity="Invalid Date"
people={ people={
[ Array [
{ Object {
"name": "author", "name": "author",
"profileImageSrc": false, "profileImageSrc": false,
}, },
@@ -26,8 +26,8 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<StyledDocumentCardPreviewBase <StyledDocumentCardPreviewBase
previewImages={ previewImages={
[ Array [
{ Object {
"height": 144, "height": 144,
"imageFit": 2, "imageFit": 2,
"previewImageSrc": "thumbnailUrl", "previewImageSrc": "thumbnailUrl",
@@ -40,8 +40,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Text <Text
nowrap={true} nowrap={true}
styles={ styles={
{ Object {
"root": { "root": Object {
"height": 18, "height": 18,
"padding": "2px 16px", "padding": "2px 16px",
}, },
@@ -69,15 +69,15 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<span <span
style={ style={
{ Object {
"padding": "8px 16px", "padding": "8px 16px",
} }
} }
> >
<Text <Text
styles={ styles={
{ Object {
"root": { "root": Object {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -88,8 +88,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="RedEye" iconName="RedEye"
styles={ styles={
{ Object {
"root": { "root": Object {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -100,8 +100,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
{ Object {
"root": { "root": Object {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -112,8 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="Download" iconName="Download"
styles={ styles={
{ Object {
"root": { "root": Object {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -124,8 +124,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
{ Object {
"root": { "root": Object {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -136,8 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="Heart" iconName="Heart"
styles={ styles={
{ Object {
"root": { "root": Object {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -151,8 +151,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardDetailsBase> <StyledDocumentCardDetailsBase>
<Separator <Separator
styles={ styles={
{ Object {
"root": { "root": Object {
"height": 1, "height": 1,
"padding": 0, "padding": 0,
}, },
@@ -161,22 +161,22 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<span <span
style={ style={
{ Object {
"padding": "0px 16px", "padding": "0px 16px",
} }
} }
> >
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
{ Object {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Favorite" content="Favorite"
id="TooltipHost-IconButton-Heart" id="TooltipHost-IconButton-Heart"
styles={ styles={
{ Object {
"root": { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "left", "float": "left",
}, },
@@ -186,7 +186,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Favorite" ariaLabel="Favorite"
iconProps={ iconProps={
{ Object {
"iconName": "Heart", "iconName": "Heart",
} }
} }
@@ -196,15 +196,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase> </StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
{ Object {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Download" content="Download"
id="TooltipHost-IconButton-Download" id="TooltipHost-IconButton-Download"
styles={ styles={
{ Object {
"root": { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "left", "float": "left",
}, },
@@ -214,7 +214,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Download" ariaLabel="Download"
iconProps={ iconProps={
{ Object {
"iconName": "Download", "iconName": "Download",
} }
} }
@@ -224,15 +224,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase> </StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
{ Object {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Remove" content="Remove"
id="TooltipHost-IconButton-Delete" id="TooltipHost-IconButton-Delete"
styles={ styles={
{ Object {
"root": { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "right", "float": "right",
}, },
@@ -242,7 +242,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Remove" ariaLabel="Remove"
iconProps={ iconProps={
{ Object {
"iconName": "Delete", "iconName": "Delete",
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,79 +0,0 @@
import {
Button,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
DialogTrigger,
Field,
ProgressBar,
} from "@fluentui/react-components";
import * as React from "react";
interface ProgressModalDialogProps {
isOpen: boolean;
title: string;
message: string;
maxValue: number;
value: number;
dismissText: string;
onDismiss: () => void;
onCancel?: () => void;
/* mode drives the state of the action buttons
* inProgress: Show cancel button
* completed: Show close button
* aborting: Show cancel button, but disabled
* aborted: Show close button
*/
mode?: "inProgress" | "completed" | "aborting" | "aborted";
}
/**
* React component that renders a modal dialog with a progress bar.
*/
export const ProgressModalDialog: React.FC<ProgressModalDialogProps> = ({
isOpen,
title,
message,
maxValue,
value,
dismissText,
onCancel,
onDismiss,
children,
mode = "completed",
}) => (
<Dialog
open={isOpen}
onOpenChange={(event, data) => {
if (!data.open) {
onDismiss();
}
}}
>
<DialogSurface>
<DialogBody>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Field validationMessage={message} validationState="none">
<ProgressBar max={maxValue} value={value} />
</Field>
{children}
</DialogContent>
<DialogActions>
{mode === "inProgress" || mode === "aborting" ? (
<Button appearance="secondary" onClick={onCancel} disabled={mode === "aborting"}>
{dismissText}
</Button>
) : (
<DialogTrigger disableButtonEnhancement>
<Button appearance="primary">Close</Button>
</DialogTrigger>
)}
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
);

View File

@@ -134,6 +134,7 @@ describe("SettingsComponent", () => {
readSettings: undefined, readSettings: undefined,
onSettingsClick: undefined, onSettingsClick: undefined,
loadOffer: undefined, loadOffer: undefined,
getPendingThroughputSplitNotification: undefined,
} as ViewModels.Database; } as ViewModels.Database;
newCollection.getDatabase = () => newDatabase; newCollection.getDatabase = () => newDatabase;
newCollection.offer = ko.observable(undefined); newCollection.offer = ko.observable(undefined);

View File

@@ -7,7 +7,6 @@ import {
ContainerVectorPolicyComponent, ContainerVectorPolicyComponent,
ContainerVectorPolicyComponentProps, ContainerVectorPolicyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
@@ -33,6 +32,7 @@ import {
PartitionKeyComponent, PartitionKeyComponent,
PartitionKeyComponentProps, PartitionKeyComponentProps,
} from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent"; } from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils"; import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
@@ -130,6 +130,7 @@ export interface SettingsComponentState {
conflictResolutionPolicyProcedureBaseline: string; conflictResolutionPolicyProcedureBaseline: string;
isConflictResolutionDirty: boolean; isConflictResolutionDirty: boolean;
initialNotification: DataModels.Notification;
selectedTab: SettingsV2TabTypes; selectedTab: SettingsV2TabTypes;
} }
@@ -228,6 +229,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: undefined, conflictResolutionPolicyProcedureBaseline: undefined,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
initialNotification: undefined,
selectedTab: SettingsV2TabTypes.ScaleTab, selectedTab: SettingsV2TabTypes.ScaleTab,
}; };
@@ -1050,6 +1052,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange, onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
onScaleSaveableChange: this.onScaleSaveableChange, onScaleSaveableChange: this.onScaleSaveableChange,
onScaleDiscardableChange: this.onScaleDiscardableChange, onScaleDiscardableChange: this.onScaleDiscardableChange,
initialNotification: this.props.settingsTab.pendingNotification(),
throughputError: this.state.throughputError, throughputError: this.state.throughputError,
}; };

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,18 @@
import { shallow } from "enzyme";
import ko from "knockout";
import React from "react";
import * as Constants from "../../../../Common/Constants"; import * as Constants from "../../../../Common/Constants";
import * as DataModels from "../../../../Contracts/DataModels";
import { updateUserContext } from "../../../../UserContext"; import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { throughputUnit } from "../SettingsRenderUtils";
import { collection } from "../TestUtils"; import { collection } from "../TestUtils";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent"; import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
describe("ScaleComponent", () => { describe("ScaleComponent", () => {
const targetThroughput = 6000;
const baseProps: ScaleComponentProps = { const baseProps: ScaleComponentProps = {
collection: collection, collection: collection,
database: undefined, database: undefined,
@@ -28,8 +36,39 @@ describe("ScaleComponent", () => {
onScaleDiscardableChange: () => { onScaleDiscardableChange: () => {
return; return;
}, },
initialNotification: {
description: `Throughput update for ${targetThroughput} ${throughputUnit}`,
} as DataModels.Notification,
}; };
it("renders with correct initial notification", () => {
let wrapper = shallow(<ScaleComponent {...baseProps} />);
expect(wrapper).toMatchSnapshot();
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);
const newCollection = { ...collection };
const maxThroughput = 5000;
newCollection.offer = ko.observable({
manualThroughput: undefined,
autoscaleMaxThroughput: maxThroughput,
minimumThroughput: 400,
id: "offer",
offerReplacePending: true,
});
const newProps = {
...baseProps,
initialNotification: undefined as DataModels.Notification,
collection: newCollection,
};
wrapper = shallow(<ScaleComponent {...newProps} />);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput);
});
it("autoScale disabled", () => { it("autoScale disabled", () => {
const scaleComponent = new ScaleComponent(baseProps); const scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.isAutoScaleEnabled()).toEqual(false); expect(scaleComponent.isAutoScaleEnabled()).toEqual(false);

View File

@@ -10,6 +10,7 @@ import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
import { import {
getTextFieldStyles, getTextFieldStyles,
getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage, getThroughputApplyShortDelayMessage,
subComponentStackProps, subComponentStackProps,
throughputUnit, throughputUnit,
@@ -33,6 +34,7 @@ export interface ScaleComponentProps {
onMaxAutoPilotThroughputChange: (newThroughput: number) => void; onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
onScaleSaveableChange: (isScaleSaveable: boolean) => void; onScaleSaveableChange: (isScaleSaveable: boolean) => void;
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void; onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
initialNotification: DataModels.Notification;
throughputError?: string; throughputError?: string;
} }
@@ -100,6 +102,10 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}; };
public getInitialNotificationElement = (): JSX.Element => { public getInitialNotificationElement = (): JSX.Element => {
if (this.props.initialNotification) {
return this.getLongDelayMessage();
}
if (this.offer?.offerReplacePending) { if (this.offer?.offerReplacePending) {
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput; const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
return getThroughputApplyShortDelayMessage( return getThroughputApplyShortDelayMessage(
@@ -114,6 +120,26 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
return undefined; return undefined;
}; };
public getLongDelayMessage = (): JSX.Element => {
const matches: string[] = this.props.initialNotification?.description.match(
`Throughput update for (.*) ${throughputUnit}`,
);
const throughput = this.props.throughputBaseline;
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
if (targetThroughput) {
return getThroughputApplyLongDelayMessage(
this.props.wasAutopilotOriginallySet,
throughput,
throughputUnit,
this.databaseId,
this.collectionId,
targetThroughput,
);
}
return <></>;
};
private getThroughputInputComponent = (): JSX.Element => ( private getThroughputInputComponent = (): JSX.Element => (
<ThroughputInputAutoPilotV3Component <ThroughputInputAutoPilotV3Component
databaseAccount={userContext?.databaseAccount} databaseAccount={userContext?.databaseAccount}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScaleComponent renders with correct initial notification 1`] = `
<Stack
tokens={
Object {
"childrenGap": 20,
}
}
>
<StyledMessageBar
messageBarType={5}
>
<Text
id="throughputApplyLongDelayMessage"
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
}
>
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
<br />
Database: test, Container: test
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
</Text>
</StyledMessageBar>
<Stack
tokens={
Object {
"childrenGap": 20,
}
}
>
<ThroughputInputAutoPilotV3Component
canExceedMaximumValue={true}
collectionName="test"
databaseName="test"
isAutoPilotSelected={false}
isEmulator={false}
isEnabled={true}
isFixed={false}
label="Throughput (6,000 - unlimited RU/s)"
maxAutoPilotThroughput={4000}
maxAutoPilotThroughputBaseline={4000}
maximum={1000000}
minimum={6000}
onAutoPilotSelected={[Function]}
onMaxAutoPilotThroughputChange={[Function]}
onScaleDiscardableChange={[Function]}
onScaleSaveableChange={[Function]}
onThroughputChange={[Function]}
spendAckChecked={false}
throughput={1000}
throughputBaseline={1000}
usageSizeInKB={100}
wasAutopilotOriginallySet={true}
/>
</Stack>
</Stack>
`;

View File

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

View File

@@ -44,6 +44,7 @@ describe("SettingsUtils", () => {
readSettings: undefined, readSettings: undefined,
onSettingsClick: undefined, onSettingsClick: undefined,
loadOffer: undefined, loadOffer: undefined,
getPendingThroughputSplitNotification: undefined,
} as ViewModels.Database; } as ViewModels.Database;
}; };
newCollection.offer(undefined); newCollection.offer(undefined);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Pivot, PivotItem } from "@fluentui/react"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import "./TabComponent.less"; import "./TabComponent.less";
export interface TabContent { export interface TabContent {
@@ -35,36 +35,58 @@ export class TabComponent extends React.Component<TabComponentProps> {
} }
private setActiveTab(index: number): void { private setActiveTab(index: number): void {
this.setState({ activeTabIndex: index });
this.props.onTabIndexChange(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 { public render(): JSX.Element {
const { tabs, currentTabIndex, hideHeader } = this.props; const currentTabContent = this.props.tabs[this.props.currentTabIndex].content;
const currentTabContent = tabs[currentTabIndex].content;
let className = "tabComponentContent"; let className = "tabComponentContent";
if (currentTabContent.className) { if (currentTabContent.className) {
className += ` ${currentTabContent.className}`; className += ` ${currentTabContent.className}`;
} }
return ( return (
<div className="tabComponentContainer"> <div className="tabComponentContainer">
<div className="tabs tabSwitch"> {!this.props.hideHeader && (
{!hideHeader && ( <div className="tabs tabSwitch" role="tablist">
<Pivot {this.renderTabTitles()}
aria-label="Tab navigation" </div>
selectedKey={currentTabIndex.toString()} )}
linkSize="normal" <div className={className}>{currentTabContent.render()}</div>
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>
</div> </div>
); );
} }

View File

@@ -30,7 +30,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header" aria-label="Throughput header"
key=".0:$.$.1" key=".0:$.$.1"
style={ style={
{ Object {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -41,7 +41,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header" aria-label="Throughput header"
className="css-110" className="css-110"
style={ style={
{ Object {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -62,9 +62,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1} delay={1}
styles={[Function]} styles={[Function]}
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -220,7 +220,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -325,7 +325,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -357,9 +357,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]} styles={[Function]}
tabIndex={0} tabIndex={0}
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -515,7 +515,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -620,7 +620,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -645,7 +645,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true} hidden={true}
id="tooltip0" id="tooltip0"
style={ style={
{ Object {
"border": 0, "border": 0,
"height": 1, "height": 1,
"margin": -1, "margin": -1,
@@ -744,9 +744,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]} styles={[Function]}
target="_blank" target="_blank"
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -902,7 +902,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1007,7 +1007,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1042,7 +1042,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription" aria-label="maxRUDescription"
key=".0:$.$.0" key=".0:$.$.0"
style={ style={
{ Object {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -1053,7 +1053,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription" aria-label="maxRUDescription"
className="css-110" className="css-110"
style={ style={
{ Object {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -1075,9 +1075,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1} delay={1}
styles={[Function]} styles={[Function]}
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1233,7 +1233,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1338,7 +1338,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1370,9 +1370,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]} styles={[Function]}
tabIndex={0} tabIndex={0}
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1528,7 +1528,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1633,7 +1633,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1658,7 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true} hidden={true}
id="tooltip1" id="tooltip1"
style={ style={
{ Object {
"border": 0, "border": 0,
"height": 1, "height": 1,
"margin": -1, "margin": -1,
@@ -1690,11 +1690,11 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
required={true} required={true}
step={1000} step={1000}
styles={ styles={
{ Object {
"field": { "field": Object {
"fontSize": 12, "fontSize": 12,
}, },
"fieldGroup": { "fieldGroup": Object {
"height": 27, "height": 27,
"width": 300, "width": 300,
}, },
@@ -1716,9 +1716,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
step={1000} step={1000}
styles={[Function]} styles={[Function]}
theme={ theme={
{ Object {
"disableGlobalClassNames": false, "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)", "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)", "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)", "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", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": { "fonts": Object {
"large": { "large": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": { "medium": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": { "mediumPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": { "mega": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": { "small": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": { "smallPlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": { "superLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": { "tiny": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": { "xLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": { "xLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": { "xSmall": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": { "xxLarge": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": { "xxLargePlus": Object {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "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, "isInverted": false,
"palette": { "palette": Object {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1874,7 +1874,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": { "semanticColors": Object {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1979,7 +1979,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": { "spacing": Object {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "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 PromiseSource from "Utils/PromiseSource";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import React from "react"; import React from "react";
@@ -9,7 +9,7 @@ function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): Tree
const node: TreeNode = { const node: TreeNode = {
id, id,
label: `${id}Label`, label: `${id}Label`,
className: "nodeIcon", className: `${id}Class`,
iconSrc: `${id}Icon`, iconSrc: `${id}Icon`,
onClick: jest.fn().mockName(`${id}Click`), onClick: jest.fn().mockName(`${id}Click`),
...additionalProps, ...additionalProps,
@@ -20,7 +20,7 @@ function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): Tree
describe("TreeNodeComponent", () => { describe("TreeNodeComponent", () => {
it("renders a single node", () => { it("renders a single node", () => {
const node = generateTestNode("root"); 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(); expect(component).toMatchSnapshot();
// The "click" handler is actually attached to onOpenChange, with a type of "Click". // 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(); expect(component).toMatchSnapshot();
}); });
@@ -54,7 +54,7 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", { const node = generateTestNode("root", {
onExpanded: () => loading.promise, onExpanded: () => loading.promise,
}); });
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />); const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
act(() => { act(() => {
component component
@@ -72,7 +72,10 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", { const node = generateTestNode("root", {
isSelected: () => true, 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(); expect(component).toMatchSnapshot();
}); });
@@ -86,7 +89,10 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"), 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(); expect(component).toMatchSnapshot();
}); });
@@ -105,7 +111,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"), 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.find(TreeItemLayout).props().style?.backgroundColor).toBeUndefined();
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
}); });
@@ -114,7 +120,7 @@ describe("TreeNodeComponent", () => {
const node = generateTestNode("root", { const node = generateTestNode("root", {
iconSrc: "the-icon.svg", 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(); expect(component).toMatchSnapshot();
}); });
@@ -128,7 +134,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"), generateTestNode("child2"),
], ],
}); });
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />); const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
}); });
@@ -142,7 +148,7 @@ describe("TreeNodeComponent", () => {
generateTestNode("child2"), generateTestNode("child2"),
], ],
}); });
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />); const component = shallow(<TreeNodeComponent node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot(); 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 // Find and expand the child3Expanding node
const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel"); const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel");

View File

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

View File

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

View File

@@ -1,19 +1,13 @@
import * as msal from "@azure/msal-browser";
import { Link } from "@fluentui/react/lib/Link"; import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
@@ -36,18 +30,20 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; 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 { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { stringToBlob } from "../Utils/BlobUtils"; import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils"; import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs"; import { useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, useDialog } from "./Controls/Dialog"; import { DialogProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { SnapshotRequest } from "./Notebook/NotebookComponent/types"; import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
@@ -255,48 +251,12 @@ export default class Explorer {
}; };
useDialog.getState().openDialog(addSynapseLinkDialogProps); useDialog.getState().openDialog(addSynapseLinkDialogProps);
TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink); TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
}
public async openLoginForEntraIDPopUp(): Promise<void> { // TODO: return result
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}`,
);
}
}
}
} }
public openNPSSurveyDialog(): void { public openNPSSurveyDialog(): void {
if (!Platform.Portal || !["Postgres", "SQL", "Mongo"].includes(userContext.apiType)) { if (!Platform.Portal) {
return; return;
} }
@@ -394,16 +354,16 @@ export default class Explorer {
return true; return true;
}; };
public onRefreshResourcesClick = async (): Promise<void> => { public onRefreshResourcesClick = (): void => {
if (configContext.platform === Platform.Fabric) { if (configContext.platform === Platform.Fabric) {
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases()); scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
return; return;
} }
await (userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases()); : this.refreshAllDatabases();
await this.refreshNotebookList(); this.refreshNotebookList();
}; };
// Facade // Facade
@@ -1120,7 +1080,7 @@ export default class Explorer {
} }
} }
public openUploadItemsPane(): void { public openUploadItemsPanePane(): void {
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />); useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />);
} }
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void { public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
@@ -1179,11 +1139,7 @@ export default class Explorer {
} }
public async configureCopilot(): Promise<void> { public async configureCopilot(): Promise<void> {
if ( if (userContext.apiType !== "SQL" || !userContext.subscriptionId) {
userContext.apiType !== "SQL" ||
!userContext.subscriptionId ||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())
) {
return; return;
} }
const copilotEnabledPromise = getCopilotEnabled(); const copilotEnabledPromise = getCopilotEnabled();

View File

@@ -737,17 +737,17 @@ export class D3ForceGraph implements GraphRenderer {
.on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) { .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) // https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation)
// this is the <g> element // 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) { .on("click", function (this: Element, _: MouseEvent, d: D3Node) {
// this is the <g> element // 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) { .on("keypress", function (this: Element, event: KeyboardEvent, d: D3Node) {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
event.stopPropagation(); event.stopPropagation();
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode as BaseType, d); self.onNodeClicked(this.parentNode, d);
} }
}); });
var nodeSize = this.igraphConfig.nodeSize; 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", () => { describe("Graph Data", () => {
it("should set only one node as root", () => { it("should set only one node as root", () => {
@@ -54,12 +54,12 @@ describe("Graph Data", () => {
// in edge // in edge
graphData.removeEdge("e1", false); graphData.removeEdge("e1", false);
expect(graphData.edges.length).toBe(1); expect(graphData.edges.length).toBe(1);
expect(graphData).not.toContainEqual({ id: "e1" }); expect(graphData).not.toContain(jasmine.objectContaining({ id: "e1" }));
// out edge // out edge
graphData.removeEdge("e2", false); graphData.removeEdge("e2", false);
expect(graphData.edges.length).toBe(0); 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", () => { it("should get string node property", () => {

View File

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

View File

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

View File

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

View File

@@ -4,51 +4,83 @@
* and update any knockout observables passed from the parent. * and update any knockout observables passed from the parent.
*/ */
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { userContext } from "UserContext";
import * as React from "react"; import * as React from "react";
import create, { UseStore } from "zustand";
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants"; import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
interface Props { interface Props {
container: Explorer; container: Explorer;
} }
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => { export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const selectedNodeState = useSelectedNode(); const selectedNodeState = useSelectedNode();
const contextButtons = useCommandBar((state) => state.contextButtons); const buttons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden); const isHidden = useCommandBar((state) => state.isHidden);
const backgroundColor = StyleConstants.BaseLight; const backgroundColor = StyleConstants.BaseLight;
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor, container); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
if (contextButtons?.length > 0) { const buttons =
userContext.apiType === "Postgres"
? CommandBarComponentButtonFactory.createPostgreButtons(container)
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
return (
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
}
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState),
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (buttons && buttons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
} }
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton( const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
contextButtons || [],
backgroundColor,
container,
);
if (uiFabricTabsButtons.length > 0) { if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider")); uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
} }
const uiFabricPlatformButtons = CommandBarUtil.convertButton(platformButtons || [], backgroundColor, container); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricPlatformButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
const connectionInfo = useNotebook((state) => state.connectionInfo); const connectionInfo = useNotebook((state) => state.connectionInfo);
@@ -56,7 +88,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
(useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) && (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) &&
connectionInfo?.status !== ConnectionStatusType.Connect connectionInfo?.status !== ConnectionStatusType.Connect
) { ) {
uiFabricPlatformButtons.unshift( uiFabricControlButtons.unshift(
CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"), CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"),
); );
} }
@@ -75,8 +107,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
}, },
}; };
const allButtons = staticButtons.concat(contextButtons).concat(platformButtons); const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons, container); const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
setKeyboardHandlers(keyboardHandlers); setKeyboardHandlers(keyboardHandlers);
return ( return (
@@ -84,7 +116,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
<FluentCommandBar <FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands" ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)} items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricPlatformButtons} farItems={uiFabricControlButtons}
styles={rootStyle} styles={rootStyle}
overflowButtonProps={{ ariaLabel: "More commands" }} overflowButtonProps={{ ariaLabel: "More commands" }}
/> />

View File

@@ -3,12 +3,15 @@ import { AuthType } from "../../../AuthType";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
import { CollectionBase } from "../../../Contracts/ViewModels"; import { CollectionBase } from "../../../Contracts/ViewModels";
import { updateUserContext } from "../../../UserContext"; import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined)); afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
describe("Enable Azure Synapse Link Button", () => { describe("Enable Azure Synapse Link Button", () => {
@@ -16,6 +19,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -26,7 +30,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("Button should be visible", () => { it("Button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -42,7 +46,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -58,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find( const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
); );
@@ -71,6 +75,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
properties: { properties: {
@@ -103,7 +108,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@@ -113,13 +118,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
portalEnv: "mooncake", portalEnv: "mooncake",
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();
}); });
@@ -129,8 +134,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openPostgresShellButtonLabel = "Open PSQL shell"; const openPostgresShellButtonLabel = "Open PSQL shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell"; const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell";
beforeAll(() => {
mockExplorer = {} as Explorer;
});
it("creates Postgres shell button", () => { it("creates Postgres shell button", () => {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(); const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer);
const openPostgresShellButton = buttons.find( const openPostgresShellButton = buttons.find(
(button) => button.commandButtonLabel === openPostgresShellButtonLabel, (button) => button.commandButtonLabel === openPostgresShellButtonLabel,
); );
@@ -138,7 +147,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
}); });
it("creates vCore Mongo shell button", () => { it("creates vCore Mongo shell button", () => {
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(); const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer);
const openVCoreMongoShellButton = buttons.find( const openVCoreMongoShellButton = buttons.find(
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel, (button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel,
); );
@@ -153,6 +162,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const selectedNodeState = useSelectedNode.getState(); const selectedNodeState = useSelectedNode.getState();
beforeAll(() => { beforeAll(() => {
mockExplorer = {} as Explorer;
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken, authType: AuthType.ResourceToken,
}); });
@@ -164,7 +175,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
kind: "DocumentDB", kind: "DocumentDB",
} as DatabaseAccount, } as DatabaseAccount,
}); });
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
expect(buttons.length).toBe(2); expect(buttons.length).toBe(2);
expect(buttons[0].commandButtonLabel).toBe("New SQL Query"); expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
expect(buttons[0].disabled).toBe(false); expect(buttons[0].disabled).toBe(false);

View File

@@ -1,13 +1,15 @@
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react"; 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 AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg"; import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg"; import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import EntraIDIcon from "../../../../images/EntraID.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import HomeIcon from "../../../../images/Home_16.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg";
@@ -18,62 +20,59 @@ import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen"; import { OpenFullScreen } from "../../OpenFullScreen";
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane, useDataPlaneRbac } from "../../Panes/SettingsPane/SettingsPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode"; import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode";
let counter = 0; let counter = 0;
export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeState): CommandButtonComponentProps[] { export function createStaticCommandBarButtons(
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { container: Explorer,
return userContext.apiType === "Postgres" ? createPostgreButtons() : createVCoreMongoButtons(); selectedNodeState: SelectedNodeState,
} ): CommandButtonComponentProps[] {
if (userContext.authType === AuthType.ResourceToken) { if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(selectedNodeState); return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
} }
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
// Avoid starting with a divider // Avoid starting with a divider
const addDivider = () => { const addDivider = () => {
if (buttons.length > 0 && !buttons[buttons.length - 1].isDivider) { if (buttons.length > 0) {
buttons.push(createDivider()); buttons.push(createDivider());
} }
}; };
if ( if (configContext.platform !== Platform.Fabric) {
configContext.platform !== Platform.Fabric && const homeBtn = createHomeButton();
userContext.apiType !== "Tables" && buttons.push(homeBtn);
userContext.apiType !== "Cassandra"
) { const newCollectionBtn = createNewCollectionGroup(container);
const addSynapseLink = createOpenSynapseLinkDialogButton(); newCollectionBtn.keyboardAction = KeyboardAction.NEW_COLLECTION; // Just for the root button, not the child version we create below.
if (addSynapseLink) { buttons.push(newCollectionBtn);
addDivider(); if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
buttons.push(addSynapseLink); const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
addDivider();
buttons.push(addSynapseLink);
}
} }
}
if (userContext.apiType === "SQL") { if (userContext.apiType !== "Tables") {
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined); newCollectionBtn.children = [createNewCollectionGroup(container)];
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled); const newDatabaseBtn = createNewDatabase(container);
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated); newCollectionBtn.children.push(newDatabaseBtn);
useEffect(() => {
const buttonProps = createLoginForEntraIDButton();
setLoginButtonProps(buttonProps);
}, [dataPlaneRbacEnabled, aadTokenUpdated]);
if (loginButtonProps) {
addDivider();
buttons.push(loginButtonProps);
} }
} }
@@ -87,8 +86,8 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
} }
if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) { if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) {
const openQueryBtn = createOpenQueryButton(); const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn); buttons.push(openQueryBtn);
} }
@@ -103,7 +102,6 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
}, },
commandButtonLabel: label, commandButtonLabel: label,
tooltipText: userContext.features.commandBarV2 ? "New..." : label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: disabled:
@@ -116,12 +114,21 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
} }
} }
return buttons;
}
export function createContextCommandBarButtons(
container: Explorer,
selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") { if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell"; const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = { const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { if (useNotebook.getState().isShellEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
@@ -133,47 +140,29 @@ export function createStaticCommandBarButtons(selectedNodeState: SelectedNodeSta
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
}; };
addDivider();
buttons.push(newMongoShellBtn); 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) => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
};
addDivider();
buttons.push(newCassandraShellButton);
}
return buttons; return buttons;
} }
export function createPlatformButtons(): CommandButtonComponentProps[] { export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [ const buttons: CommandButtonComponentProps[] =
{ configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly
iconSrc: SettingsIcon, ? []
iconAlt: "Settings", : [
onCommandClick: (_, container) => {
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />), iconSrc: SettingsIcon,
commandButtonLabel: undefined, iconAlt: "Settings",
ariaLabel: "Settings", onCommandClick: () =>
tooltipText: "Settings", useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
hasPopup: true, commandButtonLabel: undefined,
disabled: false, ariaLabel: "Settings",
}, tooltipText: "Settings",
]; hasPopup: true,
disabled: false,
},
];
const showOpenFullScreen = const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin"; configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
@@ -202,7 +191,7 @@ export function createPlatformButtons(): CommandButtonComponentProps[] {
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => container.openCESCVAFeedbackBlade(), onCommandClick: () => container.openCESCVAFeedbackBlade(),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: label, ariaLabel: label,
tooltipText: label, tooltipText: label,
@@ -234,7 +223,32 @@ function areScriptsSupported(): boolean {
); );
} }
function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps { 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) { if (configContext.platform === Platform.Emulator) {
return undefined; return undefined;
} }
@@ -252,7 +266,7 @@ function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps {
return { return {
iconSrc: SynapseIcon, iconSrc: SynapseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => container.openEnableSynapseLinkDialog(), onCommandClick: () => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled:
@@ -261,28 +275,22 @@ function createOpenSynapseLinkDialogButton(): CommandButtonComponentProps {
}; };
} }
function createLoginForEntraIDButton(): CommandButtonComponentProps { function createNewDatabase(container: Explorer): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) { const label = "New " + getDatabaseName();
return undefined;
}
const handleCommandClick: CommandButtonComponentProps["onCommandClick"] = async (_, container) => {
await container.openLoginForEntraIDPopUp();
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
};
if (!userContext.dataPlaneRbacEnabled || userContext.aadToken) {
return undefined;
}
const label = "Login for Entra ID RBAC";
return { return {
iconSrc: EntraIDIcon, iconSrc: AddDatabaseIcon,
iconAlt: label, 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, commandButtonLabel: label,
hasPopup: true,
ariaLabel: label, ariaLabel: label,
hasPopup: true,
}; };
} }
@@ -393,14 +401,13 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
return buttons; return buttons;
} }
function createOpenQueryButton(): CommandButtonComponentProps { function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query"; const label = "Open Query";
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
iconAlt: label, iconAlt: label,
tooltipText: userContext.features.commandBarV2 ? "Open Query..." : "Open Query",
keyboardAction: KeyboardAction.OPEN_QUERY, keyboardAction: KeyboardAction.OPEN_QUERY,
onCommandClick: (_, container) => onCommandClick: () =>
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />), useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
@@ -423,7 +430,10 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
}; };
} }
function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind): CommandButtonComponentProps { function createOpenTerminalButtonByKind(
container: Explorer,
terminalKind: ViewModels.TerminalKind,
): CommandButtonComponentProps {
const terminalFriendlyName = (): string => { const terminalFriendlyName = (): string => {
switch (terminalKind) { switch (terminalKind) {
case ViewModels.TerminalKind.Cassandra: case ViewModels.TerminalKind.Cassandra:
@@ -446,7 +456,7 @@ function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind):
return { return {
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
iconAlt: label, iconAlt: label,
onCommandClick: (_, container) => { onCommandClick: () => {
if (useNotebook.getState().isNotebookEnabled) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(terminalKind); container.openNotebookTerminal(terminalKind);
} }
@@ -460,10 +470,11 @@ function createOpenTerminalButtonByKind(terminalKind: ViewModels.TerminalKind):
} }
function createStaticCommandBarButtonsForResourceToken( function createStaticCommandBarButtonsForResourceToken(
container: Explorer,
selectedNodeState: SelectedNodeState, selectedNodeState: SelectedNodeState,
): CommandButtonComponentProps[] { ): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState); const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
const openQueryBtn = createOpenQueryButton(); const openQueryBtn = createOpenQueryButton(container);
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection; const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
const isResourceTokenCollectionNodeSelected: boolean = const isResourceTokenCollectionNodeSelected: boolean =
@@ -476,20 +487,20 @@ function createStaticCommandBarButtonsForResourceToken(
openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected; openQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
if (!openQueryBtn.disabled) { if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(), createOpenQueryFromDiskButton()]; openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
} }
return [newSqlQueryBtn, openQueryBtn]; return [newSqlQueryBtn, openQueryBtn];
} }
export function createPostgreButtons(): CommandButtonComponentProps[] { export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] {
const openPostgreShellBtn = createOpenTerminalButtonByKind(ViewModels.TerminalKind.Postgres); const openPostgreShellBtn = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Postgres);
return [openPostgreShellBtn]; return [openPostgreShellBtn];
} }
export function createVCoreMongoButtons(): CommandButtonComponentProps[] { export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(ViewModels.TerminalKind.VCoreMongo); const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
return [openVCoreMongoTerminalButton]; return [openVCoreMongoTerminalButton];
} }

View File

@@ -1,10 +1,8 @@
import { ICommandBarItemProps } from "@fluentui/react"; import { ICommandBarItemProps } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
describe("CommandBarUtil tests", () => { describe("CommandBarUtil tests", () => {
const mockExplorer = {} as Explorer;
const createButton = (): CommandButtonComponentProps => { const createButton = (): CommandButtonComponentProps => {
return { return {
iconSrc: "icon", iconSrc: "icon",
@@ -24,7 +22,7 @@ describe("CommandBarUtil tests", () => {
const btn = createButton(); const btn = createButton();
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
const converteds = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer); const converteds = CommandBarUtil.convertButton([btn], backgroundColor);
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(undefined); expect(converted.split).toBe(undefined);
@@ -48,7 +46,7 @@ describe("CommandBarUtil tests", () => {
btn.children.push(child); btn.children.push(child);
} }
const converteds = CommandBarUtil.convertButton([btn], "backgroundColor", mockExplorer); const converteds = CommandBarUtil.convertButton([btn], "backgroundColor");
expect(converteds.length).toBe(1); expect(converteds.length).toBe(1);
const converted = converteds[0]; const converted = converteds[0];
expect(converted.split).toBe(true); expect(converted.split).toBe(true);
@@ -64,7 +62,7 @@ describe("CommandBarUtil tests", () => {
btns.push(createButton()); btns.push(createButton());
} }
const converteds = CommandBarUtil.convertButton(btns, "backgroundColor", mockExplorer); const converteds = CommandBarUtil.convertButton(btns, "backgroundColor");
const uniqueKeys = converteds const uniqueKeys = converteds
.map((btn: ICommandBarItemProps) => btn.key) .map((btn: ICommandBarItemProps) => btn.key)
.filter((value: string, index: number, self: string[]) => self.indexOf(value) === index); .filter((value: string, index: number, self: string[]) => self.indexOf(value) === index);
@@ -76,10 +74,10 @@ describe("CommandBarUtil tests", () => {
const backgroundColor = "backgroundColor"; const backgroundColor = "backgroundColor";
btn.commandButtonLabel = undefined; btn.commandButtonLabel = undefined;
let converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0]; let converted = CommandBarUtil.convertButton([btn], backgroundColor)[0];
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
converted = CommandBarUtil.convertButton([btn], backgroundColor, mockExplorer)[0]; converted = CommandBarUtil.convertButton([btn], backgroundColor)[0];
delete btn.commandButtonLabel; delete btn.commandButtonLabel;
expect(converted.text).toEqual(btn.tooltipText); expect(converted.text).toEqual(btn.tooltipText);
}); });

View File

@@ -25,11 +25,7 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
* Convert our NavbarButtonConfig to UI Fabric buttons * Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns * @param btns
*/ */
export const convertButton = ( export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
btns: CommandButtonComponentProps[],
backgroundColor: string,
container: Explorer,
): ICommandBarItemProps[] => {
const buttonHeightPx = const buttonHeightPx =
configContext.platform == Platform.Fabric configContext.platform == Platform.Fabric
? StyleConstants.FabricCommandBarButtonHeight ? StyleConstants.FabricCommandBarButtonHeight
@@ -58,14 +54,15 @@ export const convertButton = (
iconProps: { iconProps: {
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: undefined, alignSelf: btn.iconName ? "baseline" : undefined,
filter: getFilter(btn.disabled), filter: getFilter(btn.disabled),
}, },
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName,
}, },
onClick: btn.onCommandClick onClick: btn.onCommandClick
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => { ? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev, container); btn.onCommandClick(ev);
let copilotEnabled = false; let copilotEnabled = false;
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
@@ -138,7 +135,7 @@ export const convertButton = (
result.split = true; result.split = true;
result.subMenuProps = { result.subMenuProps = {
items: convertButton(btn.children, backgroundColor, container), items: convertButton(btn.children, backgroundColor),
styles: { styles: {
list: { list: {
// TODO Figure out how to do it the proper way with subComponentStyles. // TODO Figure out how to do it the proper way with subComponentStyles.
@@ -189,7 +186,7 @@ export const convertButton = (
option?: IDropdownOption, option?: IDropdownOption,
index?: number, index?: number,
): void => { ): void => {
btn.children[index].onCommandClick(event, container); btn.children[index].onCommandClick(event);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text }); TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
}; };
@@ -240,17 +237,14 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
}; };
}; };
export function createKeyboardHandlers( export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap {
allButtons: CommandButtonComponentProps[],
container: Explorer,
): KeyboardHandlerMap {
const handlers: KeyboardHandlerMap = {}; const handlers: KeyboardHandlerMap = {};
function createHandlers(buttons: CommandButtonComponentProps[]) { function createHandlers(buttons: CommandButtonComponentProps[]) {
buttons.forEach((button) => { buttons.forEach((button) => {
if (!button.disabled && button.keyboardAction) { if (!button.disabled && button.keyboardAction) {
handlers[button.keyboardAction] = (e) => { handlers[button.keyboardAction] = (e) => {
button.onCommandClick(e, container); button.onCommandClick(e);
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action // If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
return true; return true;

View File

@@ -1,16 +0,0 @@
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import create, { UseStore } from "zustand";
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));

View File

@@ -1,159 +0,0 @@
import {
makeStyles,
Menu,
MenuButton,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
Toolbar,
ToolbarButton,
ToolbarDivider,
ToolbarGroup,
Tooltip,
} from "@fluentui/react-components";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "Explorer/Explorer";
import {
createPlatformButtons,
createStaticCommandBarButtons,
} from "Explorer/Menus/CommandBar/CommandBarComponentButtonFactory";
import { createKeyboardHandlers } from "Explorer/Menus/CommandBar/CommandBarUtil";
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import React, { MouseEventHandler } from "react";
const useToolbarStyles = makeStyles({
toolbar: {
height: tokens.layoutRowHeight,
justifyContent: "space-between", // Ensures that the two toolbar groups are at opposite ends of the toolbar
...cosmosShorthands.borderBottom(),
},
toolbarGroup: {
display: "flex",
},
});
export interface CommandBarV2Props {
explorer: Explorer;
}
export const CommandBarV2: React.FC<CommandBarV2Props> = ({ explorer }: CommandBarV2Props) => {
const styles = useToolbarStyles();
const selectedNodeState = useSelectedNode();
const contextButtons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden);
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR);
const staticButtons = createStaticCommandBarButtons(selectedNodeState);
const platformButtons = createPlatformButtons();
if (isHidden) {
setKeyboardHandlers({});
return null;
}
const allButtons = staticButtons.concat(contextButtons).concat(platformButtons);
const keyboardHandlers = createKeyboardHandlers(allButtons, explorer);
setKeyboardHandlers(keyboardHandlers);
return (
<CosmosFluentProvider>
<Toolbar className={styles.toolbar}>
<ToolbarGroup role="presentation" className={styles.toolbarGroup}>
{staticButtons.map((button, index) =>
renderButton(explorer, button, `static-${index}`, contextButtons?.length > 0),
)}
{staticButtons.length > 0 && contextButtons?.length > 0 && <ToolbarDivider />}
{contextButtons.map((button, index) => renderButton(explorer, button, `context-${index}`, false))}
</ToolbarGroup>
<ToolbarGroup role="presentation">
{platformButtons.map((button, index) => renderButton(explorer, button, `platform-${index}`, true))}
</ToolbarGroup>
</Toolbar>
</CosmosFluentProvider>
);
};
// This allows us to migrate individual buttons over to using a JSX.Element for the icon, without requiring us to change them all at once.
function renderIcon(iconSrcOrElement: string | JSX.Element, alt?: string): JSX.Element {
if (typeof iconSrcOrElement === "string") {
return <img src={iconSrcOrElement} alt={alt} />;
}
return iconSrcOrElement;
}
function renderButton(
explorer: Explorer,
btn: CommandButtonComponentProps,
key: string,
iconOnly: boolean,
): JSX.Element {
if (btn.isDivider) {
return <ToolbarDivider key={key} />;
}
const hasChildren = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const tooltip = btn.tooltipText || (iconOnly ? label : undefined);
const onClick: MouseEventHandler | undefined =
btn.onCommandClick && !hasChildren ? (e) => btn.onCommandClick(e, explorer) : undefined;
// We don't know which element will be the top-level element, so just slap a key on all of 'em
let button = hasChildren ? (
<MenuButton key={key} appearance="subtle" aria-label={btn.ariaLabel} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</MenuButton>
) : (
<ToolbarButton key={key} aria-label={btn.ariaLabel} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{!iconOnly && label}
</ToolbarButton>
);
if (tooltip) {
button = (
<Tooltip key={key} content={tooltip} relationship="description" withArrow>
{button}
</Tooltip>
);
}
if (hasChildren) {
button = (
<Menu key={key}>
<MenuTrigger disableButtonEnhancement>{button}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return button;
}
function renderMenuItem(explorer: Explorer, btn: CommandButtonComponentProps, key: string): JSX.Element {
const hasChildren = !!btn.children && btn.children.length > 0;
const onClick: MouseEventHandler | undefined = btn.onCommandClick
? (e) => btn.onCommandClick(e, explorer)
: undefined;
const item = (
<MenuItem key={key} onClick={onClick} icon={renderIcon(btn.iconSrc, btn.iconAlt)}>
{btn.commandButtonLabel || btn.tooltipText}
</MenuItem>
);
if (hasChildren) {
return (
<Menu>
<MenuTrigger disableButtonEnhancement>{item}</MenuTrigger>
<MenuPopover>
<MenuList>{btn.children.map((child, index) => renderMenuItem(explorer, child, index.toString()))}</MenuList>
</MenuPopover>
</Menu>
);
}
return item;
}

View File

@@ -131,7 +131,6 @@ export class NotificationConsoleComponent extends React.Component<
</div> </div>
<div <div
className="expandCollapseButton" className="expandCollapseButton"
data-test="NotificationConsole/ExpandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")} aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
@@ -148,7 +147,7 @@ export class NotificationConsoleComponent extends React.Component<
height={this.props.isConsoleExpanded ? "auto" : 0} height={this.props.isConsoleExpanded ? "auto" : 0}
onAnimationEnd={this.onConsoleWasExpanded} onAnimationEnd={this.onConsoleWasExpanded}
> >
<div data-test="NotificationConsole/Contents" className="notificationConsoleContents"> <div className="notificationConsoleContents">
<div className="notificationConsoleControls"> <div className="notificationConsoleControls">
<Dropdown <Dropdown
label="Filter:" label="Filter:"

View File

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

View File

@@ -1,13 +1,13 @@
import { clear, collectionWasOpened, getItems, Type } from "Explorer/MostRecentActivity/MostRecentActivity";
import { observable } from "knockout"; import { observable } from "knockout";
import { mostRecentActivity } from "./MostRecentActivity";
describe("MostRecentActivity", () => { describe("MostRecentActivity", () => {
const accountName = "some account"; const accountId = "some account";
beforeEach(() => clear(accountName)); beforeEach(() => mostRecentActivity.clear(accountId));
it("Has no items at first", () => { it("Has no items at first", () => {
expect(getItems(accountName)).toStrictEqual([]); expect(mostRecentActivity.getItems(accountId)).toStrictEqual([]);
}); });
it("Can record collections being opened", () => { it("Can record collections being opened", () => {
@@ -18,9 +18,9 @@ describe("MostRecentActivity", () => {
databaseId, databaseId,
}; };
collectionWasOpened(accountName, collection); mostRecentActivity.collectionWasOpened(accountId, collection);
const activity = getItems(accountName); const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([ expect(activity).toEqual([
expect.objectContaining({ expect.objectContaining({
collectionId, collectionId,
@@ -29,24 +29,58 @@ describe("MostRecentActivity", () => {
]); ]);
}); });
it("Does not store duplicate entries", () => { it("Can record notebooks being opened", () => {
const collectionId = "some collection"; const name = "some notebook";
const databaseId = "some database"; const path = "some path";
const collection = { const notebook = { name, path };
id: observable(collectionId),
databaseId,
};
collectionWasOpened(accountName, collection); mostRecentActivity.notebookWasItemOpened(accountId, notebook);
collectionWasOpened(accountName, collection);
const activity = getItems(accountName); const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([ expect(activity).toEqual([expect.objectContaining(notebook)]);
expect.objectContaining({ });
type: Type.OpenCollection,
collectionId, it("Filters out duplicates", () => {
databaseId, const name = "some notebook";
}), const path = "some path";
]); const notebook = { name, path };
const sameNotebook = { name, path };
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
mostRecentActivity.notebookWasItemOpened(accountId, sameNotebook);
const activity = mostRecentActivity.getItems(accountId);
expect(activity.length).toEqual(1);
expect(activity).toEqual([expect.objectContaining(notebook)]);
});
it("Allows for multiple accounts", () => {
const name = "some notebook";
const path = "some path";
const notebook = { name, path };
const anotherNotebook = { name: "Another " + name, path };
const anotherAccountId = "Another " + accountId;
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
mostRecentActivity.notebookWasItemOpened(anotherAccountId, anotherNotebook);
expect(mostRecentActivity.getItems(accountId)).toEqual([expect.objectContaining(notebook)]);
expect(mostRecentActivity.getItems(anotherAccountId)).toEqual([expect.objectContaining(anotherNotebook)]);
});
it("Can store multiple distinct elements, in FIFO order", () => {
const name = "some notebook";
const path = "some path";
const first = { name, path };
const second = { name: "Another " + name, path };
const third = { name, path: "Another " + path };
mostRecentActivity.notebookWasItemOpened(accountId, first);
mostRecentActivity.notebookWasItemOpened(accountId, second);
mostRecentActivity.notebookWasItemOpened(accountId, third);
const activity = mostRecentActivity.getItems(accountId);
expect(activity).toEqual([third, second, first].map(expect.objectContaining));
}); });
}); });

View File

@@ -1,10 +1,10 @@
import { AppStateComponentNames, deleteState, loadState, saveState } from "Shared/AppStatePersistenceUtility";
import { CollectionBase } from "../../Contracts/ViewModels"; import { CollectionBase } from "../../Contracts/ViewModels";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
export enum Type { export enum Type {
OpenCollection = "OpenCollection", OpenCollection,
OpenNotebook = "OpenNotebook", OpenNotebook,
} }
export interface OpenNotebookItem { export interface OpenNotebookItem {
@@ -21,174 +21,158 @@ export interface OpenCollectionItem {
type Item = OpenNotebookItem | OpenCollectionItem; type Item = OpenNotebookItem | OpenCollectionItem;
const itemsMaxNumber: number = 5; // Update schemaVersion if you are going to change this interface
interface StoredData {
schemaVersion: string;
itemsMap: { [accountId: string]: Item[] }; // FIFO
}
/** /**
* Migrate old data to new AppState * Stores most recent activity
*/ */
const migrateOldData = () => { class MostRecentActivity {
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) { private static readonly schemaVersion: string = "2";
const oldDataSchemaVersion: string = "2"; private static itemsMaxNumber: number = 5;
const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity); private storedData: StoredData;
if (rawData) { constructor() {
const oldData = JSON.parse(rawData); // Retrieve from local storage
if (oldData.schemaVersion === oldDataSchemaVersion) { if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
const itemsMap: Record<string, Item[]> = oldData.itemsMap; const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity);
Object.keys(itemsMap).forEach((accountId: string) => {
const accountName = accountId.split("/").pop(); if (!rawData) {
if (accountName) { this.storedData = MostRecentActivity.createEmptyData();
saveState( } else {
{ try {
componentName: AppStateComponentNames.MostRecentActivity, this.storedData = JSON.parse(rawData);
globalAccountName: accountName, } catch (e) {
}, console.error("Unable to parse stored most recent activity. Use empty data:", rawData);
itemsMap[accountId].map((item) => { this.storedData = MostRecentActivity.createEmptyData();
if ((item.type as unknown as number) === 0) { }
item.type = Type.OpenCollection;
} else if ((item.type as unknown as number) === 1) { // If version doesn't match or schema broke, nuke it!
item.type = Type.OpenNotebook; if (
} !this.storedData.hasOwnProperty("schemaVersion") ||
return item; this.storedData["schemaVersion"] !== MostRecentActivity.schemaVersion
}), ) {
); LocalStorageUtility.removeEntry(StorageKey.MostRecentActivity);
} this.storedData = MostRecentActivity.createEmptyData();
}); }
}
} else {
this.storedData = MostRecentActivity.createEmptyData();
}
for (let p in this.storedData.itemsMap) {
this.cleanupItems(p);
}
this.saveToLocalStorage();
}
private static createEmptyData(): StoredData {
return {
schemaVersion: MostRecentActivity.schemaVersion,
itemsMap: {},
};
}
private static isEmpty(object: any) {
return Object.keys(object).length === 0 && object.constructor === Object;
}
private saveToLocalStorage() {
if (MostRecentActivity.isEmpty(this.storedData.itemsMap)) {
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
LocalStorageUtility.removeEntry(StorageKey.MostRecentActivity);
}
// Don't save if empty
return;
}
LocalStorageUtility.setEntryString(StorageKey.MostRecentActivity, JSON.stringify(this.storedData));
}
private addItem(accountId: string, newItem: Item): void {
// When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable.
// if (!accountId) {
// return;
// }
// Remove duplicate
MostRecentActivity.removeDuplicate(newItem, this.storedData.itemsMap[accountId]);
this.storedData.itemsMap[accountId] = this.storedData.itemsMap[accountId] || [];
this.storedData.itemsMap[accountId].unshift(newItem);
this.cleanupItems(accountId);
this.saveToLocalStorage();
}
public getItems(accountId: string): Item[] {
return this.storedData.itemsMap[accountId] || [];
}
public collectionWasOpened(accountId: string, { id, databaseId }: Pick<CollectionBase, "id" | "databaseId">) {
const collectionId = id();
this.addItem(accountId, {
type: Type.OpenCollection,
databaseId,
collectionId,
});
}
public notebookWasItemOpened(accountId: string, { name, path }: Pick<NotebookContentItem, "name" | "path">) {
this.addItem(accountId, {
type: Type.OpenNotebook,
name,
path,
});
}
public clear(accountId: string): void {
delete this.storedData.itemsMap[accountId];
this.saveToLocalStorage();
}
/**
* Find items by doing strict comparison and remove from array if duplicate is found
* @param item
*/
private static removeDuplicate(item: Item, itemsArray: Item[]): void {
if (!itemsArray) {
return;
}
let index = -1;
for (let i = 0; i < itemsArray.length; i++) {
const currentItem = itemsArray[i];
if (JSON.stringify(currentItem) === JSON.stringify(item)) {
index = i;
break;
} }
} }
// Remove old data if (index !== -1) {
LocalStorageUtility.removeEntry(StorageKey.MostRecentActivity); itemsArray.splice(index, 1);
}
};
const addItem = (accountName: string, newItem: Item): void => {
// When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable.
// if (!accountId) {
// return;
// }
let items =
(loadState({
componentName: AppStateComponentNames.MostRecentActivity,
globalAccountName: accountName,
}) as Item[]) || [];
// Remove duplicate
items = removeDuplicate(newItem, items);
items.unshift(newItem);
items = cleanupItems(items, accountName);
saveState(
{
componentName: AppStateComponentNames.MostRecentActivity,
globalAccountName: accountName,
},
items,
);
};
export const getItems = (accountName: string): Item[] => {
if (!accountName) {
return [];
}
return (
(loadState({
componentName: AppStateComponentNames.MostRecentActivity,
globalAccountName: accountName,
}) as Item[]) || []
);
};
export const collectionWasOpened = (
accountName: string,
{ id, databaseId }: Pick<CollectionBase, "id" | "databaseId">,
) => {
if (accountName === undefined) {
return;
}
const collectionId = id();
addItem(accountName, {
type: Type.OpenCollection,
databaseId,
collectionId,
});
};
export const clear = (accountName: string): void => {
if (!accountName) {
return;
}
deleteState({
componentName: AppStateComponentNames.MostRecentActivity,
globalAccountName: accountName,
});
};
// Sort object by key
const sortObjectKeys = (unordered: Record<string, unknown>): Record<string, unknown> => {
return Object.keys(unordered)
.sort()
.reduce((obj: Record<string, unknown>, key: string) => {
obj[key] = unordered[key];
return obj;
}, {});
};
/**
* Find items by doing strict comparison and remove from array if duplicate is found.
* Modifies the array.
* @param item
* @param itemsArray
* @returns new array
*/
const removeDuplicate = (item: Item, itemsArray: Item[]): Item[] => {
if (!itemsArray) {
return itemsArray;
}
const result: Item[] = [...itemsArray];
let index = -1;
for (let i = 0; i < result.length; i++) {
const currentItem = result[i];
if (
JSON.stringify(sortObjectKeys(currentItem as unknown as Record<string, unknown>)) ===
JSON.stringify(sortObjectKeys(item as unknown as Record<string, unknown>))
) {
index = i;
break;
} }
} }
if (index !== -1) { /**
result.splice(index, 1); * Remove unknown types
* Limit items to max number
*/
private cleanupItems(accountId: string): void {
if (!this.storedData.itemsMap.hasOwnProperty(accountId)) {
return;
}
const itemsArray = this.storedData.itemsMap[accountId]
.filter((item) => item.type in Type)
.slice(0, MostRecentActivity.itemsMaxNumber);
if (itemsArray.length === 0) {
delete this.storedData.itemsMap[accountId];
} else {
this.storedData.itemsMap[accountId] = itemsArray;
}
} }
}
return result; export const mostRecentActivity = new MostRecentActivity();
};
/**
* Remove unknown types
* Limit items to max number
* Modifies the array.
*/
const cleanupItems = (items: Item[], accountName: string): Item[] => {
if (accountName === undefined) {
return [];
}
const itemsArray = items.filter((item) => item.type in Type).slice(0, itemsMaxNumber);
if (itemsArray.length === 0) {
deleteState({
componentName: AppStateComponentNames.MostRecentActivity,
globalAccountName: accountName,
});
}
return itemsArray;
};
migrateOldData();

View File

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

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