mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
134 Commits
ashleyst/p
...
ashleyst/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4facfba0c1 | ||
|
|
7e95f5d8c8 | ||
|
|
1be221e106 | ||
|
|
8e7a3db67e | ||
|
|
07c0ead523 | ||
|
|
4296b5ae02 | ||
|
|
e8a5658799 | ||
|
|
b4973e8367 | ||
|
|
4b207f3fa6 | ||
|
|
c5b7f599b3 | ||
|
|
6aeac542b1 | ||
|
|
0d22d4ab4d | ||
|
|
0658448b54 | ||
|
|
833d677d20 | ||
|
|
038142c180 | ||
|
|
94d3fcb30f | ||
|
|
d3722f2c99 | ||
|
|
5a5e155205 | ||
|
|
2226169a71 | ||
|
|
6f35fb5526 | ||
|
|
805a4ae168 | ||
|
|
cc89691da3 | ||
|
|
24860a6842 | ||
|
|
bf6b362610 | ||
|
|
baca7922b4 | ||
|
|
b59ba20ed0 | ||
|
|
7f55de7aa2 | ||
|
|
62c76cc264 | ||
|
|
99d95a4cec | ||
|
|
647cca09b3 | ||
|
|
2c5f4e9666 | ||
|
|
58ae64193f | ||
|
|
806a0657df | ||
|
|
bc479fb808 | ||
|
|
31773ee73b | ||
|
|
3d1f280378 | ||
|
|
2ef036ee94 | ||
|
|
77c758714d | ||
|
|
bcd8b7229f | ||
|
|
0a1d16de1b | ||
|
|
1e6c40eabf | ||
|
|
70d1dc6f74 | ||
|
|
d07d2c7c0d | ||
|
|
7a1aa89cd1 | ||
|
|
e67c3f6774 | ||
|
|
bd334a118a | ||
|
|
5871c1e2d0 | ||
|
|
81dccbe5be | ||
|
|
49c3d0f0cb | ||
|
|
375bb5f567 | ||
|
|
e9f83a8efd | ||
|
|
093ddba2db | ||
|
|
dfe79b20f5 | ||
|
|
1021e9c969 | ||
|
|
c30a9681fe | ||
|
|
17754cba05 | ||
|
|
b07fa89a23 | ||
|
|
28db549fa1 | ||
|
|
fe892dcc62 | ||
|
|
380caba5f5 | ||
|
|
62ab0e3e60 | ||
|
|
d199311633 | ||
|
|
bf225f91c4 | ||
|
|
4d0b1a6db8 | ||
|
|
e66c8a1b5c | ||
|
|
7e1a738f8e | ||
|
|
dabb91e9e9 | ||
|
|
7570d6b91d | ||
|
|
b8d6a0188a | ||
|
|
8c25742304 | ||
|
|
1ba3a6c761 | ||
|
|
c680481fe0 | ||
|
|
06d4829422 | ||
|
|
416743c548 | ||
|
|
b5d4509d49 | ||
|
|
417ef899f0 | ||
|
|
736731474f | ||
|
|
9b12775151 | ||
|
|
7002da0b51 | ||
|
|
7c5fb1b697 | ||
|
|
06e28ae3e7 | ||
|
|
52c2cfe419 | ||
|
|
b76d83d8e1 | ||
|
|
495296602a | ||
|
|
96ba0a9729 | ||
|
|
6276464e0d | ||
|
|
98c5fe65e6 | ||
|
|
cebf044803 | ||
|
|
f669a99228 | ||
|
|
36736882ee | ||
|
|
19d1e0d1df | ||
|
|
ceeead8458 | ||
|
|
4da3363cf7 | ||
|
|
ff4bc78d6c | ||
|
|
b6e3e5ea1c | ||
|
|
9e9d270b65 | ||
|
|
f56e5e64b9 | ||
|
|
14e5efcebf | ||
|
|
5c3f18f5f8 | ||
|
|
6ebc48ad28 | ||
|
|
298197b1b8 | ||
|
|
81a5b7cb6d | ||
|
|
b023250e67 | ||
|
|
92246144f7 | ||
|
|
a08415e7bc | ||
|
|
b94ce28e96 | ||
|
|
f8f7ea34bd | ||
|
|
cbd5e6bf76 | ||
|
|
618c5ec0fe | ||
|
|
afc82845b5 | ||
|
|
f4bcee5461 | ||
|
|
17207624a9 | ||
|
|
d36e511b18 | ||
|
|
c1a28793ba | ||
|
|
acf5acfdb4 | ||
|
|
7b81767ded | ||
|
|
c12eced120 | ||
|
|
2b15a4d43d | ||
|
|
c220a8b070 | ||
|
|
a5a5a95973 | ||
|
|
e3fab9b5bf | ||
|
|
98000a27f0 | ||
|
|
af664326ea | ||
|
|
a44ed1f45c | ||
|
|
e0cb3da6aa | ||
|
|
6c9673975a | ||
|
|
d35e2a325e | ||
|
|
00a816c488 | ||
|
|
953bef404b | ||
|
|
dfcb771939 | ||
|
|
6925fa8e4e | ||
|
|
7f6338b68b | ||
|
|
db50f42832 | ||
|
|
f533eeb0fc |
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
# NOTE: Prettier reads EditorConfig settings, so be careful adjusting settings here and assuming they'll only affect your editor ;).
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
indent_size = 2
|
||||
@@ -1,3 +1,5 @@
|
||||
playwright.config.ts
|
||||
|
||||
**/node_modules/
|
||||
src/**/__mocks__/**/*
|
||||
dist/
|
||||
@@ -89,10 +91,7 @@ src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
src/Explorer/Tabs/ConflictsTab.ts
|
||||
src/Explorer/Tabs/DatabaseSettingsTab.ts
|
||||
src/Explorer/Tabs/DocumentsTab.test.ts
|
||||
src/Explorer/Tabs/DocumentsTab.ts
|
||||
src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
@@ -128,7 +127,7 @@ src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)
|
||||
142
.github/workflows/ci.yml
vendored
142
.github/workflows/ci.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
codemetrics:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -101,78 +104,6 @@ jobs:
|
||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
|
||||
env:
|
||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||
- name: Post a comment with the preview URL
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: gh pr comment $PR_NUMBER --body "Build complete. You can now [preview this branch in the portal](https://cosmos-explorer-preview.azurewebsites.net/pull/$PR_NUMBER?feature.someFeatureFlagYouMightNeed=true)"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
endtoendemulator:
|
||||
name: "End To End Emulator Tests"
|
||||
# Temporarily disabled. This test needs to be rewritten in playwright
|
||||
if: false
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- uses: southpolesteve/cosmos-emulator-github-action@v1
|
||||
- name: End to End Tests
|
||||
run: |
|
||||
npm ci
|
||||
npm start &
|
||||
npm run wait-for-server
|
||||
npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts
|
||||
shell: bash
|
||||
env:
|
||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
|
||||
PLATFORM: "Emulator"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshots
|
||||
path: failed-*
|
||||
endtoend:
|
||||
name: "E2E"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test-file:
|
||||
- ./test/cassandra/container.spec.ts
|
||||
- ./test/graph/container.spec.ts
|
||||
- ./test/sql/container.spec.ts
|
||||
- ./test/mongo/container.spec.ts
|
||||
- ./test/mongo/container32.spec.ts
|
||||
- ./test/selfServe/selfServeExample.spec.ts
|
||||
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
|
||||
- ./test/sql/resourceToken.spec.ts
|
||||
- ./test/tables/container.spec.ts
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: npm ci
|
||||
- run: npm start &
|
||||
- run: npm run wait-for-server
|
||||
- name: ${{ matrix['test-file'] }}
|
||||
run: |
|
||||
# Run tests up to three times
|
||||
for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s)
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshots
|
||||
path: screenshots/
|
||||
nuget:
|
||||
name: Publish Nuget
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
@@ -222,3 +153,70 @@ jobs:
|
||||
name: packages
|
||||
with:
|
||||
path: "*.nupkg"
|
||||
|
||||
playwright-tests:
|
||||
name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Az CLI login"
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps
|
||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
merge-playwright-reports:
|
||||
name: "Merge Playwright Reports"
|
||||
# Merge reports after playwright-tests, even if some shards have failed
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [playwright-tests]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
15
.github/workflows/cleanup.yml
vendored
15
.github/workflows/cleanup.yml
vendored
@@ -9,6 +9,10 @@ on:
|
||||
# Once every hour
|
||||
- cron: "0 15 * * *"
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
@@ -16,10 +20,17 @@ jobs:
|
||||
name: "Cleanup Test Database Accounts"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: "Az CLI login"
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,4 +16,8 @@ Contracts/*
|
||||
.env
|
||||
failure.png
|
||||
screenshots/*
|
||||
GettingStarted-ignore*.ipynb
|
||||
GettingStarted-ignore*.ipynb
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
5
.npmrc
5
.npmrc
@@ -1 +1,4 @@
|
||||
save-exact=true
|
||||
save-exact=true
|
||||
|
||||
# Ignore peer dependency conflicts
|
||||
force=true # TODO: Remove this when we update to React 17 or higher!
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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 +0,0 @@
|
||||
module.exports = {}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "canvas",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
9
images/EntraID.svg
Normal file
9
images/EntraID.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,13 +0,0 @@
|
||||
const isCI = require("is-ci");
|
||||
|
||||
module.exports = {
|
||||
exitOnPageError: false,
|
||||
launchOptions: {
|
||||
headless: isCI,
|
||||
slowMo: 10,
|
||||
timeout: 60000,
|
||||
},
|
||||
contextOptions: {
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
};
|
||||
@@ -31,7 +31,7 @@ module.exports = {
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
coverageReporters: ["json", "text", "cobertura"],
|
||||
coverageReporters: ["json", "text", "cobertura", "lcov"],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
coverageThreshold: {
|
||||
@@ -76,6 +76,11 @@ module.exports = {
|
||||
"^dnd-core$": "dnd-core/dist/cjs",
|
||||
"^react-dnd$": "react-dnd/dist/cjs",
|
||||
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
|
||||
"d3-force": "<rootDir>/node_modules/d3-force/dist/d3-force.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-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
|
||||
@@ -129,8 +134,7 @@ module.exports = {
|
||||
snapshotSerializers: ["enzyme-to-json/serializer"],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-jsdom",
|
||||
|
||||
testEnvironment: "jsdom",
|
||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
@@ -154,7 +158,7 @@ module.exports = {
|
||||
// testResultsProcessor: "./trxProcessor.js",
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
@@ -164,13 +168,13 @@ module.exports = {
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
transform: {
|
||||
"^.+\\.html?$": "html-loader-jest",
|
||||
"^.+\\.html?$": "jest-html-loader",
|
||||
"^.+\\.[t|j]sx?$": "babel-jest",
|
||||
"^.+\\.svg$": "<rootDir>/svgTransform.js",
|
||||
"^.+\\.svg$": "<rootDir>/jest/svgTransform.js",
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
transformIgnorePatterns: ["/node_modules/", "/externals/"],
|
||||
transformIgnorePatterns: ["/node_modules/(?!@fluentui/react-icons)", "/externals/"],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
@@ -183,4 +187,7 @@ module.exports = {
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
// TODO: toMatchInlineSnapshot() does not work with prettier 3. Remove when fixed: https://github.com/jestjs/jest/issues/14305
|
||||
prettierPath: null,
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: "jest-playwright-preset",
|
||||
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
||||
setupFiles: ["dotenv/config"],
|
||||
testEnvironment: "./test/playwrightEnv.js",
|
||||
setupFilesAfterEnv: ["expect-playwright"],
|
||||
};
|
||||
@@ -130,6 +130,7 @@
|
||||
@ActiveTabWidth: 141px;
|
||||
@TabsHeight: 30px;
|
||||
@TabsWidth: 140px;
|
||||
@ContentWrapper: 111px;
|
||||
@StatusIconContainerSize: 18px;
|
||||
@LoadingErrorIconSize: 14px;
|
||||
@ErrorIconContainer: 16px;
|
||||
@@ -167,7 +168,7 @@
|
||||
|
||||
@FabricBoxBorderRadius: 8px;
|
||||
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||
@FabricBoxMargin: 4px 3px 4px 3px;
|
||||
@FabricBoxMargin: 4px 8px 4px 8px;
|
||||
|
||||
@FabricAccentMediumHigh: #0c695a;
|
||||
@FabricAccentMedium: #117865;
|
||||
@@ -335,4 +336,11 @@
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: @InfoPointerColor transparent;
|
||||
}
|
||||
/*********************************************************************************************************
|
||||
Screen Reader Only
|
||||
**********************************************************************************************************/
|
||||
.screenReaderOnly {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
@@ -1906,8 +1906,14 @@ input::-webkit-calendar-picker-indicator::after {
|
||||
}
|
||||
|
||||
.nav-tabs-margin {
|
||||
padding-top: 8px;
|
||||
height: 32px;
|
||||
background-color: #f2f2f2;
|
||||
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.navTabHeight {
|
||||
@@ -2074,14 +2080,6 @@ a:link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.resourceTreeAndTabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: clip;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.collectiontitle {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
@@ -2264,33 +2262,33 @@ a:link {
|
||||
width: 82px;
|
||||
}
|
||||
|
||||
.tabdocuments .scrollable {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
// .tabdocuments .scrollable {
|
||||
// height: 100%;
|
||||
// overflow-y: auto;
|
||||
// overflow-x: hidden;
|
||||
// padding-left: 5px;
|
||||
// padding-right: 5px;
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
.tabdocuments > .tabdocumentsGridElement {
|
||||
width: 50%;
|
||||
}
|
||||
// .tabdocuments > .tabdocumentsGridElement {
|
||||
// width: 50%;
|
||||
// }
|
||||
|
||||
.tabdocuments > .evenlySpacedHeader {
|
||||
width: 30%;
|
||||
}
|
||||
// .tabdocuments > .evenlySpacedHeader {
|
||||
// width: 30%;
|
||||
// }
|
||||
|
||||
.tabdocuments.scrollable:focus,
|
||||
.tabdocuments.scrollable:active {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
// .tabdocuments.scrollable:focus,
|
||||
// .tabdocuments.scrollable:active {
|
||||
// outline: 1px dotted;
|
||||
// }
|
||||
|
||||
.tabdocuments .scrollable table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
// .tabdocuments .scrollable table td {
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
|
||||
.mongoDocumentEditor .monaco-editor.vs .redsquiggly {
|
||||
display: none !important;
|
||||
@@ -2316,21 +2314,15 @@ td a:hover {
|
||||
}
|
||||
|
||||
.loadMore {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: 30%;
|
||||
padding-top: 2px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loadMore > a:focus {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
|
||||
#content.active .tabdocuments .scrollable {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-fixed thead {
|
||||
width: 97%;
|
||||
padding-left: 18px;
|
||||
@@ -2366,10 +2358,9 @@ a:link {
|
||||
|
||||
.tabsManagerContainer {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
min-height: 300px;
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@@ -2558,10 +2549,12 @@ a:link {
|
||||
}
|
||||
|
||||
.filterdivs {
|
||||
padding-top: 15px;
|
||||
height: 45px;
|
||||
margin-bottom: 8px;
|
||||
margin: 10px 0px;
|
||||
white-space: nowrap;
|
||||
input {
|
||||
line-height: 14px; // To correct vertical position of the down arrow of the input
|
||||
outline: none; // Remove the dotted border on focus, because fluent has its own focus style (underlined)
|
||||
}
|
||||
}
|
||||
|
||||
.editFilterContainer {
|
||||
@@ -2624,7 +2617,7 @@ a:link {
|
||||
|
||||
.tabPanesContainer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -2658,7 +2651,7 @@ a:link {
|
||||
width: @ActiveTabWidth;
|
||||
}
|
||||
|
||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText {
|
||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
||||
font-weight: bolder;
|
||||
border-bottom: 2px solid rgba(0, 120, 212, 1);
|
||||
}
|
||||
@@ -2694,67 +2687,71 @@ a:link {
|
||||
width: @TabsWidth;
|
||||
border-right: @ButtonBorderWidth solid @BaseMedium;
|
||||
white-space: nowrap;
|
||||
.contentWrapper {
|
||||
.flex-display();
|
||||
width: @ContentWrapper;
|
||||
|
||||
.statusIconContainer {
|
||||
width: @StatusIconContainerSize;
|
||||
height: @StatusIconContainerSize;
|
||||
margin-left: @SmallSpace;
|
||||
display: inline-flex;
|
||||
.statusIconContainer {
|
||||
width: @StatusIconContainerSize;
|
||||
height: @StatusIconContainerSize;
|
||||
margin-left: @SmallSpace;
|
||||
display: inline-flex;
|
||||
|
||||
.errorIconContainer {
|
||||
width: @ErrorIconContainer;
|
||||
height: @ErrorIconContainer;
|
||||
margin-top: 1px;
|
||||
.errorIconContainer {
|
||||
width: @ErrorIconContainer;
|
||||
height: @ErrorIconContainer;
|
||||
margin-top: 1px;
|
||||
|
||||
.errorIcon {
|
||||
width: @ErrorIconWidth;
|
||||
.errorIcon {
|
||||
width: @ErrorIconWidth;
|
||||
height: @LoadingErrorIconSize;
|
||||
background-image: url(../images/error_no_outline.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 3px;
|
||||
display: block;
|
||||
margin: 1px 0px 0px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.errorIconContainer.actionsEnabled {
|
||||
&:hover {
|
||||
.hover();
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
|
||||
&:active {
|
||||
.active();
|
||||
}
|
||||
}
|
||||
|
||||
.errorIconContainer[tabindex]:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
width: @LoadingErrorIconSize;
|
||||
height: @LoadingErrorIconSize;
|
||||
background-image: url(../images/error_no_outline.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 3px;
|
||||
display: block;
|
||||
margin: 1px 0px 0px 6px;
|
||||
margin: 0px 0px @SmallSpace @SmallSpace;
|
||||
}
|
||||
}
|
||||
|
||||
.errorIconContainer.actionsEnabled {
|
||||
&:hover {
|
||||
.hover();
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
|
||||
&:active {
|
||||
.active();
|
||||
}
|
||||
.tabNavText {
|
||||
margin-left: @SmallSpace;
|
||||
margin-right: 2px;
|
||||
color: @BaseDark;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.errorIconContainer[tabindex]:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
width: @LoadingErrorIconSize;
|
||||
height: @LoadingErrorIconSize;
|
||||
margin: 0px 0px @SmallSpace @SmallSpace;
|
||||
}
|
||||
}
|
||||
|
||||
.tabNavText {
|
||||
margin-left: @SmallSpace;
|
||||
margin-right: 2px;
|
||||
color: @BaseDark;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tabIconSection {
|
||||
width: 30px;
|
||||
width: 29px;
|
||||
position: relative;
|
||||
padding-top: 2px;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ a:focus {
|
||||
}
|
||||
|
||||
.nav-tabs-margin {
|
||||
padding-top: 8px;
|
||||
padding-top: 5px;
|
||||
background-color: #ffffff
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ a:focus {
|
||||
border-bottom: 2px solid @FabricAccentMedium;
|
||||
}
|
||||
|
||||
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.tabNavText {
|
||||
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.contentWrapper>.tabNavText {
|
||||
border-bottom: 0px none transparent;
|
||||
}
|
||||
|
||||
@@ -93,9 +93,11 @@ a:focus {
|
||||
width: calc(@TabsWidth - (@SmallSpace * 2));
|
||||
padding-bottom: @SmallSpace;
|
||||
|
||||
.statusIconContainer {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.contentWrapper {
|
||||
.statusIconContainer {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabIconSection {
|
||||
.cancelButton {
|
||||
|
||||
@@ -3,19 +3,6 @@
|
||||
.dataResourceTree {
|
||||
margin-left: @MediumSpace;
|
||||
overflow: auto;
|
||||
|
||||
.databaseHeader {
|
||||
padding: 1px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.collectionHeader {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loadMoreHeader {
|
||||
color: RGB(5, 99, 193);
|
||||
}
|
||||
}
|
||||
|
||||
.notebookResourceTree {
|
||||
|
||||
22770
package-lock.json
generated
22770
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -5,21 +5,21 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@azure/arm-cosmosdb": "9.1.0",
|
||||
"@azure/cosmos": "4.0.1-beta.2",
|
||||
"@azure/cosmos": "4.0.1-beta.3",
|
||||
"@azure/cosmos-language-service": "0.0.5",
|
||||
"@azure/identity": "1.2.1",
|
||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||
"@azure/identity": "1.5.2",
|
||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
||||
"@azure/msal-browser": "2.14.2",
|
||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||
"@fluentui/react": "8.112.1",
|
||||
"@fluentui/react-components": "9.34.0",
|
||||
"@fluentui/react": "8.119.0",
|
||||
"@fluentui/react-components": "9.54.2",
|
||||
"@jupyterlab/services": "6.0.2",
|
||||
"@jupyterlab/terminal": "3.0.3",
|
||||
"@microsoft/applicationinsights-web": "2.6.1",
|
||||
"@nteract/commutable": "7.5.1",
|
||||
"@nteract/connected-components": "6.8.2",
|
||||
"@nteract/core": "15.1.0",
|
||||
"@nteract/core": "15.1.9",
|
||||
"@nteract/data-explorer": "8.0.3",
|
||||
"@nteract/directory-listing": "2.0.6",
|
||||
"@nteract/dropdown-menu": "1.0.1",
|
||||
@@ -42,19 +42,21 @@
|
||||
"@nteract/transform-vega": "7.0.6",
|
||||
"@octokit/rest": "17.9.2",
|
||||
"@phosphor/widgets": "1.9.3",
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@testing-library/jest-dom": "6.4.6",
|
||||
"@types/lodash": "4.14.171",
|
||||
"@types/mkdirp": "1.0.1",
|
||||
"@types/node-fetch": "2.5.7",
|
||||
"@xmldom/xmldom": "0.7.13",
|
||||
"allotment": "1.20.2",
|
||||
"applicationinsights": "1.8.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"canvas": "file:./canvas",
|
||||
"canvas": "2.11.2",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"clipboard-copy": "4.0.1",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"crossroads": "0.12.2",
|
||||
"css-element-queries": "1.1.1",
|
||||
"d3": "6.1.1",
|
||||
"d3": "7.8.5",
|
||||
"datatables.net-colreorder-dt": "1.7.0",
|
||||
"datatables.net-dt": "1.13.8",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -65,16 +67,18 @@
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"hasher": "1.2.0",
|
||||
"html2canvas": "1.0.0-rc.5",
|
||||
"i18next": "19.8.4",
|
||||
"i18next": "23.11.5",
|
||||
"i18next-browser-languagedetector": "6.0.1",
|
||||
"i18next-http-backend": "1.0.23",
|
||||
"iframe-resizer-react": "1.1.0",
|
||||
"immer": "9.0.6",
|
||||
"immutable": "4.0.0-rc.12",
|
||||
"is-ci": "2.0.0",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-typeahead": "2.11.1",
|
||||
"jquery-ui-dist": "1.13.2",
|
||||
"knockout": "3.5.1",
|
||||
"loader-utils": "2.0.3",
|
||||
"mkdirp": "1.0.4",
|
||||
"monaco-editor": "0.44.0",
|
||||
"ms": "2.1.3",
|
||||
@@ -89,27 +93,31 @@
|
||||
"react-dnd-html5-backend": "14.0.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-hotkeys": "2.0.0",
|
||||
"react-i18next": "11.8.5",
|
||||
"react-i18next": "14.1.2",
|
||||
"react-notification-system": "0.2.17",
|
||||
"react-redux": "7.1.3",
|
||||
"react-splitter-layout": "4.0.0",
|
||||
"react-string-format": "1.0.1",
|
||||
"react-window": "1.8.10",
|
||||
"react-youtube": "9.0.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rx-jupyter": "5.5.12",
|
||||
"sanitize-html": "2.3.3",
|
||||
"shell-quote": "1.7.3",
|
||||
"styled-components": "5.0.1",
|
||||
"swr": "0.4.0",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"underscore": "1.9.1",
|
||||
"tinykeys": "2.1.0",
|
||||
"underscore": "1.12.1",
|
||||
"utility-types": "3.10.0",
|
||||
"zustand": "3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.9.0",
|
||||
"@babel/preset-env": "7.9.0",
|
||||
"@babel/preset-react": "7.9.4",
|
||||
"@babel/preset-typescript": "7.9.0",
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "7.24.7",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@playwright/test": "1.44.0",
|
||||
"@testing-library/react": "11.2.3",
|
||||
"@types/applicationinsights-js": "1.0.7",
|
||||
"@types/codemirror": "0.0.56",
|
||||
@@ -118,19 +126,20 @@
|
||||
"@types/datatables.net": "1.10.28",
|
||||
"@types/datatables.net-colreorder": "1.4.5",
|
||||
"@types/dom-to-image": "2.6.2",
|
||||
"@types/enzyme": "3.10.7",
|
||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||
"@types/enzyme": "3.10.12",
|
||||
"@types/enzyme-adapter-react-16": "1.0.9",
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/post-robot": "10.0.1",
|
||||
"@types/q": "1.5.1",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.3",
|
||||
"@types/react": "17.0.44",
|
||||
"@types/react-dom": "17.0.15",
|
||||
"@types/react-notification-system": "0.2.39",
|
||||
"@types/react-redux": "7.1.7",
|
||||
"@types/react-splitter-layout": "3.0.1",
|
||||
"@types/react-window": "1.8.8",
|
||||
"@types/sanitize-html": "1.27.2",
|
||||
"@types/sinon": "2.3.3",
|
||||
"@types/styled-components": "5.1.1",
|
||||
@@ -139,56 +148,55 @@
|
||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||
"@typescript-eslint/parser": "6.7.4",
|
||||
"@webpack-cli/serve": "2.0.5",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-jest": "29.7.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"buffer": "5.1.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||
"create-file-webpack": "1.0.2",
|
||||
"css-loader": "6.8.1",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.5",
|
||||
"enzyme-to-json": "3.6.1",
|
||||
"enzyme-adapter-react-16": "1.15.8",
|
||||
"enzyme-to-json": "3.6.2",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-cli": "1.1.1",
|
||||
"eslint-plugin-no-null": "1.0.2",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"expect-playwright": "0.3.3",
|
||||
"fast-glob": "3.2.5",
|
||||
"fs-extra": "7.0.0",
|
||||
"html-inline-css-webpack-plugin": "1.11.2",
|
||||
"html-loader": "0.5.5",
|
||||
"html-loader-jest": "0.2.1",
|
||||
"html-loader": "5.0.0",
|
||||
"html-webpack-plugin": "5.5.3",
|
||||
"jest": "26.6.3",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"jest-playwright-preset": "1.5.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-canvas-mock": "2.5.2",
|
||||
"jest-circus": "29.7.0",
|
||||
"jest-html-loader": "1.0.0",
|
||||
"jest-react-hooks-shallow": "1.5.1",
|
||||
"jest-trx-results-processor": "0.0.7",
|
||||
"jest-trx-results-processor": "3.0.2",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "11.1.3",
|
||||
"less-vars-loader": "1.1.0",
|
||||
"mini-css-extract-plugin": "2.1.0",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"playwright": "1.13.0",
|
||||
"node-fetch": "2.6.7",
|
||||
"prettier": "3.0.3",
|
||||
"process": "0.11.10",
|
||||
"querystring-es3": "0.2.1",
|
||||
"raw-loader": "0.5.1",
|
||||
"react-dev-utils": "11.0.4",
|
||||
"react-dev-utils": "12.0.1",
|
||||
"rimraf": "3.0.0",
|
||||
"sinon": "3.2.1",
|
||||
"style-loader": "0.23.0",
|
||||
"ts-loader": "9.2.4",
|
||||
"typedoc": "0.21.5",
|
||||
"typescript": "4.3.5",
|
||||
"typedoc": "0.26.2",
|
||||
"typescript": "4.9.5",
|
||||
"url-loader": "4.1.1",
|
||||
"wait-on": "4.0.2",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-bundle-analyzer": "4.9.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "4.15.1"
|
||||
"webpack-dev-server": "4.15.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
@@ -203,6 +211,7 @@
|
||||
"test": "rimraf coverage && jest",
|
||||
"test:debug": "jest --runInBand",
|
||||
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
|
||||
"test:file": "jest --coverage=false",
|
||||
"watch": "npm run start",
|
||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
||||
"build:ase": "gulp build:ase",
|
||||
@@ -238,4 +247,4 @@
|
||||
"printWidth": 120,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
patches/@phosphor+virtualdom+1.2.0.patch
Normal file
13
patches/@phosphor+virtualdom+1.2.0.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
60
playwright.config.ts
Normal file
60
playwright.config.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "test",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 3 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI ? "blob" : "html",
|
||||
timeout: 10 * 60 * 1000,
|
||||
use: {
|
||||
trace: "off",
|
||||
video: "off",
|
||||
screenshot: "on",
|
||||
testIdAttribute: "data-test",
|
||||
contextOptions: {
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
},
|
||||
|
||||
expect: {
|
||||
// Many of our expectations take a little longer than the default 5 seconds.
|
||||
timeout: 15 * 1000,
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
},
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: "Google Chrome",
|
||||
use: { ...devices["Desktop Chrome"], channel: "chrome" }, // or 'chrome-beta'
|
||||
},
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
use: { ...devices["Desktop Edge"], channel: "msedge" }, // or 'msedge-dev'
|
||||
},
|
||||
],
|
||||
|
||||
webServer: {
|
||||
command: "npm run start",
|
||||
url: "https://127.0.0.1:1234/_ready",
|
||||
timeout: 120 * 1000,
|
||||
ignoreHTTPSErrors: true,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -88,6 +88,12 @@ export class CapabilityNames {
|
||||
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
|
||||
public static readonly EnableMongo: string = "EnableMongo";
|
||||
public static readonly EnableServerless: string = "EnableServerless";
|
||||
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
|
||||
}
|
||||
|
||||
export enum CapacityMode {
|
||||
Provisioned = "Provisioned",
|
||||
Serverless = "Serverless",
|
||||
}
|
||||
|
||||
// flight names returned from the portal are always lowercase
|
||||
@@ -124,8 +130,12 @@ export enum MongoBackendEndpointType {
|
||||
remote,
|
||||
}
|
||||
|
||||
export enum BackendApi {
|
||||
GenerateToken,
|
||||
export class BackendApi {
|
||||
public static readonly GenerateToken: string = "GenerateToken";
|
||||
public static readonly PortalSettings: string = "PortalSettings";
|
||||
public static readonly AccountRestrictions: string = "AccountRestrictions";
|
||||
public static readonly RuntimeProxy: string = "RuntimeProxy";
|
||||
public static readonly DisallowedLocations: string = "DisallowedLocations";
|
||||
}
|
||||
|
||||
export class PortalBackendEndpoints {
|
||||
@@ -137,7 +147,7 @@ export class PortalBackendEndpoints {
|
||||
}
|
||||
|
||||
export class MongoProxyEndpoints {
|
||||
public static readonly Development: string = "https://localhost:7238";
|
||||
public static readonly Local: string = "https://localhost:7238";
|
||||
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
|
||||
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
|
||||
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
|
||||
@@ -175,6 +185,12 @@ export class CassandraProxyAPIs {
|
||||
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
|
||||
}
|
||||
|
||||
export class AadEndpoints {
|
||||
public static readonly Prod: string = "https://login.microsoftonline.com/";
|
||||
public static readonly Fairfax: string = "https://login.microsoftonline.us/";
|
||||
public static readonly Mooncake: string = "https://login.partner.microsoftonline.cn/";
|
||||
}
|
||||
|
||||
export class Queries {
|
||||
public static CustomPageOption: string = "custom";
|
||||
public static UnlimitedPageOption: string = "unlimited";
|
||||
@@ -189,6 +205,12 @@ export class Queries {
|
||||
public static readonly DefaultMaxWaitTimeInSeconds = 30;
|
||||
}
|
||||
|
||||
export class RBACOptions {
|
||||
public static setAutomaticRBACOption: string = "Automatic";
|
||||
public static setTrueRBACOption: string = "True";
|
||||
public static setFalseRBACOption: string = "False";
|
||||
}
|
||||
|
||||
export class SavedQueries {
|
||||
public static readonly CollectionName: string = "___Query";
|
||||
public static readonly DatabaseName: string = "___Cosmos";
|
||||
@@ -248,6 +270,7 @@ export class HttpHeaders {
|
||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||
public static xAPIKey: string = "X-API-Key";
|
||||
}
|
||||
|
||||
export class ContentType {
|
||||
|
||||
@@ -1,47 +1,6 @@
|
||||
import { ResourceType } from "@azure/cosmos";
|
||||
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
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);
|
||||
});
|
||||
});
|
||||
import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient";
|
||||
|
||||
describe("getTokenFromAuthService", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
||||
import { AuthorizationToken } from "Contracts/MessageTypes";
|
||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { PriorityLevel } from "../Common/Constants";
|
||||
import { BackendApi, PriorityLevel } from "../Common/Constants";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { Platform, configContext } from "../ConfigContext";
|
||||
import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
@@ -17,7 +19,18 @@ const _global = typeof self === "undefined" ? window : self;
|
||||
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||
|
||||
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
|
||||
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
|
||||
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
||||
Logger.logInfo(
|
||||
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
||||
"Explorer/tokenProvider",
|
||||
);
|
||||
if (!userContext.aadToken) {
|
||||
logConsoleError(
|
||||
`AAD token does not exist. Please click on "Login for Entra ID" button prior to performing Entra ID RBAC operations`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
|
||||
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
|
||||
return authorizationToken;
|
||||
@@ -59,7 +72,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
||||
// User master tokens
|
||||
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
||||
MessageTypes.GetAuthorizationToken,
|
||||
FabricMessageTypes.GetAuthorizationToken,
|
||||
[requestInfo],
|
||||
userContext.fabricContext.connectionId,
|
||||
);
|
||||
@@ -71,8 +84,15 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||
}
|
||||
|
||||
if (userContext.masterKey) {
|
||||
Logger.logInfo(`Master Key exists`, "Explorer/tokenProvider");
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(
|
||||
verb,
|
||||
resourceId,
|
||||
resourceType,
|
||||
headers,
|
||||
userContext.masterKey,
|
||||
);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
}
|
||||
|
||||
@@ -104,6 +124,37 @@ export async function getTokenFromAuthService(
|
||||
verb: string,
|
||||
resourceType: string,
|
||||
resourceId?: string,
|
||||
): Promise<AuthorizationToken> {
|
||||
if (!useNewPortalBackendEndpoint(BackendApi.RuntimeProxy)) {
|
||||
return getTokenFromAuthService_ToBeDeprecated(verb, resourceType, resourceId);
|
||||
}
|
||||
|
||||
try {
|
||||
const host: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||
const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-encrypted-auth-token": userContext.accessToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
verb,
|
||||
resourceType,
|
||||
resourceId,
|
||||
}),
|
||||
});
|
||||
const result: AuthorizationToken = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenFromAuthService_ToBeDeprecated(
|
||||
verb: string,
|
||||
resourceType: string,
|
||||
resourceId?: string,
|
||||
): Promise<AuthorizationToken> {
|
||||
try {
|
||||
const host = configContext.BACKEND_ENDPOINT;
|
||||
@@ -137,8 +188,11 @@ enum SDKSupportedCapabilities {
|
||||
let _client: Cosmos.CosmosClient;
|
||||
|
||||
export function client(): Cosmos.CosmosClient {
|
||||
if (_client) return _client;
|
||||
|
||||
if (_client) {
|
||||
if (!userContext.hasDataPlaneRbacSettingChanged) {
|
||||
return _client;
|
||||
}
|
||||
}
|
||||
let _defaultHeaders: Cosmos.CosmosHeaders = {};
|
||||
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
|
||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
||||
@@ -157,7 +211,7 @@ export function client(): Cosmos.CosmosClient {
|
||||
|
||||
const options: Cosmos.CosmosClientOptions = {
|
||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||
key: userContext.masterKey,
|
||||
key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey,
|
||||
tokenProvider,
|
||||
userAgentSuffix: "Azure Portal",
|
||||
defaultHeaders: _defaultHeaders,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { userContext } from "../UserContext";
|
||||
|
||||
export const getEntityName = (): string => {
|
||||
export const getEntityName = (multiple?: boolean): string => {
|
||||
if (userContext.apiType === "Mongo") {
|
||||
return "document";
|
||||
return multiple ? "documents" : "document";
|
||||
}
|
||||
|
||||
return "item";
|
||||
return multiple ? "items" : "item";
|
||||
};
|
||||
|
||||
@@ -53,7 +53,8 @@ const replaceKnownError = (errorMessage: string): string => {
|
||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||
} else if (
|
||||
errorMessage?.indexOf("The user aborted a request") >= 0 ||
|
||||
errorMessage?.indexOf("The operation was aborted") >= 0
|
||||
errorMessage?.indexOf("The operation was aborted") >= 0 ||
|
||||
errorMessage === "signal is aborted without reason"
|
||||
) {
|
||||
return "User aborted query.";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||
import Q from "q";
|
||||
import * as _ from "underscore";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { getDataExplorerWindow } from "../Utils/WindowUtils";
|
||||
import * as Constants from "./Constants";
|
||||
@@ -36,7 +38,7 @@ export function handleCachedDataMessage(message: any): void {
|
||||
* @returns
|
||||
*/
|
||||
export function sendCachedDataMessage<TResponseDataModel>(
|
||||
messageType: MessageTypes,
|
||||
messageType: MessageTypes | FabricMessageTypes,
|
||||
params: Object[],
|
||||
scope?: string,
|
||||
timeoutInMs?: number,
|
||||
@@ -95,10 +97,18 @@ const _sendMessage = (message: any): void => {
|
||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
||||
if (portalChildWindow === window) {
|
||||
// Current window is a child of portal, send message to portal window
|
||||
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
|
||||
if (portalChildWindow.document.referrer) {
|
||||
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer);
|
||||
} else {
|
||||
Logger.logError("Iframe failed to send message to portal", "MessageHandler");
|
||||
}
|
||||
} else {
|
||||
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
|
||||
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
|
||||
if (portalChildWindow.location.origin) {
|
||||
portalChildWindow.postMessage(message, portalChildWindow.location.origin);
|
||||
} else {
|
||||
Logger.logError("Iframe failed to send message to data explorer", "MessageHandler");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -550,6 +550,49 @@ export function deleteDocument_ToBeDeprecated(
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteDocuments(
|
||||
databaseId: string,
|
||||
collection: Collection,
|
||||
documentIds: DocumentId[],
|
||||
): Promise<{
|
||||
deletedCount: number;
|
||||
isAcknowledged: boolean;
|
||||
}> {
|
||||
const { databaseAccount } = userContext;
|
||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||
|
||||
const rids = documentIds.map((documentId) => documentId.id());
|
||||
|
||||
const params = {
|
||||
databaseID: databaseId,
|
||||
collectionID: collection.id(),
|
||||
resourceUrl: `${resourceEndpoint}`,
|
||||
resourceIDs: rids,
|
||||
subscriptionID: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseAccountName: databaseAccount.name,
|
||||
};
|
||||
const endpoint = getFeatureEndpointOrDefault("bulkdelete");
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}/bulkdelete`, {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
...authHeaders(),
|
||||
[HttpHeaders.contentType]: ContentType.applicationJson,
|
||||
},
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
return await errorHandling(response, "deleting documents", params);
|
||||
});
|
||||
}
|
||||
|
||||
export function createMongoCollectionWithProxy(
|
||||
params: DataModels.CreateCollectionParams,
|
||||
): Promise<DataModels.Collection> {
|
||||
@@ -672,6 +715,21 @@ export function getEndpoint(endpoint: string): string {
|
||||
return url;
|
||||
}
|
||||
|
||||
export function useMongoProxyEndpoint(api: string): boolean {
|
||||
const activeMongoProxyEndpoints: string[] = [
|
||||
MongoProxyEndpoints.Local,
|
||||
MongoProxyEndpoints.Mpac,
|
||||
MongoProxyEndpoints.Prod,
|
||||
MongoProxyEndpoints.Fairfax,
|
||||
MongoProxyEndpoints.Mooncake,
|
||||
];
|
||||
|
||||
return (
|
||||
configContext.NEW_MONGO_APIS?.includes(api) &&
|
||||
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: This function throws most of the time except on Forbidden which is a bit strange
|
||||
// It causes problems for TypeScript understanding the types
|
||||
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
||||
@@ -688,17 +746,3 @@ async function errorHandling(response: Response, action: string, params: unknown
|
||||
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
|
||||
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
|
||||
}
|
||||
|
||||
function useMongoProxyEndpoint(api: string): boolean {
|
||||
const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac];
|
||||
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
||||
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||
}
|
||||
|
||||
return (
|
||||
canAccessMongoProxy &&
|
||||
configContext.NEW_MONGO_APIS?.includes(api) &&
|
||||
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import * as _ from "underscore";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId";
|
||||
import { useDatabases } from "../Explorer/useDatabases";
|
||||
import { userContext } from "../UserContext";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
@@ -162,10 +161,10 @@ export class QueriesClient {
|
||||
{
|
||||
partitionKey: QueriesClient.PartitionKey,
|
||||
partitionKeyProperties: ["id"],
|
||||
} as DocumentsTab,
|
||||
} as IDocumentIdContainer,
|
||||
query,
|
||||
[query.queryName],
|
||||
); // TODO: Remove DocumentId's dependency on DocumentsTab
|
||||
);
|
||||
const options: any = { partitionKey: query.resourceId };
|
||||
return deleteDocument(queriesCollection, documentId)
|
||||
.then(
|
||||
|
||||
212
src/Common/QueryError.ts
Normal file
212
src/Common/QueryError.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { getErrorMessage } from "Common/ErrorHandlingUtils";
|
||||
import { monaco } from "Explorer/LazyMonaco";
|
||||
|
||||
export enum QueryErrorSeverity {
|
||||
Error = "Error",
|
||||
Warning = "Warning",
|
||||
}
|
||||
|
||||
export class QueryErrorLocation {
|
||||
constructor(
|
||||
public start: ErrorPosition,
|
||||
public end: ErrorPosition,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class ErrorPosition {
|
||||
constructor(
|
||||
public offset: number,
|
||||
public lineNumber?: number,
|
||||
public column?: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
// Maps severities to numbers for sorting.
|
||||
const severityMap: Record<QueryErrorSeverity, number> = {
|
||||
Error: 1,
|
||||
Warning: 0,
|
||||
};
|
||||
|
||||
export function compareSeverity(left: QueryErrorSeverity, right: QueryErrorSeverity): number {
|
||||
return severityMap[left] - severityMap[right];
|
||||
}
|
||||
|
||||
export function createMonacoErrorLocationResolver(
|
||||
editor: monaco.editor.IStandaloneCodeEditor,
|
||||
selection?: monaco.Selection,
|
||||
): (location: { start: number; end: number }) => QueryErrorLocation {
|
||||
return ({ start, end }) => {
|
||||
// Start and end are absolute offsets (character index) in the document.
|
||||
// But we need line numbers and columns for the monaco editor.
|
||||
// To get those, we use the editor's model to convert the offsets to positions.
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end));
|
||||
}
|
||||
|
||||
// If the error was found in a selection, adjust the start and end positions to be relative to the document.
|
||||
if (selection) {
|
||||
// Get the character index of the start of the selection.
|
||||
const selectionStartOffset = model.getOffsetAt(selection.getStartPosition());
|
||||
|
||||
// Adjust the start and end positions to be relative to the document.
|
||||
start = selectionStartOffset + start;
|
||||
end = selectionStartOffset + end;
|
||||
|
||||
// Now, when we resolve the positions, they will be relative to the document and appear in the correct location.
|
||||
}
|
||||
|
||||
const startPos = model.getPositionAt(start);
|
||||
const endPos = model.getPositionAt(end);
|
||||
return new QueryErrorLocation(
|
||||
new ErrorPosition(start, startPos.lineNumber, startPos.column),
|
||||
new ErrorPosition(end, endPos.lineNumber, endPos.column),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const createMonacoMarkersForQueryErrors = (errors: QueryError[]) => {
|
||||
if (!errors) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return errors
|
||||
.map((error): monaco.editor.IMarkerData => {
|
||||
// Validate that we have what we need to make a marker
|
||||
if (
|
||||
error.location === undefined ||
|
||||
error.location.start === undefined ||
|
||||
error.location.end === undefined ||
|
||||
error.location.start.lineNumber === undefined ||
|
||||
error.location.end.lineNumber === undefined ||
|
||||
error.location.start.column === undefined ||
|
||||
error.location.end.column === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
message: error.message,
|
||||
severity: error.getMonacoSeverity(),
|
||||
startLineNumber: error.location.start.lineNumber,
|
||||
startColumn: error.location.start.column,
|
||||
endLineNumber: error.location.end.lineNumber,
|
||||
endColumn: error.location.end.column,
|
||||
};
|
||||
})
|
||||
.filter((marker) => !!marker);
|
||||
};
|
||||
|
||||
export default class QueryError {
|
||||
constructor(
|
||||
public message: string,
|
||||
public severity: QueryErrorSeverity,
|
||||
public code?: string,
|
||||
public location?: QueryErrorLocation,
|
||||
) {}
|
||||
|
||||
getMonacoSeverity(): monaco.MarkerSeverity {
|
||||
// It's very difficult to use the monaco.MarkerSeverity enum from here, so we'll just use the numbers directly.
|
||||
// See: https://microsoft.github.io/monaco-editor/typedoc/enums/MarkerSeverity.html
|
||||
switch (this.severity) {
|
||||
case QueryErrorSeverity.Error:
|
||||
return 8;
|
||||
case QueryErrorSeverity.Warning:
|
||||
return 4;
|
||||
default:
|
||||
return 2; // Info
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempts to parse a query error from a string or object.
|
||||
*
|
||||
* @param error The error to parse.
|
||||
* @returns An array of query errors if the error could be parsed, or null otherwise.
|
||||
*/
|
||||
static tryParse(
|
||||
error: unknown,
|
||||
locationResolver?: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError[] {
|
||||
locationResolver =
|
||||
locationResolver ||
|
||||
(({ start, end }) => new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end)));
|
||||
const errors = QueryError.tryParseObject(error, locationResolver);
|
||||
if (errors !== null) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const errorMessage = getErrorMessage(error as string | Error);
|
||||
|
||||
// Map some well known messages to richer errors
|
||||
const knownError = knownErrors[errorMessage];
|
||||
if (knownError) {
|
||||
return [knownError];
|
||||
} else {
|
||||
return [new QueryError(errorMessage, QueryErrorSeverity.Error)];
|
||||
}
|
||||
}
|
||||
|
||||
static read(
|
||||
error: unknown,
|
||||
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError | null {
|
||||
if (typeof error !== "object" || error === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = "message" in error && typeof error.message === "string" ? error.message : undefined;
|
||||
if (!message) {
|
||||
return null; // Invalid error (no message).
|
||||
}
|
||||
|
||||
const severity =
|
||||
"severity" in error && typeof error.severity === "string" ? (error.severity as QueryErrorSeverity) : undefined;
|
||||
const location =
|
||||
"location" in error && typeof error.location === "object"
|
||||
? locationResolver(error.location as { start: number; end: number })
|
||||
: undefined;
|
||||
const code = "code" in error && typeof error.code === "string" ? error.code : undefined;
|
||||
return new QueryError(message, severity, code, location);
|
||||
}
|
||||
|
||||
private static tryParseObject(
|
||||
error: unknown,
|
||||
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError[] | null {
|
||||
if (typeof error === "object" && "message" in error) {
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
if (typeof error !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign to a new variable because of a TypeScript flow typing quirk, see below.
|
||||
let message = error;
|
||||
if (message.startsWith("Message: ")) {
|
||||
// Reassigning this to 'error' restores the original type of 'error', which is 'unknown'.
|
||||
// So we use a separate variable to avoid this.
|
||||
message = message.substring("Message: ".length);
|
||||
}
|
||||
|
||||
const lines = message.split("\n");
|
||||
message = lines[0].trim();
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(message);
|
||||
} catch (e) {
|
||||
// Not a query error.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof parsed === "object" && "errors" in parsed && Array.isArray(parsed.errors)) {
|
||||
return parsed.errors.map((e) => QueryError.read(e, locationResolver)).filter((e) => e !== null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const knownErrors: Record<string, QueryError> = {
|
||||
"User aborted query.": new QueryError("User aborted query.", QueryErrorSeverity.Warning),
|
||||
};
|
||||
@@ -1,90 +0,0 @@
|
||||
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||
import refreshImg from "../../images/refresh-cosmos.svg";
|
||||
import { AuthType } from "../AuthType";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
|
||||
import { ResourceTree2 } from "../Explorer/Tree2/ResourceTree";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
|
||||
import { Platform, configContext } from "./../ConfigContext";
|
||||
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.authType === AuthType.ResourceToken ? (
|
||||
<ResourceTokenTree />
|
||||
) : userContext.features.enableKoResourceTree ? (
|
||||
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
|
||||
) : configContext.platform === Platform.Fabric ? (
|
||||
<ResourceTree2 container={container} />
|
||||
) : (
|
||||
<ResourceTree container={container} />
|
||||
)}
|
||||
</div>
|
||||
{/* Collections Window - End */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -142,7 +142,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={EditIcon}
|
||||
alt="editEntity"
|
||||
alt={`Edit ${entityProperty} entity`}
|
||||
onClick={onEditEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPress}
|
||||
@@ -156,7 +156,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={DeleteIcon}
|
||||
alt="delete entity"
|
||||
alt={`Delete ${entityProperty} entity`}
|
||||
id="deleteEntity"
|
||||
onClick={onDeleteEntity}
|
||||
tabIndex={0}
|
||||
|
||||
@@ -3,11 +3,12 @@ import * as React from "react";
|
||||
|
||||
export interface TooltipProps {
|
||||
children: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
|
||||
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => {
|
||||
return (
|
||||
<span>
|
||||
<span className={className}>
|
||||
<TooltipHost content={children}>
|
||||
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
|
||||
Object {
|
||||
{
|
||||
"endpoint": "http://localhost/proxy",
|
||||
"headers": Object {
|
||||
"headers": {
|
||||
"x-ms-proxy-target": "http://localhost",
|
||||
},
|
||||
"path": "/dbs/foo",
|
||||
@@ -11,9 +11,9 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
|
||||
Object {
|
||||
{
|
||||
"endpoint": "http://localhost/proxy",
|
||||
"headers": Object {
|
||||
"headers": {
|
||||
"x-ms-proxy-target": "baz",
|
||||
},
|
||||
"path": "/dbs/foo",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`nextPage returns results for the next page 1`] = `
|
||||
Object {
|
||||
{
|
||||
"activityId": "foo",
|
||||
"documents": Array [],
|
||||
"documents": [],
|
||||
"firstItemIndex": 11,
|
||||
"hasMoreResults": false,
|
||||
"headers": Object {},
|
||||
"headers": {},
|
||||
"itemCount": 0,
|
||||
"lastItemIndex": 10,
|
||||
"requestCharge": 1,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getCommonQueryOptions builds the correct default options objects 1`] = `
|
||||
Object {
|
||||
{
|
||||
"disableNonStreamingOrderByQuery": true,
|
||||
"enableScanInQuery": true,
|
||||
"forceQueryPlan": true,
|
||||
"maxDegreeOfParallelism": 0,
|
||||
@@ -11,7 +12,8 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`getCommonQueryOptions reads from localStorage 1`] = `
|
||||
Object {
|
||||
{
|
||||
"disableNonStreamingOrderByQuery": true,
|
||||
"enableScanInQuery": true,
|
||||
"forceQueryPlan": true,
|
||||
"maxDegreeOfParallelism": 17,
|
||||
|
||||
@@ -6,13 +6,13 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
|
||||
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
|
||||
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
|
||||
@@ -96,6 +96,9 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
|
||||
if (params.uniqueKeyPolicy) {
|
||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
||||
}
|
||||
if (params.vectorEmbeddingPolicy) {
|
||||
resource.vectorEmbeddingPolicy = params.vectorEmbeddingPolicy;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
@@ -266,6 +269,7 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
|
||||
indexingPolicy: params.indexingPolicy || undefined,
|
||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
||||
analyticalStorageTtl: params.analyticalStorageTtl,
|
||||
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
|
||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||
const collectionOptions: RequestOptions = {};
|
||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { useDatabases } from "../../Explorer/useDatabases";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
MongoDBDatabaseCreateUpdateParameters,
|
||||
SqlDatabaseCreateUpdateParameters,
|
||||
} from "../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
@@ -152,8 +152,18 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
|
||||
createBody.throughput = params.offerThroughput;
|
||||
}
|
||||
}
|
||||
|
||||
const response: DatabaseResponse = await client().databases.create(createBody);
|
||||
let response: DatabaseResponse;
|
||||
try {
|
||||
response = await client().databases.create(createBody);
|
||||
} catch (error) {
|
||||
if (error.message.includes("Shared throughput database creation is not supported for serverless accounts")) {
|
||||
createBody.maxThroughput = undefined;
|
||||
createBody.throughput = undefined;
|
||||
response = await client().databases.create(createBody);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return response.resource;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BulkOperationType, OperationInput } from "@azure/cosmos";
|
||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
@@ -24,3 +25,58 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
||||
clearMessage();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bulk delete documents
|
||||
* @param collection
|
||||
* @param documentId
|
||||
* @returns array of ids that were successfully deleted
|
||||
*/
|
||||
export const deleteDocuments = async (collection: CollectionBase, documentIds: DocumentId[]): Promise<DocumentId[]> => {
|
||||
const nbDocuments = documentIds.length;
|
||||
const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`);
|
||||
try {
|
||||
const v2Container = await client().database(collection.databaseId).container(collection.id());
|
||||
|
||||
// Bulk can only delete 100 documents at a time
|
||||
const BULK_DELETE_LIMIT = 100;
|
||||
const promiseArray = [];
|
||||
|
||||
while (documentIds.length > 0) {
|
||||
const documentIdsChunk = documentIds.splice(0, BULK_DELETE_LIMIT);
|
||||
const operations: OperationInput[] = documentIdsChunk.map((documentId) => ({
|
||||
id: documentId.id(),
|
||||
// bulk delete: if not partition key is specified, do not pass empty array, but undefined
|
||||
partitionKey:
|
||||
documentId.partitionKeyValue &&
|
||||
Array.isArray(documentId.partitionKeyValue) &&
|
||||
documentId.partitionKeyValue.length === 0
|
||||
? undefined
|
||||
: documentId.partitionKeyValue,
|
||||
operationType: BulkOperationType.Delete,
|
||||
}));
|
||||
|
||||
const promise = v2Container.items.bulk(operations).then((bulkResult) => {
|
||||
return documentIdsChunk.filter((_, index) => bulkResult[index].statusCode === 204);
|
||||
});
|
||||
promiseArray.push(promise);
|
||||
}
|
||||
|
||||
const allResult = await Promise.all(promiseArray);
|
||||
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;
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
"DeleteDocuments",
|
||||
`Error while deleting ${documentIds.length} ${getEntityName(documentIds.length > 1)}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||
import { Queries } from "../Constants";
|
||||
import { client } from "../CosmosClient";
|
||||
@@ -26,5 +27,6 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
|
||||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
||||
Queries.itemsPerPage;
|
||||
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
||||
options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled();
|
||||
return options;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { CosmosClient } from "@azure/cosmos";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import { userContext } from "UserContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
@@ -31,7 +30,6 @@ export async function readCollectionInternal(
|
||||
collectionId: string,
|
||||
): Promise<DataModels.Collection> {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
|
||||
try {
|
||||
const response = await cosmosClient.database(databaseId).container(collectionId).read();
|
||||
collection = response.resource as DataModels.Collection;
|
||||
@@ -39,6 +37,5 @@ export async function readCollectionInternal(
|
||||
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return collection;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ export interface ConfigContext {
|
||||
ARM_API_VERSION: string;
|
||||
GRAPH_ENDPOINT: string;
|
||||
GRAPH_API_VERSION: string;
|
||||
// This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP
|
||||
CATALOG_ENDPOINT: string;
|
||||
CATALOG_API_VERSION: string;
|
||||
CATALOG_API_KEY: string;
|
||||
ARCADIA_ENDPOINT: string;
|
||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||
BACKEND_ENDPOINT?: string;
|
||||
@@ -49,10 +53,8 @@ export interface ConfigContext {
|
||||
NEW_BACKEND_APIS?: BackendApi[];
|
||||
MONGO_BACKEND_ENDPOINT?: string;
|
||||
MONGO_PROXY_ENDPOINT?: string;
|
||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
||||
NEW_MONGO_APIS?: string[];
|
||||
CASSANDRA_PROXY_ENDPOINT?: string;
|
||||
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
|
||||
NEW_CASSANDRA_APIS?: string[];
|
||||
PROXY_PATH?: string;
|
||||
JUNO_ENDPOINT: string;
|
||||
@@ -83,6 +85,7 @@ let configContext: Readonly<ConfigContext> = {
|
||||
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
||||
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
|
||||
`^https:\\/\\/.*\\.azure-test\\.net$`,
|
||||
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net$`,
|
||||
], // Webpack injects this at build time
|
||||
gitSha: process.env.GIT_SHA,
|
||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||
@@ -92,6 +95,9 @@ let configContext: Readonly<ConfigContext> = {
|
||||
ARM_API_VERSION: "2016-06-01",
|
||||
GRAPH_ENDPOINT: "https://graph.microsoft.com",
|
||||
GRAPH_API_VERSION: "1.6",
|
||||
CATALOG_ENDPOINT: "https://catalogapi.azure.com/",
|
||||
CATALOG_API_VERSION: "2023-05-01-preview",
|
||||
CATALOG_API_KEY: "",
|
||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
||||
@@ -99,7 +105,6 @@ let configContext: Readonly<ConfigContext> = {
|
||||
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
||||
NEW_BACKEND_APIS: [BackendApi.GenerateToken],
|
||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||
NEW_MONGO_APIS: [
|
||||
"resourcelist",
|
||||
@@ -109,11 +114,11 @@ let configContext: Readonly<ConfigContext> = {
|
||||
"updateDocument",
|
||||
"deleteDocument",
|
||||
"createCollectionWithProxy",
|
||||
"legacyMongoShell",
|
||||
"bulkdelete",
|
||||
],
|
||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
||||
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
|
||||
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||
isTerminalEnabled: false,
|
||||
isPhoenixEnabled: false,
|
||||
};
|
||||
@@ -191,6 +196,9 @@ if (process.env.NODE_ENV === "development") {
|
||||
updateConfigContext({
|
||||
PROXY_PATH: "/proxy",
|
||||
EMULATOR_ENDPOINT: "https://localhost:8081",
|
||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
|
||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export interface QueryRequestOptions {
|
||||
$skipToken?: string;
|
||||
$top?: number;
|
||||
subscriptions: string[];
|
||||
$allowPartialScopes: boolean;
|
||||
subscriptions?: string[];
|
||||
}
|
||||
|
||||
export interface QueryResponse {
|
||||
|
||||
@@ -1,37 +1,22 @@
|
||||
import { MessageTypes } from "./MessageTypes";
|
||||
import { FabricMessageTypes } from "./FabricMessageTypes";
|
||||
|
||||
// This is the current version of these messages
|
||||
export const DATA_EXPLORER_RPC_VERSION = "2";
|
||||
export const DATA_EXPLORER_RPC_VERSION = "3";
|
||||
|
||||
// Data Explorer to Fabric
|
||||
|
||||
// TODO Remove when upgrading to Fabric v2
|
||||
export type DataExploreMessageV1 =
|
||||
| "ready"
|
||||
export type DataExploreMessageV3 =
|
||||
| {
|
||||
type: MessageTypes.GetAuthorizationToken;
|
||||
id: string;
|
||||
params: GetCosmosTokenMessageOptions[];
|
||||
}
|
||||
| {
|
||||
type: MessageTypes.GetAllResourceTokens;
|
||||
id: string;
|
||||
};
|
||||
// -----------------------------
|
||||
|
||||
export type DataExploreMessageV2 =
|
||||
| {
|
||||
type: MessageTypes.Ready;
|
||||
type: FabricMessageTypes.Ready;
|
||||
id: string;
|
||||
params: [string]; // version
|
||||
}
|
||||
| {
|
||||
type: MessageTypes.GetAuthorizationToken;
|
||||
type: FabricMessageTypes.GetAuthorizationToken;
|
||||
id: string;
|
||||
params: GetCosmosTokenMessageOptions[];
|
||||
}
|
||||
| {
|
||||
type: MessageTypes.GetAllResourceTokens;
|
||||
type: FabricMessageTypes.GetAllResourceTokens;
|
||||
id: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
import { CapacityMode, ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
|
||||
export interface ArmEntity {
|
||||
id: string;
|
||||
@@ -35,6 +35,7 @@ export interface DatabaseAccountExtendedProperties {
|
||||
ipRules?: IpRule[];
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
capacityMode?: CapacityMode;
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
postgresqlEndpoint?: string;
|
||||
publicNetworkAccess?: string;
|
||||
@@ -157,6 +158,7 @@ export interface Collection extends Resource {
|
||||
changeFeedPolicy?: ChangeFeedPolicy;
|
||||
analyticalStorageTtl?: number;
|
||||
geospatialConfig?: GeospatialConfig;
|
||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||
schema?: ISchema;
|
||||
requestSchema?: () => void;
|
||||
computedProperties?: ComputedProperties;
|
||||
@@ -194,8 +196,14 @@ export interface IndexingPolicy {
|
||||
indexingMode: "consistent" | "lazy" | "none";
|
||||
includedPaths: any;
|
||||
excludedPaths: any;
|
||||
compositeIndexes?: any;
|
||||
spatialIndexes?: any;
|
||||
compositeIndexes?: any[];
|
||||
spatialIndexes?: any[];
|
||||
vectorIndexes?: VectorIndex[];
|
||||
}
|
||||
|
||||
export interface VectorIndex {
|
||||
path: string;
|
||||
type: "flat" | "diskANN" | "quantizedFlat";
|
||||
}
|
||||
|
||||
export interface ComputedProperty {
|
||||
@@ -333,6 +341,18 @@ export interface CreateCollectionParams {
|
||||
partitionKey?: PartitionKey;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||
createMongoWildcardIndex?: boolean;
|
||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||
}
|
||||
|
||||
export interface VectorEmbeddingPolicy {
|
||||
vectorEmbeddings: VectorEmbedding[];
|
||||
}
|
||||
|
||||
export interface VectorEmbedding {
|
||||
dataType: "float16" | "float32" | "uint8" | "int8";
|
||||
dimensions: number;
|
||||
distanceFunction: "euclidean" | "cosine" | "dotproduct";
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ReadDatabaseOfferParams {
|
||||
|
||||
13
src/Contracts/FabricMessageTypes.ts
Normal file
13
src/Contracts/FabricMessageTypes.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Data Explorer -> Fabric communication.
|
||||
*/
|
||||
export enum FabricMessageTypes {
|
||||
GetAuthorizationToken = "GetAuthorizationToken",
|
||||
GetAllResourceTokens = "GetAllResourceTokens",
|
||||
Ready = "Ready",
|
||||
}
|
||||
|
||||
export interface AuthorizationToken {
|
||||
XDate: string;
|
||||
PrimaryReadWriteToken: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AuthorizationToken } from "./MessageTypes";
|
||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||
|
||||
// This is the version of these messages
|
||||
export const FABRIC_RPC_VERSION = "2";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Messaging types used with Data Explorer <-> Portal communication,
|
||||
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
||||
* Hosted <-> Explorer communication
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* Enum are integers, so inserting or deleting a type will break the communication.
|
||||
*
|
||||
*/
|
||||
export enum MessageTypes {
|
||||
TelemetryInfo,
|
||||
@@ -43,14 +44,9 @@ export enum MessageTypes {
|
||||
DisplayNPSSurvey,
|
||||
OpenVCoreMongoNetworkingBlade,
|
||||
OpenVCoreMongoConnectionStringsBlade,
|
||||
GetAuthorizationToken, // Data Explorer -> Fabric
|
||||
GetAllResourceTokens, // Data Explorer -> Fabric
|
||||
Ready, // Data Explorer -> Fabric
|
||||
GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums.
|
||||
GetAllResourceTokens, // unused. Can be removed if the portal uses the same list of enums.
|
||||
Ready, // unused. Can be removed if the portal uses the same list of enums.
|
||||
OpenCESCVAFeedbackBlade,
|
||||
ActivateTab,
|
||||
}
|
||||
|
||||
export interface AuthorizationToken {
|
||||
XDate: string;
|
||||
PrimaryReadWriteToken: string;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,11 @@ export interface Collection extends CollectionBase {
|
||||
loadTriggers(): Promise<any>;
|
||||
loadOffer(): Promise<void>;
|
||||
|
||||
showStoredProcedures: ko.Observable<boolean>;
|
||||
showTriggers: ko.Observable<boolean>;
|
||||
showUserDefinedFunctions: ko.Observable<boolean>;
|
||||
showConflicts: ko.Observable<boolean>;
|
||||
|
||||
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
||||
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
||||
createTriggerNode(data: TriggerDefinition | SqlTriggerResource): Trigger;
|
||||
@@ -324,9 +329,9 @@ export enum DocumentExplorerState {
|
||||
noDocumentSelected,
|
||||
newDocumentValid,
|
||||
newDocumentInvalid,
|
||||
exisitingDocumentNoEdits,
|
||||
exisitingDocumentDirtyValid,
|
||||
exisitingDocumentDirtyInvalid,
|
||||
existingDocumentNoEdits,
|
||||
existingDocumentDirtyValid,
|
||||
existingDocumentDirtyInvalid,
|
||||
}
|
||||
|
||||
export enum IndexingPolicyEditorState {
|
||||
@@ -339,9 +344,9 @@ export enum IndexingPolicyEditorState {
|
||||
export enum ScriptEditorState {
|
||||
newInvalid,
|
||||
newValid,
|
||||
exisitingNoEdits,
|
||||
exisitingDirtyValid,
|
||||
exisitingDirtyInvalid,
|
||||
existingNoEdits,
|
||||
existingDirtyValid,
|
||||
existingDirtyInvalid,
|
||||
}
|
||||
|
||||
export enum CollectionTabKind {
|
||||
@@ -420,6 +425,7 @@ export interface SelfServeFrameInputs {
|
||||
authorizationToken: string;
|
||||
csmEndpoint: string;
|
||||
flights?: readonly string[];
|
||||
catalogAPIKey: string;
|
||||
}
|
||||
|
||||
export class MonacoEditorSettings {
|
||||
|
||||
@@ -36,21 +36,21 @@ describe("The Heatmap Control", () => {
|
||||
});
|
||||
|
||||
it("should call _getChartSettings when drawHeatmap is invoked", () => {
|
||||
const _getChartSettings = spyOn<any>(heatmap, "_getChartSettings");
|
||||
const _getChartSettings = jest.spyOn(heatmap, "_getChartSettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getChartSettings.calls.any()).toBe(true);
|
||||
expect(_getChartSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call _getLayoutSettings when drawHeatmap is invoked", () => {
|
||||
const _getLayoutSettings = spyOn<any>(heatmap, "_getLayoutSettings");
|
||||
const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getLayoutSettings.calls.any()).toBe(true);
|
||||
expect(_getLayoutSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => {
|
||||
const _getChartDisplaySettings = spyOn<any>(heatmap, "_getChartDisplaySettings");
|
||||
const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getChartDisplaySettings.calls.any()).toBe(true);
|
||||
expect(_getChartDisplaySettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("drawHeatmap should render a Heatmap inside the div element", () => {
|
||||
|
||||
@@ -96,7 +96,8 @@ export class Heatmap {
|
||||
return output;
|
||||
}
|
||||
|
||||
private _getChartSettings(): ChartSettings[] {
|
||||
// public for testing purposes
|
||||
public _getChartSettings(): ChartSettings[] {
|
||||
return [
|
||||
{
|
||||
z: this._chartData.dataPoints,
|
||||
@@ -131,7 +132,8 @@ export class Heatmap {
|
||||
];
|
||||
}
|
||||
|
||||
private _getLayoutSettings(): LayoutSettings {
|
||||
// public for testing purposes
|
||||
public _getLayoutSettings(): LayoutSettings {
|
||||
return {
|
||||
margin: {
|
||||
l: 40,
|
||||
@@ -177,7 +179,8 @@ export class Heatmap {
|
||||
};
|
||||
}
|
||||
|
||||
private _getChartDisplaySettings(): DisplaySettings {
|
||||
// public for testing purposes
|
||||
public _getChartDisplaySettings(): DisplaySettings {
|
||||
return {
|
||||
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
|
||||
responsive: true,*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -19,7 +20,6 @@ import { userContext } from "../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
import { Platform, configContext } from "./../ConfigContext";
|
||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "./Explorer";
|
||||
import { useNotebook } from "./Notebook/useNotebook";
|
||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||
@@ -41,6 +41,10 @@ export interface DatabaseContextMenuButtonParams {
|
||||
* New resource tree (in ReactJS)
|
||||
*/
|
||||
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: TreeNodeMenuItem[] = [
|
||||
{
|
||||
iconSrc: AddCollectionIcon,
|
||||
@@ -100,6 +104,16 @@ export const createCollectionContextMenuButton = (
|
||||
});
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isShellEnabled && userContext.apiType === "Cassandra") {
|
||||
items.push({
|
||||
iconSrc: HostedTerminalIcon,
|
||||
onClick: () => {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||
},
|
||||
label: "Open Cassandra Shell",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
configContext.platform !== Platform.Fabric &&
|
||||
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Icon, Label, Stack } from "@fluentui/react";
|
||||
import { DirectionalHint, Icon, Label, Stack, TooltipHost } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||
@@ -8,6 +8,7 @@ export interface CollapsibleSectionProps {
|
||||
isExpandedByDefault: boolean;
|
||||
onExpand?: () => void;
|
||||
children: JSX.Element;
|
||||
tooltipContent?: string | JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
export interface CollapsibleSectionState {
|
||||
@@ -26,8 +27,8 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
if (this.state.isExpanded && this.props.onExpand) {
|
||||
public componentDidUpdate(_prevProps: CollapsibleSectionProps, prevState: CollapsibleSectionState): void {
|
||||
if (!prevState.isExpanded && this.state.isExpanded && this.props.onExpand) {
|
||||
this.props.onExpand();
|
||||
}
|
||||
}
|
||||
@@ -43,7 +44,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
className="collapsibleSection"
|
||||
className={"collapsibleSection"}
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
tokens={accordionStackTokens}
|
||||
@@ -55,6 +56,19 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
>
|
||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||
<Label>{this.props.title}</Label>
|
||||
{this.props.tooltipContent && (
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={this.props.tooltipContent}
|
||||
styles={{
|
||||
root: {
|
||||
marginLeft: "0 !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
)}
|
||||
</Stack>
|
||||
{this.state.isExpanded && this.props.children}
|
||||
</>
|
||||
|
||||
@@ -11,7 +11,7 @@ exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* React component for Command button component.
|
||||
*/
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import * as React from "react";
|
||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
@@ -30,7 +31,7 @@ export interface CommandButtonComponentProps {
|
||||
/**
|
||||
* Click handler for command button click
|
||||
*/
|
||||
onCommandClick: (e: React.SyntheticEvent) => void;
|
||||
onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Label for the button
|
||||
@@ -107,10 +108,17 @@ export interface CommandButtonComponentProps {
|
||||
* Vertical bar to divide buttons
|
||||
*/
|
||||
isDivider?: boolean;
|
||||
|
||||
/**
|
||||
* Aria-label for the button
|
||||
*/
|
||||
ariaLabel: string;
|
||||
|
||||
/**
|
||||
* If specified, a keyboard action that should trigger this button's onCommandClick handler when activated.
|
||||
* If not specified, the button will not be triggerable by keyboard shortcuts.
|
||||
*/
|
||||
keyboardAction?: KeyboardAction;
|
||||
}
|
||||
|
||||
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
||||
|
||||
@@ -3,6 +3,37 @@ import * as React from "react";
|
||||
import { loadMonaco, monaco } from "../../LazyMonaco";
|
||||
// import "./EditorReact.less";
|
||||
|
||||
// In development, add a function to window to allow us to get the editor instance for a given element
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const win = window as any;
|
||||
win._monaco_getEditorForElement =
|
||||
win._monaco_getEditorForElement ||
|
||||
((element: HTMLElement) => {
|
||||
const editorId = element.dataset["monacoEditorId"];
|
||||
if (!editorId || !win.__monaco_editors || typeof win.__monaco_editors !== "object") {
|
||||
return null;
|
||||
}
|
||||
return win.__monaco_editors[editorId];
|
||||
});
|
||||
|
||||
win._monaco_getEditorContentForElement =
|
||||
win._monaco_getEditorContentForElement ||
|
||||
((element: HTMLElement) => {
|
||||
const editor = win._monaco_getEditorForElement(element);
|
||||
return editor ? editor.getValue() : null;
|
||||
});
|
||||
|
||||
win._monaco_setEditorContentForElement =
|
||||
win._monaco_setEditorContentForElement ||
|
||||
((element: HTMLElement, text: string) => {
|
||||
const editor = win._monaco_getEditorForElement(element);
|
||||
if (editor) {
|
||||
editor.setValue(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface EditorReactStates {
|
||||
showEditor: boolean;
|
||||
}
|
||||
@@ -11,7 +42,7 @@ export interface EditorReactProps {
|
||||
content: string;
|
||||
isReadOnly: boolean;
|
||||
ariaLabel: string; // Sets what will be read to the user to define the control
|
||||
onContentSelected?: (selectedContent: string) => void; // Called when text is selected
|
||||
onContentSelected?: (selectedContent: string, selection: monaco.Selection) => void; // Called when text is selected
|
||||
onContentChanged?: (newContent: string) => void; // Called when text is changed
|
||||
theme?: string; // Monaco editor theme
|
||||
wordWrap?: monaco.editor.IEditorOptions["wordWrap"];
|
||||
@@ -20,13 +51,38 @@ export interface EditorReactProps {
|
||||
lineDecorationsWidth?: monaco.editor.IEditorOptions["lineDecorationsWidth"];
|
||||
minimap?: monaco.editor.IEditorOptions["minimap"];
|
||||
scrollBeyondLastLine?: monaco.editor.IEditorOptions["scrollBeyondLastLine"];
|
||||
fontSize?: monaco.editor.IEditorOptions["fontSize"];
|
||||
monacoContainerStyles?: React.CSSProperties;
|
||||
className?: string;
|
||||
spinnerClassName?: string;
|
||||
|
||||
modelMarkers?: monaco.editor.IMarkerData[];
|
||||
enableWordWrapContextMenuItem?: boolean; // Enable/Disable "Word Wrap" context menu item
|
||||
onWordWrapChanged?: (wordWrap: "on" | "off") => void; // Called when word wrap is changed
|
||||
}
|
||||
|
||||
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
|
||||
private static readonly VIEWING_OPTIONS_GROUP_ID = "viewingoptions"; // Group ID for the context menu group
|
||||
private rootNode: HTMLElement;
|
||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||
public editor: monaco.editor.IStandaloneCodeEditor;
|
||||
private selectionListener: monaco.IDisposable;
|
||||
monacoApi: {
|
||||
default: typeof monaco;
|
||||
Emitter: typeof monaco.Emitter;
|
||||
MarkerTag: typeof monaco.MarkerTag;
|
||||
MarkerSeverity: typeof monaco.MarkerSeverity;
|
||||
CancellationTokenSource: typeof monaco.CancellationTokenSource;
|
||||
Uri: typeof monaco.Uri;
|
||||
KeyCode: typeof monaco.KeyCode;
|
||||
KeyMod: typeof monaco.KeyMod;
|
||||
Position: typeof monaco.Position;
|
||||
Range: typeof monaco.Range;
|
||||
Selection: typeof monaco.Selection;
|
||||
SelectionDirection: typeof monaco.SelectionDirection;
|
||||
Token: typeof monaco.Token;
|
||||
editor: typeof monaco.editor;
|
||||
languages: typeof monaco.languages;
|
||||
};
|
||||
|
||||
public constructor(props: EditorReactProps) {
|
||||
super(props);
|
||||
@@ -46,10 +102,28 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public componentDidUpdate(previous: EditorReactProps) {
|
||||
if (this.props.content !== previous.content) {
|
||||
this.editor?.setValue(this.props.content);
|
||||
public componentDidUpdate() {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingContent = this.editor.getModel().getValue();
|
||||
|
||||
if (this.props.content !== existingContent) {
|
||||
if (this.props.isReadOnly) {
|
||||
this.editor.setValue(this.props.content || ""); // Monaco throws an error if you set the value to undefined.
|
||||
} else {
|
||||
this.editor.pushUndoStop();
|
||||
this.editor.executeEdits("", [
|
||||
{
|
||||
range: this.editor.getModel().getFullModelRange(),
|
||||
text: this.props.content,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
this.monacoApi.editor.setModelMarkers(this.editor.getModel(), "owner", this.props.modelMarkers || []);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
@@ -59,9 +133,12 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
|
||||
{!this.state.showEditor && (
|
||||
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
|
||||
)}
|
||||
<div
|
||||
className="jsonEditor"
|
||||
data-test="EditorReact/Host/Unloaded"
|
||||
className={this.props.className || "jsonEditor"}
|
||||
style={this.props.monacoContainerStyles}
|
||||
ref={(elt: HTMLElement) => this.setRef(elt)}
|
||||
/>
|
||||
@@ -71,9 +148,26 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
|
||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||
this.editor = editor;
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
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) {
|
||||
queryEditorModel.onDidChangeContent(() => {
|
||||
// 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),
|
||||
// then there are some inconsistencies as to which event fires first.
|
||||
// But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event.
|
||||
// (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.)
|
||||
// If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first.
|
||||
this.editor.onDidChangeModelContent(() => {
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
this.props.onContentChanged(queryEditorModel.getValue());
|
||||
});
|
||||
@@ -83,10 +177,27 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
this.selectionListener = this.editor.onDidChangeCursorSelection(
|
||||
(event: monaco.editor.ICursorSelectionChangedEvent) => {
|
||||
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection);
|
||||
this.props.onContentSelected(selectedContent);
|
||||
this.props.onContentSelected(selectedContent, event.selection);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +209,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
value: this.props.content,
|
||||
readOnly: this.props.isReadOnly,
|
||||
ariaLabel: this.props.ariaLabel,
|
||||
fontSize: 12,
|
||||
fontSize: this.props.fontSize || 12,
|
||||
automaticLayout: true,
|
||||
theme: this.props.theme,
|
||||
wordWrap: this.props.wordWrap || "off",
|
||||
@@ -107,11 +218,19 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
lineDecorationsWidth: this.props.lineDecorationsWidth,
|
||||
minimap: this.props.minimap,
|
||||
scrollBeyondLastLine: this.props.scrollBeyondLastLine,
|
||||
fixedOverflowWidgets: true,
|
||||
};
|
||||
|
||||
this.rootNode.innerHTML = "";
|
||||
const monaco = await loadMonaco();
|
||||
createCallback(monaco?.editor?.create(this.rootNode, options));
|
||||
this.monacoApi = await loadMonaco();
|
||||
|
||||
try {
|
||||
createCallback(this.monacoApi.editor.create(this.rootNode, options));
|
||||
} catch (error) {
|
||||
// This could happen if the parent node suddenly disappears during create()
|
||||
console.error("Unable to create EditorReact", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.rootNode.innerHTML) {
|
||||
this.setState({
|
||||
|
||||
@@ -18,7 +18,7 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
<Stack
|
||||
className="options"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
horizontal={true}
|
||||
horizontalAlign="start"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -61,16 +61,16 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
label="Base Url"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "https://localhost:1234/explorer.html",
|
||||
"text": "localhost:1234",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "https://cosmos.azure.com/explorer.html",
|
||||
"text": "cosmos.azure.com",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "https://portal.azure.com",
|
||||
"text": "portal",
|
||||
},
|
||||
@@ -78,8 +78,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
}
|
||||
selectedKey="https://localhost:1234/explorer.html"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
{
|
||||
"dropdown": {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
@@ -89,20 +89,20 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
label="Platform"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "Hosted",
|
||||
"text": "Hosted",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "Portal",
|
||||
"text": "Portal",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "Emulator",
|
||||
"text": "Emulator",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "",
|
||||
"text": "None",
|
||||
},
|
||||
@@ -110,8 +110,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
}
|
||||
selectedKey="Hosted"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
{
|
||||
"dropdown": {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
@@ -208,7 +208,7 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -222,8 +222,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder="https://notebookserver"
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -235,8 +235,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -248,8 +248,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -265,8 +265,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -279,8 +279,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder="https://localhost:1234/explorer.html"
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -292,8 +292,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
|
||||
37
src/Explorer/Controls/IndeterminateProgressBar.tsx
Normal file
37
src/Explorer/Controls/IndeterminateProgressBar.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
68
src/Explorer/Controls/MessageBanner.tsx
Normal file
68
src/Explorer/Controls/MessageBanner.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -4,8 +4,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardBase
|
||||
aria-label="name"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"display": "inline-block",
|
||||
"marginRight": 20,
|
||||
"width": 256,
|
||||
@@ -16,8 +16,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardActivityBase
|
||||
activity="Invalid Date"
|
||||
people={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"name": "author",
|
||||
"profileImageSrc": false,
|
||||
},
|
||||
@@ -26,8 +26,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<StyledDocumentCardPreviewBase
|
||||
previewImages={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"height": 144,
|
||||
"imageFit": 2,
|
||||
"previewImageSrc": "thumbnailUrl",
|
||||
@@ -40,8 +40,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Text
|
||||
nowrap={true}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"height": 18,
|
||||
"padding": "2px 16px",
|
||||
},
|
||||
@@ -69,15 +69,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"padding": "8px 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -88,8 +88,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="RedEye"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -100,8 +100,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -112,8 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="Download"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -124,8 +124,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -136,8 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="Heart"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -151,8 +151,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardDetailsBase>
|
||||
<Separator
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"height": 1,
|
||||
"padding": 0,
|
||||
},
|
||||
@@ -161,22 +161,22 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"padding": "0px 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
Object {
|
||||
{
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Favorite"
|
||||
id="TooltipHost-IconButton-Heart"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"display": "inline-block",
|
||||
"float": "left",
|
||||
},
|
||||
@@ -186,7 +186,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Favorite"
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "Heart",
|
||||
}
|
||||
}
|
||||
@@ -196,15 +196,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</StyledTooltipHostBase>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
Object {
|
||||
{
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Download"
|
||||
id="TooltipHost-IconButton-Download"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"display": "inline-block",
|
||||
"float": "left",
|
||||
},
|
||||
@@ -214,7 +214,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Download"
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "Download",
|
||||
}
|
||||
}
|
||||
@@ -224,15 +224,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</StyledTooltipHostBase>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
Object {
|
||||
{
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Remove"
|
||||
id="TooltipHost-IconButton-Delete"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"display": "inline-block",
|
||||
"float": "right",
|
||||
},
|
||||
@@ -242,7 +242,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Remove"
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "Delete",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`CodeOfConduct renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports[`CodeOfConduct renders 1`] = `
|
||||
<StackItem>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 500,
|
||||
}
|
||||
@@ -41,12 +41,12 @@ exports[`CodeOfConduct renders 1`] = `
|
||||
label="I have read and accept the code of conduct."
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
{
|
||||
"label": {
|
||||
"margin": 0,
|
||||
"padding": "2 0 2 0",
|
||||
},
|
||||
"text": Object {
|
||||
"text": {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`InfoComponent renders 1`] = `
|
||||
<StyledHoverCardBase
|
||||
instantOpenOnClick={true}
|
||||
plainCardProps={
|
||||
Object {
|
||||
{
|
||||
"onRenderPlainCard": [Function],
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ exports[`InfoComponent renders 1`] = `
|
||||
className="infoIconMain"
|
||||
iconName="Help"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
itemKey="OfficialSamples"
|
||||
key="OfficialSamples"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
"padding": 10,
|
||||
}
|
||||
@@ -50,8 +50,8 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
</StackItem>
|
||||
<StackItem
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"minWidth": 200,
|
||||
},
|
||||
}
|
||||
@@ -60,20 +60,20 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
<Dropdown
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": 0,
|
||||
"text": "Most viewed",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": 1,
|
||||
"text": "Most downloaded",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": 3,
|
||||
"text": "Most recent",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": 2,
|
||||
"text": "Most favorited",
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 30,
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "HeartFill",
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -96,8 +96,8 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
@@ -115,7 +115,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 30,
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "Heart",
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -208,8 +208,8 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
}
|
||||
|
||||
.settingsV2ToolTip {
|
||||
padding: 10px;
|
||||
font: 12px @DataExplorerFont;
|
||||
max-width: 300px;
|
||||
padding: 10px;
|
||||
font: 12px @DataExplorerFont;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.autoPilotSelector span {
|
||||
height: 25px;
|
||||
font: 14px @DataExplorerFont;
|
||||
height: 25px;
|
||||
font: 14px @DataExplorerFont;
|
||||
}
|
||||
|
||||
.settingsV2TabsContainer {
|
||||
@@ -25,7 +25,14 @@
|
||||
font-family: @DataExplorerFont;
|
||||
}
|
||||
|
||||
.settingsV2IndexingPolicyEditor {
|
||||
.settingsV2Editor {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
.settingsV2EditorSpinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ import {
|
||||
ComputedPropertiesComponent,
|
||||
ComputedPropertiesComponentProps,
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
|
||||
import {
|
||||
ContainerVectorPolicyComponent,
|
||||
ContainerVectorPolicyComponentProps,
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||
import * as React from "react";
|
||||
import DiscardIcon from "../../../../images/discard.svg";
|
||||
@@ -144,6 +149,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private shouldShowComputedPropertiesEditor: boolean;
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
private shouldShowPartitionKeyEditor: boolean;
|
||||
private isVectorSearchEnabled: boolean;
|
||||
private totalThroughputUsed: number;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
@@ -158,6 +164,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL";
|
||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
||||
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||
|
||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||
|
||||
@@ -1097,6 +1104,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange,
|
||||
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
||||
};
|
||||
|
||||
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||
@@ -1143,6 +1151,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
explorer: this.props.settingsTab.getContainer(),
|
||||
};
|
||||
|
||||
const containerVectorPolicyProps: ContainerVectorPolicyComponentProps = {
|
||||
vectorEmbeddingPolicy: this.collection.rawDataModel?.vectorEmbeddingPolicy,
|
||||
};
|
||||
|
||||
const tabs: SettingsV2TabInfo[] = [];
|
||||
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||
tabs.push({
|
||||
@@ -1156,6 +1168,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
content: <SubSettingsComponent {...subSettingsComponentProps} />,
|
||||
});
|
||||
|
||||
if (this.isVectorSearchEnabled) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.ContainerVectorPolicyTab,
|
||||
content: <ContainerVectorPolicyComponent {...containerVectorPolicyProps} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.shouldShowIndexingPolicyEditor) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
|
||||
@@ -121,7 +121,7 @@ export class ComputedPropertiesComponent extends React.Component<
|
||||
</Link>
|
||||
  about how to define computed properties and how to use them.
|
||||
</Text>
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
|
||||
<div className="settingsV2Editor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { VectorEmbeddingPolicy } from "Contracts/DataModels";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
||||
import React from "react";
|
||||
|
||||
export interface ContainerVectorPolicyComponentProps {
|
||||
vectorEmbeddingPolicy: VectorEmbeddingPolicy;
|
||||
}
|
||||
|
||||
export const ContainerVectorPolicyComponent: React.FC<ContainerVectorPolicyComponentProps> = ({
|
||||
vectorEmbeddingPolicy,
|
||||
}) => {
|
||||
return (
|
||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative" } }}>
|
||||
<EditorReact
|
||||
language={"json"}
|
||||
content={JSON.stringify(vectorEmbeddingPolicy || {}, null, 4)}
|
||||
isReadOnly={true}
|
||||
wordWrap={"on"}
|
||||
ariaLabel={"Container vector policy"}
|
||||
lineNumbers={"on"}
|
||||
scrollBeyondLastLine={false}
|
||||
className={"settingsV2Editor"}
|
||||
spinnerClassName={"settingsV2EditorSpinner"}
|
||||
fontSize={14}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ export interface IndexingPolicyComponentProps {
|
||||
logIndexingPolicySuccessMessage: () => void;
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
isVectorSearchEnabled?: boolean;
|
||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -119,10 +120,15 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||
/>
|
||||
{this.props.isVectorSearchEnabled && (
|
||||
<MessageBar messageBarType={MessageBarType.severeWarning}>
|
||||
Container vector policies and vector indexes are not modifiable after container creation
|
||||
</MessageBar>
|
||||
)}
|
||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
|
||||
)}
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
<div className="settingsV2Editor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
componentRef={[Function]}
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"paddingLeft": 10,
|
||||
"width": 210,
|
||||
},
|
||||
@@ -34,12 +34,12 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
ariaLabel="Index Type 1"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "Single",
|
||||
"text": "Single Field",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "Wildcard",
|
||||
"text": "Wildcard",
|
||||
},
|
||||
@@ -48,8 +48,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
placeholder="Select an index type"
|
||||
selectedKey="Single"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
{
|
||||
"dropdown": {
|
||||
"paddingleft": 10,
|
||||
"width": 202,
|
||||
},
|
||||
@@ -60,7 +60,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
ariaLabel="Undo Button 1"
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "Undo",
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<StyledMessageBar
|
||||
messageBarType={1}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"marginLeft": 10,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ exports[`MongoIndexingPolicyComponent error shown for collection with compound i
|
||||
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
</Text>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fieldName": "definition",
|
||||
"isResizable": true,
|
||||
"key": "definition",
|
||||
@@ -56,7 +56,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
"minWidth": 100,
|
||||
"name": "Definition",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fieldName": "type",
|
||||
"isResizable": true,
|
||||
"key": "type",
|
||||
@@ -64,7 +64,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
"minWidth": 100,
|
||||
"name": "Type",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fieldName": "actionButton",
|
||||
"isResizable": true,
|
||||
"key": "actionButton",
|
||||
@@ -75,15 +75,15 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={Array []}
|
||||
items={[]}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
".ms-FocusZone": Object {
|
||||
{
|
||||
"root": {
|
||||
"selectors": {
|
||||
".ms-FocusZone": {
|
||||
"paddingTop": 0,
|
||||
},
|
||||
},
|
||||
@@ -93,14 +93,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -117,11 +117,11 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
</Stack>
|
||||
<Separator
|
||||
styles={
|
||||
Object {
|
||||
"root": Array [
|
||||
Object {
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
{
|
||||
"root": [
|
||||
{
|
||||
"selectors": {
|
||||
"::before": {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
@@ -132,8 +132,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("ScaleComponent", () => {
|
||||
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
|
||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(`${targetThroughput}`);
|
||||
|
||||
const newCollection = { ...collection };
|
||||
const maxThroughput = 5000;
|
||||
@@ -66,7 +66,7 @@ describe("ScaleComponent", () => {
|
||||
wrapper = shallow(<ScaleComponent {...newProps} />);
|
||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
|
||||
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput);
|
||||
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(`${maxThroughput}`);
|
||||
});
|
||||
|
||||
it("autoScale disabled", () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,14 @@
|
||||
exports[`ComputedPropertiesComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": "10px",
|
||||
"marginLeft": "30px",
|
||||
}
|
||||
@@ -29,7 +29,7 @@ exports[`ComputedPropertiesComponent renders 1`] = `
|
||||
about how to define computed properties and how to use them.
|
||||
</Text>
|
||||
<div
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
className="settingsV2Editor"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
label="Mode"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "LastWriterWins",
|
||||
"text": "Last Write Wins (default)",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "Custom",
|
||||
"text": "Merge Procedure (custom)",
|
||||
},
|
||||
@@ -25,19 +25,19 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
}
|
||||
selectedKey="LastWriterWins"
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Array [
|
||||
Object {
|
||||
{
|
||||
"flexContainer": [
|
||||
{
|
||||
"columnGap": "default",
|
||||
"display": "default",
|
||||
"selectors": Object {
|
||||
".ms-ChoiceField-field.is-checked::after": Object {
|
||||
"selectors": {
|
||||
".ms-ChoiceField-field.is-checked::after": {
|
||||
"borderColor": undefined,
|
||||
},
|
||||
".ms-ChoiceField-field.is-checked::before": Object {
|
||||
".ms-ChoiceField-field.is-checked::before": {
|
||||
"borderColor": undefined,
|
||||
},
|
||||
".ms-ChoiceField-wrapper label": Object {
|
||||
".ms-ChoiceField-wrapper label": {
|
||||
"fontFamily": undefined,
|
||||
"fontSize": 14,
|
||||
"padding": "2px 5px",
|
||||
@@ -55,12 +55,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
onChange={[Function]}
|
||||
onRenderLabel={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"borderColor": "",
|
||||
"height": 25,
|
||||
"selectors": Object {
|
||||
":disabled": Object {
|
||||
"selectors": {
|
||||
":disabled": {
|
||||
"backgroundColor": undefined,
|
||||
"borderColor": undefined,
|
||||
},
|
||||
@@ -77,7 +77,7 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -86,12 +86,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
label="Mode"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "LastWriterWins",
|
||||
"text": "Last Write Wins (default)",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "Custom",
|
||||
"text": "Merge Procedure (custom)",
|
||||
},
|
||||
@@ -99,19 +99,19 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
}
|
||||
selectedKey="Custom"
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Array [
|
||||
Object {
|
||||
{
|
||||
"flexContainer": [
|
||||
{
|
||||
"columnGap": "default",
|
||||
"display": "default",
|
||||
"selectors": Object {
|
||||
".ms-ChoiceField-field.is-checked::after": Object {
|
||||
"selectors": {
|
||||
".ms-ChoiceField-field.is-checked::after": {
|
||||
"borderColor": "",
|
||||
},
|
||||
".ms-ChoiceField-field.is-checked::before": Object {
|
||||
".ms-ChoiceField-field.is-checked::before": {
|
||||
"borderColor": "",
|
||||
},
|
||||
".ms-ChoiceField-wrapper label": Object {
|
||||
".ms-ChoiceField-wrapper label": {
|
||||
"fontFamily": undefined,
|
||||
"fontSize": 14,
|
||||
"padding": "2px 5px",
|
||||
@@ -129,12 +129,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
onChange={[Function]}
|
||||
onRenderLabel={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
{
|
||||
"fieldGroup": {
|
||||
"borderColor": "",
|
||||
"height": 25,
|
||||
"selectors": Object {
|
||||
":disabled": Object {
|
||||
"selectors": {
|
||||
":disabled": {
|
||||
"backgroundColor": undefined,
|
||||
"borderColor": undefined,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`IndexingPolicyComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
||||
refreshIndexTransformationProgress={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
className="settingsV2Editor"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`ScaleComponent renders with correct initial notification 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
||||
<Text
|
||||
id="throughputApplyLongDelayMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -30,7 +30,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
||||
</StyledMessageBar>
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 6,
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
</Text>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
Object {
|
||||
{
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
}
|
||||
directionalHint={12}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"display": "inline-block",
|
||||
"float": "right",
|
||||
},
|
||||
@@ -45,8 +45,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
ariaLabel="Info"
|
||||
iconName="Info"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": -3,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export enum SettingsV2TabTypes {
|
||||
IndexingPolicyTab,
|
||||
PartitionKeyTab,
|
||||
ComputedPropertiesTab,
|
||||
ContainerVectorPolicyTab,
|
||||
}
|
||||
|
||||
export interface IsComponentDirtyResult {
|
||||
@@ -151,7 +152,9 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
case SettingsV2TabTypes.PartitionKeyTab:
|
||||
return "Partition Keys (preview)";
|
||||
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||
return "Computed Properties (preview)";
|
||||
return "Computed Properties";
|
||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||
return "Container Vector Policy (preview)";
|
||||
default:
|
||||
throw new Error(`Unknown tab ${tab}`);
|
||||
}
|
||||
|
||||
@@ -16,28 +16,27 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="ScaleTab"
|
||||
key="ScaleTab"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScaleComponent
|
||||
collection={
|
||||
Object {
|
||||
{
|
||||
"analyticalStorageTtl": [Function],
|
||||
"changeFeedPolicy": [Function],
|
||||
"computedProperties": [Function],
|
||||
"conflictResolutionPolicy": [Function],
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"retryOptions": {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -61,16 +60,16 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": Object {
|
||||
"partitionKey": {
|
||||
"kind": "hash",
|
||||
"paths": Array [],
|
||||
"paths": [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKeyProperties": [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"uniqueKeyPolicy": {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
@@ -91,7 +90,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="SubSettingsTab"
|
||||
key="SubSettingsTab"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
@@ -101,21 +100,20 @@ exports[`SettingsComponent renders 1`] = `
|
||||
changeFeedPolicyBaseline="Off"
|
||||
changeFeedPolicyVisible={false}
|
||||
collection={
|
||||
Object {
|
||||
{
|
||||
"analyticalStorageTtl": [Function],
|
||||
"changeFeedPolicy": [Function],
|
||||
"computedProperties": [Function],
|
||||
"conflictResolutionPolicy": [Function],
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"retryOptions": {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -139,16 +137,16 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": Object {
|
||||
"partitionKey": {
|
||||
"kind": "hash",
|
||||
"paths": Array [],
|
||||
"paths": [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKeyProperties": [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"uniqueKeyPolicy": {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
@@ -176,28 +174,29 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="IndexingPolicyTab"
|
||||
key="IndexingPolicyTab"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndexingPolicyComponent
|
||||
indexingPolicyContent={
|
||||
Object {
|
||||
{
|
||||
"automatic": true,
|
||||
"excludedPaths": Array [],
|
||||
"includedPaths": Array [],
|
||||
"excludedPaths": [],
|
||||
"includedPaths": [],
|
||||
"indexingMode": "consistent",
|
||||
}
|
||||
}
|
||||
indexingPolicyContentBaseline={
|
||||
Object {
|
||||
{
|
||||
"automatic": true,
|
||||
"excludedPaths": Array [],
|
||||
"includedPaths": Array [],
|
||||
"excludedPaths": [],
|
||||
"includedPaths": [],
|
||||
"indexingMode": "consistent",
|
||||
}
|
||||
}
|
||||
isVectorSearchEnabled={false}
|
||||
logIndexingPolicySuccessMessage={[Function]}
|
||||
onIndexingPolicyContentChange={[Function]}
|
||||
onIndexingPolicyDirtyChange={[Function]}
|
||||
@@ -211,28 +210,27 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="PartitionKeyTab"
|
||||
key="PartitionKeyTab"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<PartitionKeyComponent
|
||||
collection={
|
||||
Object {
|
||||
{
|
||||
"analyticalStorageTtl": [Function],
|
||||
"changeFeedPolicy": [Function],
|
||||
"computedProperties": [Function],
|
||||
"conflictResolutionPolicy": [Function],
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"retryOptions": {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -256,30 +254,29 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": Object {
|
||||
"partitionKey": {
|
||||
"kind": "hash",
|
||||
"paths": Array [],
|
||||
"paths": [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKeyProperties": [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"uniqueKeyPolicy": {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
explorer={
|
||||
Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"retryOptions": {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -300,27 +297,27 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerText="Computed Properties (preview)"
|
||||
headerText="Computed Properties"
|
||||
itemKey="ComputedPropertiesTab"
|
||||
key="ComputedPropertiesTab"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ComputedPropertiesComponent
|
||||
computedPropertiesContent={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"name": "queryName",
|
||||
"query": "query",
|
||||
},
|
||||
]
|
||||
}
|
||||
computedPropertiesContentBaseline={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"name": "queryName",
|
||||
"query": "query",
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
"marginTop": 15,
|
||||
}
|
||||
@@ -25,7 +25,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Stack
|
||||
id="throughputSpendElement"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 5,
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="manualToAutoscaleDisclaimerElement"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -81,8 +81,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -102,8 +102,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="updateThroughputDelayedApplyWarningMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -114,8 +114,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -134,8 +134,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="throughputApplyShortDelayMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -150,8 +150,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="throughputApplyLongDelayMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -165,8 +165,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -179,8 +179,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -191,8 +191,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -211,8 +211,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -247,15 +247,15 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -270,8 +270,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Stack>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -287,8 +287,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
TextField,
|
||||
Toggle,
|
||||
} from "@fluentui/react";
|
||||
import { TFunction } from "i18next";
|
||||
import * as React from "react";
|
||||
import {
|
||||
ChoiceItem,
|
||||
@@ -100,7 +99,7 @@ export interface SmartUiComponentProps {
|
||||
onInputChange: (input: AnyDisplay, newValue: InputType) => void;
|
||||
onError: (hasError: boolean) => void;
|
||||
disabled: boolean;
|
||||
getTranslation: TFunction;
|
||||
getTranslation: (messageKey: string, namespace?: string) => string;
|
||||
}
|
||||
|
||||
interface SmartUiComponentState {
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
aria-labelledby="description-label"
|
||||
id="description-text-display"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"whiteSpace": "pre-line",
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -70,14 +70,14 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
</StyledLabelBase>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 2,
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -133,11 +133,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
onChange={[Function]}
|
||||
step={10}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
"valueLabel": Object {
|
||||
"valueLabel": {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
@@ -157,7 +157,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -199,8 +199,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
id="containerId-textField-input"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -219,7 +219,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -243,8 +243,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
onChange={[Function]}
|
||||
onText="Enabled"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -261,7 +261,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -282,16 +282,16 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
id="database-dropdown-input"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "db1",
|
||||
"text": "Database 1",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "db2",
|
||||
"text": "Database 2",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "db3",
|
||||
"text": "Database 3",
|
||||
},
|
||||
@@ -299,13 +299,13 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
}
|
||||
selectedKey="db2"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
{
|
||||
"dropdown": {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
},
|
||||
"root": Object {
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -323,7 +323,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
aria-labelledby="description-label"
|
||||
id="description-text-display"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"whiteSpace": "pre-line",
|
||||
}
|
||||
}
|
||||
@@ -372,7 +372,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -389,14 +389,14 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
</StyledLabelBase>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 2,
|
||||
}
|
||||
}
|
||||
@@ -425,7 +425,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -450,11 +450,11 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
onChange={[Function]}
|
||||
step={10}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
"valueLabel": Object {
|
||||
"valueLabel": {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
@@ -474,7 +474,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -495,7 +495,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -515,8 +515,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
id="containerId-textField-input"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -535,7 +535,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -558,8 +558,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
onChange={[Function]}
|
||||
onText="Enabled"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
{
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -576,7 +576,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
Object {
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -596,16 +596,16 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
id="database-dropdown-input"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"key": "db1",
|
||||
"text": "Database 1",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "db2",
|
||||
"text": "Database 2",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"key": "db3",
|
||||
"text": "Database 3",
|
||||
},
|
||||
@@ -613,13 +613,13 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
}
|
||||
selectedKey="db2"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
{
|
||||
"dropdown": {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
},
|
||||
"root": Object {
|
||||
"root": {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.tabComponentContainer {
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
.tabSwitch {
|
||||
margin-left: @LargeSpace;
|
||||
margin-bottom: 20px;
|
||||
.tabSwitch {
|
||||
margin-left: @LargeSpace;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.tab {
|
||||
margin-right: @MediumSpace;
|
||||
}
|
||||
|
||||
.toggleSwitch {
|
||||
.toggleSwitch();
|
||||
}
|
||||
|
||||
.selectedToggle {
|
||||
.selectedToggle();
|
||||
}
|
||||
|
||||
.unselectedToggle {
|
||||
.unselectedToggle();
|
||||
}
|
||||
}
|
||||
.tab {
|
||||
margin-right: @MediumSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { Pivot, PivotItem } from "@fluentui/react";
|
||||
import "./TabComponent.less";
|
||||
|
||||
export interface TabContent {
|
||||
@@ -35,58 +35,36 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
}
|
||||
|
||||
private setActiveTab(index: number): void {
|
||||
this.setState({ activeTabIndex: index });
|
||||
this.props.onTabIndexChange(index);
|
||||
}
|
||||
|
||||
private renderTabTitles(): JSX.Element[] {
|
||||
return this.props.tabs.map((tab: Tab, index: number) => {
|
||||
if (!tab.isVisible()) {
|
||||
return <React.Fragment key={index} />;
|
||||
}
|
||||
|
||||
let className = "toggleSwitch";
|
||||
let ariaselected;
|
||||
if (index === this.props.currentTabIndex) {
|
||||
className += " selectedToggle";
|
||||
ariaselected = true;
|
||||
} else {
|
||||
className += " unselectedToggle";
|
||||
ariaselected = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tab" key={index}>
|
||||
<AccessibleElement
|
||||
as="span"
|
||||
className={className}
|
||||
role="tab"
|
||||
onActivated={() => this.setActiveTab(index)}
|
||||
aria-label={`Select tab: ${tab.title}`}
|
||||
aria-selected={ariaselected}
|
||||
>
|
||||
{tab.title}
|
||||
</AccessibleElement>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const currentTabContent = this.props.tabs[this.props.currentTabIndex].content;
|
||||
const { tabs, currentTabIndex, hideHeader } = this.props;
|
||||
const currentTabContent = tabs[currentTabIndex].content;
|
||||
let className = "tabComponentContent";
|
||||
if (currentTabContent.className) {
|
||||
className += ` ${currentTabContent.className}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tabComponentContainer">
|
||||
{!this.props.hideHeader && (
|
||||
<div className="tabs tabSwitch" role="tablist">
|
||||
{this.renderTabTitles()}
|
||||
</div>
|
||||
)}
|
||||
<div className={className}>{currentTabContent.render()}</div>
|
||||
<div className="tabs tabSwitch">
|
||||
{!hideHeader && (
|
||||
<Pivot
|
||||
aria-label="Tab navigation"
|
||||
selectedKey={currentTabIndex.toString()}
|
||||
linkSize="normal"
|
||||
onLinkClick={(item) => this.setActiveTab(parseInt(item?.props.itemKey || ""))}
|
||||
>
|
||||
{tabs.map((tab: Tab, index: number) => {
|
||||
if (!tab.isVisible()) {
|
||||
return null; // Skip rendering invisible tabs
|
||||
}
|
||||
return <PivotItem key={index} headerText={tab.title} itemKey={index.toString()} />;
|
||||
})}
|
||||
</Pivot>
|
||||
)}
|
||||
</div>
|
||||
<div className={className}>{tabs[currentTabIndex].content.render()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.outlineNone{
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.copyQuery:focus::after,
|
||||
.deleteQuery:focus::after {
|
||||
outline: none !important;
|
||||
|
||||
@@ -223,6 +223,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
<Text variant="small" aria-label="capacity calculator of azure cosmos db">
|
||||
Estimate your required RU/s with{" "}
|
||||
<Link
|
||||
className="underlinedLink outlineNone"
|
||||
target="_blank"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
@@ -271,7 +272,12 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
<Stack className="throughputInputSpacing">
|
||||
<Text variant="small" aria-label="ruDescription">
|
||||
Estimate your required RU/s with
|
||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="Capacity calculator">
|
||||
<Link
|
||||
className="underlinedLink"
|
||||
target="_blank"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
aria-label="Capacity calculator"
|
||||
>
|
||||
capacity calculator
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -30,7 +30,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-label="Throughput header"
|
||||
key=".0:$.$.1"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
"lineHeight": "20px",
|
||||
}
|
||||
@@ -41,7 +41,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-label="Throughput header"
|
||||
className="css-110"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
"lineHeight": "20px",
|
||||
}
|
||||
@@ -62,9 +62,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
delay={1}
|
||||
styles={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -73,92 +73,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -167,7 +167,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -220,7 +220,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -325,7 +325,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
@@ -357,9 +357,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
styles={[Function]}
|
||||
tabIndex={0}
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -368,92 +368,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -462,7 +462,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -515,7 +515,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -620,7 +620,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
@@ -645,7 +645,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
hidden={true}
|
||||
id="tooltip0"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"border": 0,
|
||||
"height": 1,
|
||||
"margin": -1,
|
||||
@@ -733,18 +733,20 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
|
||||
<StyledLinkBase
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
className="underlinedLink outlineNone"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
target="_blank"
|
||||
>
|
||||
<LinkBase
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
className="underlinedLink outlineNone"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
styles={[Function]}
|
||||
target="_blank"
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -753,92 +755,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -847,7 +849,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -900,7 +902,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -1005,7 +1007,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
@@ -1017,7 +1019,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
>
|
||||
<a
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
className="ms-Link root-117"
|
||||
className="ms-Link underlinedLink outlineNone root-117"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
onClick={[Function]}
|
||||
target="_blank"
|
||||
@@ -1040,7 +1042,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-label="maxRUDescription"
|
||||
key=".0:$.$.0"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
"lineHeight": "20px",
|
||||
}
|
||||
@@ -1051,7 +1053,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-label="maxRUDescription"
|
||||
className="css-110"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontWeight": 600,
|
||||
"lineHeight": "20px",
|
||||
}
|
||||
@@ -1073,9 +1075,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
delay={1}
|
||||
styles={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -1084,92 +1086,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -1178,7 +1180,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -1231,7 +1233,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -1336,7 +1338,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
@@ -1368,9 +1370,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
styles={[Function]}
|
||||
tabIndex={0}
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -1379,92 +1381,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -1473,7 +1475,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -1526,7 +1528,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -1631,7 +1633,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
@@ -1656,7 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
hidden={true}
|
||||
id="tooltip1"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"border": 0,
|
||||
"height": 1,
|
||||
"margin": -1,
|
||||
@@ -1688,11 +1690,11 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
required={true}
|
||||
step={1000}
|
||||
styles={
|
||||
Object {
|
||||
"field": Object {
|
||||
{
|
||||
"field": {
|
||||
"fontSize": 12,
|
||||
},
|
||||
"fieldGroup": Object {
|
||||
"fieldGroup": {
|
||||
"height": 27,
|
||||
"width": 300,
|
||||
},
|
||||
@@ -1714,9 +1716,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
step={1000}
|
||||
styles={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
{
|
||||
"disableGlobalClassNames": false,
|
||||
"effects": Object {
|
||||
"effects": {
|
||||
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
|
||||
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
|
||||
@@ -1725,92 +1727,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"roundedCorner4": "4px",
|
||||
"roundedCorner6": "6px",
|
||||
},
|
||||
"fonts": Object {
|
||||
"large": Object {
|
||||
"fonts": {
|
||||
"large": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"medium": Object {
|
||||
"medium": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mediumPlus": Object {
|
||||
"mediumPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"mega": Object {
|
||||
"mega": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "68px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"small": Object {
|
||||
"small": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"smallPlus": Object {
|
||||
"smallPlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"superLarge": Object {
|
||||
"superLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "42px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"tiny": Object {
|
||||
"tiny": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xLarge": Object {
|
||||
"xLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xLargePlus": Object {
|
||||
"xLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "24px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xSmall": Object {
|
||||
"xSmall": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
},
|
||||
"xxLarge": Object {
|
||||
"xxLarge": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
"fontSize": "28px",
|
||||
"fontWeight": 600,
|
||||
},
|
||||
"xxLargePlus": Object {
|
||||
"xxLargePlus": {
|
||||
"MozOsxFontSmoothing": "grayscale",
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
|
||||
@@ -1819,7 +1821,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
},
|
||||
},
|
||||
"isInverted": false,
|
||||
"palette": Object {
|
||||
"palette": {
|
||||
"accent": "#0078d4",
|
||||
"black": "#000000",
|
||||
"blackTranslucent40": "rgba(0,0,0,.4)",
|
||||
@@ -1872,7 +1874,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"yellowLight": "#fff100",
|
||||
},
|
||||
"rtl": undefined,
|
||||
"semanticColors": Object {
|
||||
"semanticColors": {
|
||||
"accentButtonBackground": "#0078d4",
|
||||
"accentButtonText": "#ffffff",
|
||||
"actionLink": "#323130",
|
||||
@@ -1977,7 +1979,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
"warningIcon": "#797775",
|
||||
"warningText": "#323130",
|
||||
},
|
||||
"spacing": Object {
|
||||
"spacing": {
|
||||
"l1": "20px",
|
||||
"l2": "32px",
|
||||
"m": "16px",
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { TreeComponent, TreeNode, TreeNodeComponent } from "./TreeComponent";
|
||||
import React from "react";
|
||||
import { LegacyTreeComponent, LegacyTreeNode, LegacyTreeNodeComponent } from "./LegacyTreeComponent";
|
||||
|
||||
const buildChildren = (): TreeNode[] => {
|
||||
const grandChild11: TreeNode = {
|
||||
const buildChildren = (): LegacyTreeNode[] => {
|
||||
const grandChild11: LegacyTreeNode = {
|
||||
label: "ZgrandChild11",
|
||||
};
|
||||
const grandChild12: TreeNode = {
|
||||
const grandChild12: LegacyTreeNode = {
|
||||
label: "AgrandChild12",
|
||||
};
|
||||
const child1: TreeNode = {
|
||||
const child1: LegacyTreeNode = {
|
||||
label: "Bchild1",
|
||||
children: [grandChild11, grandChild12],
|
||||
};
|
||||
|
||||
const child2: TreeNode = {
|
||||
const child2: LegacyTreeNode = {
|
||||
label: "2child2",
|
||||
};
|
||||
|
||||
return [child1, child2];
|
||||
};
|
||||
|
||||
const buildChildren2 = (): TreeNode[] => {
|
||||
const grandChild11: TreeNode = {
|
||||
const buildChildren2 = (): LegacyTreeNode[] => {
|
||||
const grandChild11: LegacyTreeNode = {
|
||||
label: "ZgrandChild11",
|
||||
};
|
||||
const grandChild12: TreeNode = {
|
||||
const grandChild12: LegacyTreeNode = {
|
||||
label: "AgrandChild12",
|
||||
};
|
||||
|
||||
const child1: TreeNode = {
|
||||
const child1: LegacyTreeNode = {
|
||||
label: "aChild",
|
||||
};
|
||||
|
||||
const child2: TreeNode = {
|
||||
const child2: LegacyTreeNode = {
|
||||
label: "bchild",
|
||||
children: [grandChild11, grandChild12],
|
||||
};
|
||||
|
||||
const child3: TreeNode = {
|
||||
const child3: LegacyTreeNode = {
|
||||
label: "cchild",
|
||||
};
|
||||
|
||||
const child4: TreeNode = {
|
||||
const child4: LegacyTreeNode = {
|
||||
label: "dchild",
|
||||
children: [grandChild11, grandChild12],
|
||||
};
|
||||
@@ -50,7 +50,7 @@ const buildChildren2 = (): TreeNode[] => {
|
||||
return [child1, child2, child3, child4];
|
||||
};
|
||||
|
||||
describe("TreeComponent", () => {
|
||||
describe("LegacyTreeComponent", () => {
|
||||
it("renders a simple tree", () => {
|
||||
const root = {
|
||||
label: "root",
|
||||
@@ -62,14 +62,14 @@ describe("TreeComponent", () => {
|
||||
className: "tree",
|
||||
};
|
||||
|
||||
const wrapper = shallow(<TreeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("TreeNodeComponent", () => {
|
||||
describe("LegacyTreeNodeComponent", () => {
|
||||
it("renders a simple node (sorted children, expanded)", () => {
|
||||
const node: TreeNode = {
|
||||
const node: LegacyTreeNode = {
|
||||
label: "label",
|
||||
id: "id",
|
||||
children: buildChildren(),
|
||||
@@ -98,12 +98,12 @@ describe("TreeNodeComponent", () => {
|
||||
generation: 12,
|
||||
paddingLeft: 23,
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders unsorted children by default", () => {
|
||||
const node: TreeNode = {
|
||||
const node: LegacyTreeNode = {
|
||||
label: "label",
|
||||
children: buildChildren(),
|
||||
isExpanded: true,
|
||||
@@ -113,12 +113,12 @@ describe("TreeNodeComponent", () => {
|
||||
generation: 2,
|
||||
paddingLeft: 9,
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render children by default", () => {
|
||||
const node: TreeNode = {
|
||||
const node: LegacyTreeNode = {
|
||||
label: "label",
|
||||
children: buildChildren(),
|
||||
isAlphaSorted: false,
|
||||
@@ -128,12 +128,12 @@ describe("TreeNodeComponent", () => {
|
||||
generation: 2,
|
||||
paddingLeft: 9,
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders sorted children, expanded, leaves and parents separated", () => {
|
||||
const node: TreeNode = {
|
||||
const node: LegacyTreeNode = {
|
||||
label: "label",
|
||||
id: "id",
|
||||
children: buildChildren2(),
|
||||
@@ -156,12 +156,12 @@ describe("TreeNodeComponent", () => {
|
||||
generation: 12,
|
||||
paddingLeft: 23,
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders loading icon", () => {
|
||||
const node: TreeNode = {
|
||||
const node: LegacyTreeNode = {
|
||||
label: "label",
|
||||
children: [],
|
||||
isExpanded: true,
|
||||
@@ -172,7 +172,7 @@ describe("TreeNodeComponent", () => {
|
||||
generation: 2,
|
||||
paddingLeft: 9,
|
||||
};
|
||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
||||
const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
IContextualMenuItemProps,
|
||||
IContextualMenuProps,
|
||||
} from "@fluentui/react";
|
||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||
import * as React from "react";
|
||||
import AnimateHeight from "react-animate-height";
|
||||
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||
@@ -22,18 +23,10 @@ import { StyleConstants } from "../../../Common/StyleConstants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
export interface TreeNodeMenuItem {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
iconSrc?: string;
|
||||
isDisabled?: boolean;
|
||||
styleClass?: string;
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
export interface LegacyTreeNode {
|
||||
label: string;
|
||||
id?: string;
|
||||
children?: TreeNode[];
|
||||
children?: LegacyTreeNode[];
|
||||
contextMenu?: TreeNodeMenuItem[];
|
||||
iconSrc?: string;
|
||||
isExpanded?: boolean;
|
||||
@@ -50,34 +43,37 @@ export interface TreeNode {
|
||||
onContextMenuOpen?: () => void;
|
||||
}
|
||||
|
||||
export interface TreeComponentProps {
|
||||
rootNode: TreeNode;
|
||||
export interface LegacyTreeComponentProps {
|
||||
rootNode: LegacyTreeNode;
|
||||
style?: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export class TreeComponent extends React.Component<TreeComponentProps> {
|
||||
export class LegacyTreeComponent extends React.Component<LegacyTreeComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
|
||||
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
||||
<LegacyTreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tree node is a react component */
|
||||
interface TreeNodeComponentProps {
|
||||
node: TreeNode;
|
||||
interface LegacyTreeNodeComponentProps {
|
||||
node: LegacyTreeNode;
|
||||
generation: number;
|
||||
paddingLeft: number;
|
||||
}
|
||||
|
||||
interface TreeNodeComponentState {
|
||||
interface LegacyTreeNodeComponentState {
|
||||
isExpanded: boolean;
|
||||
isMenuShowing: boolean;
|
||||
}
|
||||
export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, TreeNodeComponentState> {
|
||||
export class LegacyTreeNodeComponent extends React.Component<
|
||||
LegacyTreeNodeComponentProps,
|
||||
LegacyTreeNodeComponentState
|
||||
> {
|
||||
private static readonly paddingPerGenerationPx = 16;
|
||||
private static readonly iconOffset = 22;
|
||||
private static readonly transitionDurationMS = 200;
|
||||
@@ -85,7 +81,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
private contextMenuRef = React.createRef<HTMLDivElement>();
|
||||
private isExpanded: boolean;
|
||||
|
||||
constructor(props: TreeNodeComponentProps) {
|
||||
constructor(props: LegacyTreeNodeComponentProps) {
|
||||
super(props);
|
||||
this.isExpanded = props.node.isExpanded;
|
||||
this.state = {
|
||||
@@ -94,13 +90,13 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: TreeNodeComponentProps, prevState: TreeNodeComponentState) {
|
||||
componentDidUpdate(prevProps: LegacyTreeNodeComponentProps, prevState: LegacyTreeNodeComponentState) {
|
||||
// Only call when expand has actually changed
|
||||
if (this.state.isExpanded !== prevState.isExpanded) {
|
||||
if (this.state.isExpanded) {
|
||||
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent.callbackDelayMS);
|
||||
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, LegacyTreeNodeComponent.callbackDelayMS);
|
||||
} else {
|
||||
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS);
|
||||
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, LegacyTreeNodeComponent.callbackDelayMS);
|
||||
}
|
||||
}
|
||||
if (this.props.node.isExpanded !== this.isExpanded) {
|
||||
@@ -115,18 +111,18 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
return this.renderNode(this.props.node, this.props.generation);
|
||||
}
|
||||
|
||||
private static getSortedChildren(treeNode: TreeNode): TreeNode[] {
|
||||
private static getSortedChildren(treeNode: LegacyTreeNode): LegacyTreeNode[] {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label);
|
||||
const compareFct = (a: LegacyTreeNode, b: LegacyTreeNode) => a.label.localeCompare(b.label);
|
||||
|
||||
let unsortedChildren;
|
||||
if (treeNode.isLeavesParentsSeparate) {
|
||||
// Separate parents and leave
|
||||
const parents: TreeNode[] = treeNode.children.filter((node) => node.children);
|
||||
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children);
|
||||
const parents: LegacyTreeNode[] = treeNode.children.filter((node) => node.children);
|
||||
const leaves: LegacyTreeNode[] = treeNode.children.filter((node) => !node.children);
|
||||
|
||||
if (treeNode.isAlphaSorted) {
|
||||
parents.sort(compareFct);
|
||||
@@ -141,18 +137,18 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
return unsortedChildren;
|
||||
}
|
||||
|
||||
private static isNodeHeaderBlank(node: TreeNode): boolean {
|
||||
private static isNodeHeaderBlank(node: LegacyTreeNode): boolean {
|
||||
return (node.label === undefined || node.label === null) && !node.contextMenu;
|
||||
}
|
||||
|
||||
private renderNode(node: TreeNode, generation: number): JSX.Element {
|
||||
let paddingLeft = generation * TreeNodeComponent.paddingPerGenerationPx;
|
||||
private renderNode(node: LegacyTreeNode, generation: number): JSX.Element {
|
||||
const paddingLeft = generation * LegacyTreeNodeComponent.paddingPerGenerationPx;
|
||||
let additionalOffsetPx = 15;
|
||||
|
||||
if (node.children) {
|
||||
const childrenWithSubChildren = node.children.filter((child: TreeNode) => !!child.children);
|
||||
const childrenWithSubChildren = node.children.filter((child: LegacyTreeNode) => !!child.children);
|
||||
if (childrenWithSubChildren.length > 0) {
|
||||
additionalOffsetPx = TreeNodeComponent.iconOffset;
|
||||
additionalOffsetPx = LegacyTreeNodeComponent.iconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,16 +156,17 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
const showSelected =
|
||||
this.props.node.isSelected &&
|
||||
this.props.node.isSelected() &&
|
||||
!TreeNodeComponent.isAnyDescendantSelected(this.props.node);
|
||||
!LegacyTreeNodeComponent.isAnyDescendantSelected(this.props.node);
|
||||
|
||||
const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft };
|
||||
if (TreeNodeComponent.isNodeHeaderBlank(node)) {
|
||||
if (LegacyTreeNodeComponent.isNodeHeaderBlank(node)) {
|
||||
headerStyle.height = 0;
|
||||
headerStyle.padding = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-test={`Tree/TreeNode:${node.label}`}
|
||||
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
|
||||
@@ -178,9 +175,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
>
|
||||
<div
|
||||
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
|
||||
data-test={`Tree/TreeNode/Header:${node.label}`}
|
||||
style={headerStyle}
|
||||
tabIndex={node.children ? -1 : 0}
|
||||
data-test={node.label}
|
||||
>
|
||||
{this.renderCollapseExpandIcon(node)}
|
||||
{node.iconSrc && <img className="nodeIcon" src={node.iconSrc} alt="" />}
|
||||
@@ -195,10 +192,13 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
<img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} />
|
||||
</div>
|
||||
{node.children && (
|
||||
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
||||
<AnimateHeight
|
||||
duration={LegacyTreeNodeComponent.transitionDurationMS}
|
||||
height={this.state.isExpanded ? "auto" : 0}
|
||||
>
|
||||
<div className="nodeChildren" data-test={node.label} role="group">
|
||||
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
|
||||
<TreeNodeComponent
|
||||
{LegacyTreeNodeComponent.getSortedChildren(node).map((childNode: LegacyTreeNode) => (
|
||||
<LegacyTreeNodeComponent
|
||||
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
||||
node={childNode}
|
||||
generation={generation + 1}
|
||||
@@ -216,12 +216,14 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
* Recursive: is the node or any descendant selected
|
||||
* @param node
|
||||
*/
|
||||
private static isAnyDescendantSelected(node: TreeNode): boolean {
|
||||
private static isAnyDescendantSelected(node: LegacyTreeNode): boolean {
|
||||
return (
|
||||
node.children &&
|
||||
node.children.reduce(
|
||||
(previous: boolean, child: TreeNode) =>
|
||||
previous || (child.isSelected && child.isSelected()) || TreeNodeComponent.isAnyDescendantSelected(child),
|
||||
(previous: boolean, child: LegacyTreeNode) =>
|
||||
previous ||
|
||||
(child.isSelected && child.isSelected()) ||
|
||||
LegacyTreeNodeComponent.isAnyDescendantSelected(child),
|
||||
false,
|
||||
)
|
||||
);
|
||||
@@ -232,10 +234,10 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
}
|
||||
|
||||
private onRightClick = (): void => {
|
||||
this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent.createClickEvent());
|
||||
this.contextMenuRef.current.firstChild.dispatchEvent(LegacyTreeNodeComponent.createClickEvent());
|
||||
};
|
||||
|
||||
private renderContextMenuButton(node: TreeNode): JSX.Element {
|
||||
private renderContextMenuButton(node: LegacyTreeNode): JSX.Element {
|
||||
const menuItemLabel = "More";
|
||||
const buttonStyles: Partial<IButtonStyles> = {
|
||||
rootFocused: { outline: `1px dashed ${StyleConstants.FocusColor}` },
|
||||
@@ -263,9 +265,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
onMenuDismissed: (contextualMenu?: IContextualMenuProps) => this.setState({ isMenuShowing: false }),
|
||||
contextualMenuItemAs: (props: IContextualMenuItemProps) => (
|
||||
<div
|
||||
data-test={`treeComponentMenuItemContainer`}
|
||||
data-test={`Tree/TreeNode/MenuItem:${props.item.text}`}
|
||||
className="treeComponentMenuItemContainer"
|
||||
onContextMenu={(e) => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
|
||||
onContextMenu={(e) => e.target.dispatchEvent(LegacyTreeNodeComponent.createClickEvent())}
|
||||
>
|
||||
{props.item.onRenderIcon()}
|
||||
<span
|
||||
@@ -297,7 +299,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
);
|
||||
}
|
||||
|
||||
private renderCollapseExpandIcon(node: TreeNode): JSX.Element {
|
||||
private renderCollapseExpandIcon(node: LegacyTreeNode): JSX.Element {
|
||||
if (!node.children || !node.label) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -314,12 +316,12 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
);
|
||||
}
|
||||
|
||||
private onNodeClick = (event: React.MouseEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
private onNodeClick = (event: React.MouseEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
|
||||
event.stopPropagation();
|
||||
if (node.children) {
|
||||
const isExpanded = !this.state.isExpanded;
|
||||
// Prevent collapsing if node header is blank
|
||||
if (!(TreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) {
|
||||
if (!(LegacyTreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) {
|
||||
this.setState({ isExpanded });
|
||||
}
|
||||
}
|
||||
@@ -327,14 +329,14 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
||||
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
|
||||
};
|
||||
|
||||
private onNodeKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
private onNodeKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
|
||||
}
|
||||
};
|
||||
|
||||
private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => {
|
||||
private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
|
||||
event.stopPropagation();
|
||||
if (node.children) {
|
||||
66
src/Explorer/Controls/TreeComponent/Styles.ts
Normal file
66
src/Explorer/Controls/TreeComponent/Styles.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
188
src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx
Normal file
188
src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { TreeItem, TreeItemLayout } from "@fluentui/react-components";
|
||||
import PromiseSource from "Utils/PromiseSource";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { TreeNode, TreeNodeComponent } from "./TreeNodeComponent";
|
||||
|
||||
function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): TreeNode {
|
||||
const node: TreeNode = {
|
||||
id,
|
||||
label: `${id}Label`,
|
||||
className: "nodeIcon",
|
||||
iconSrc: `${id}Icon`,
|
||||
onClick: jest.fn().mockName(`${id}Click`),
|
||||
...additionalProps,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
describe("TreeNodeComponent", () => {
|
||||
it("renders a single node", () => {
|
||||
const node = generateTestNode("root");
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
|
||||
// The "click" handler is actually attached to onOpenChange, with a type of "Click".
|
||||
component
|
||||
.find(TreeItem)
|
||||
.props()
|
||||
.onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "Click" });
|
||||
expect(node.onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders a node with a menu", () => {
|
||||
const node = generateTestNode("root", {
|
||||
contextMenu: [
|
||||
{
|
||||
label: "enabledItem",
|
||||
onClick: jest.fn().mockName("enabledItemClick"),
|
||||
},
|
||||
{
|
||||
label: "disabledItem",
|
||||
onClick: jest.fn().mockName("disabledItemClick"),
|
||||
isDisabled: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a loading spinner if the node is loading", async () => {
|
||||
const loading = new PromiseSource();
|
||||
const node = generateTestNode("root", {
|
||||
onExpanded: () => loading.promise,
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
|
||||
act(() => {
|
||||
component
|
||||
.find(TreeItem)
|
||||
.props()
|
||||
.onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "ExpandIconClick" });
|
||||
});
|
||||
|
||||
expect(component).toMatchSnapshot("loading");
|
||||
await loading.resolveAndWait();
|
||||
expect(component).toMatchSnapshot("loaded");
|
||||
});
|
||||
|
||||
it("renders single selected leaf node as selected", () => {
|
||||
const node = generateTestNode("root", {
|
||||
isSelected: () => true,
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders selected parent node as selected if no descendant nodes are selected", () => {
|
||||
const node = generateTestNode("root", {
|
||||
isSelected: () => true,
|
||||
children: [
|
||||
generateTestNode("child1", {
|
||||
children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")],
|
||||
}),
|
||||
generateTestNode("child2"),
|
||||
],
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders selected parent node as unselected if any descendant node is selected", () => {
|
||||
const node = generateTestNode("root", {
|
||||
isSelected: () => true,
|
||||
children: [
|
||||
generateTestNode("child1", {
|
||||
children: [
|
||||
generateTestNode("grandchild1", {
|
||||
isSelected: () => true,
|
||||
}),
|
||||
generateTestNode("grandchild2"),
|
||||
],
|
||||
}),
|
||||
generateTestNode("child2"),
|
||||
],
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component.find(TreeItemLayout).props().style?.backgroundColor).toBeUndefined();
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders an icon if the node has one", () => {
|
||||
const node = generateTestNode("root", {
|
||||
iconSrc: "the-icon.svg",
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a node as expandable if it has empty, but defined, children array", () => {
|
||||
const node = generateTestNode("root", {
|
||||
isLoading: true,
|
||||
children: [
|
||||
generateTestNode("child1", {
|
||||
children: [],
|
||||
}),
|
||||
generateTestNode("child2"),
|
||||
],
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render children if the node is loading", () => {
|
||||
const node = generateTestNode("root", {
|
||||
isLoading: true,
|
||||
children: [
|
||||
generateTestNode("child1", {
|
||||
children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")],
|
||||
}),
|
||||
generateTestNode("child2"),
|
||||
],
|
||||
});
|
||||
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("fully renders a tree", () => {
|
||||
const child3Loading = new PromiseSource();
|
||||
const node = generateTestNode("root", {
|
||||
isSelected: () => true,
|
||||
children: [
|
||||
generateTestNode("child1", {
|
||||
children: [
|
||||
generateTestNode("grandchild1", {
|
||||
iconSrc: "grandchild1Icon.svg",
|
||||
isSelected: () => true,
|
||||
}),
|
||||
generateTestNode("grandchild2"),
|
||||
],
|
||||
}),
|
||||
generateTestNode("child2Loading", {
|
||||
isLoading: true,
|
||||
children: [generateTestNode("grandchild3NotRendered")],
|
||||
}),
|
||||
generateTestNode("child3Expanding", {
|
||||
onExpanded: () => child3Loading.promise,
|
||||
}),
|
||||
],
|
||||
});
|
||||
const component = mount(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
|
||||
|
||||
// Find and expand the child3Expanding node
|
||||
const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel");
|
||||
act(() => {
|
||||
expandingChild.props().onOpenChange(null!, {
|
||||
open: true,
|
||||
value: "root/child3ExpandingLabel",
|
||||
target: null!,
|
||||
event: null!,
|
||||
type: "Click",
|
||||
});
|
||||
});
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
237
src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx
Normal file
237
src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import {
|
||||
Button,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
MenuOpenChangeData,
|
||||
MenuOpenEvent,
|
||||
MenuPopover,
|
||||
MenuTrigger,
|
||||
Spinner,
|
||||
Tree,
|
||||
TreeItem,
|
||||
TreeItemLayout,
|
||||
TreeItemValue,
|
||||
TreeOpenChangeData,
|
||||
TreeOpenChangeEvent,
|
||||
mergeClasses,
|
||||
} from "@fluentui/react-components";
|
||||
import { ChevronDown20Regular, ChevronRight20Regular, MoreHorizontal20Regular } from "@fluentui/react-icons";
|
||||
import { TreeStyleName, useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export interface TreeNodeMenuItem {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
iconSrc?: string;
|
||||
isDisabled?: boolean;
|
||||
styleClass?: string;
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
label: string;
|
||||
id?: string;
|
||||
children?: TreeNode[];
|
||||
contextMenu?: TreeNodeMenuItem[];
|
||||
iconSrc?: string | JSX.Element;
|
||||
isExpanded?: boolean;
|
||||
className?: TreeStyleName;
|
||||
isAlphaSorted?: boolean;
|
||||
// data?: any; // Piece of data corresponding to this node
|
||||
timestamp?: number;
|
||||
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
|
||||
isLoading?: boolean;
|
||||
isSelected?: () => boolean;
|
||||
onClick?: () => void; // Only if a leaf, other click will expand/collapse
|
||||
onExpanded?: () => Promise<void>;
|
||||
onCollapsed?: () => void;
|
||||
onContextMenuOpen?: () => void;
|
||||
}
|
||||
|
||||
export interface TreeNodeComponentProps {
|
||||
node: TreeNode;
|
||||
className?: string;
|
||||
treeNodeId: string;
|
||||
openItems: TreeItemValue[];
|
||||
}
|
||||
|
||||
/** Function that returns true if any descendant (at any depth) of this node is selected. */
|
||||
function isAnyDescendantSelected(node: TreeNode): boolean {
|
||||
return (
|
||||
node.children &&
|
||||
node.children.reduce(
|
||||
(previous: boolean, child: TreeNode) =>
|
||||
previous || (child.isSelected && child.isSelected()) || isAnyDescendantSelected(child),
|
||||
false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
node,
|
||||
treeNodeId,
|
||||
openItems,
|
||||
}: TreeNodeComponentProps): JSX.Element => {
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
const treeStyles = useTreeStyles();
|
||||
|
||||
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label);
|
||||
|
||||
let unsortedChildren;
|
||||
if (treeNode.isLeavesParentsSeparate) {
|
||||
// Separate parents and leave
|
||||
const parents: TreeNode[] = treeNode.children.filter((node) => node.children);
|
||||
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children);
|
||||
|
||||
if (treeNode.isAlphaSorted) {
|
||||
parents.sort(compareFct);
|
||||
leaves.sort(compareFct);
|
||||
}
|
||||
|
||||
unsortedChildren = parents.concat(leaves);
|
||||
} else {
|
||||
unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children;
|
||||
}
|
||||
|
||||
return unsortedChildren;
|
||||
};
|
||||
|
||||
// A branch node is any node with a defined children array, even if the array is empty.
|
||||
const isBranch = !!node.children;
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
|
||||
if (data.type === "Click" && !isBranch && node.onClick) {
|
||||
node.onClick();
|
||||
}
|
||||
if (!node.isExpanded && data.open && node.onExpanded) {
|
||||
// Catch the transition non-expanded to expanded
|
||||
setIsLoading(true);
|
||||
node.onExpanded?.().then(() => setIsLoading(false));
|
||||
} else if (node.isExpanded && !data.open && node.onCollapsed) {
|
||||
// Catch the transition expanded to non-expanded
|
||||
node.onCollapsed?.();
|
||||
}
|
||||
},
|
||||
[isBranch, node, setIsLoading],
|
||||
);
|
||||
|
||||
const onMenuOpenChange = useCallback(
|
||||
(e: MenuOpenEvent, data: MenuOpenChangeData) => {
|
||||
if (data.open) {
|
||||
node.onContextMenuOpen?.();
|
||||
}
|
||||
},
|
||||
[node],
|
||||
);
|
||||
|
||||
// We show a node as selected if it is selected AND no descendant is selected.
|
||||
// We want to show only the deepest selected node as selected.
|
||||
const isCurrentNodeSelected = node.isSelected && node.isSelected();
|
||||
const shouldShowAsSelected = isCurrentNodeSelected && !isAnyDescendantSelected(node);
|
||||
|
||||
const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => (
|
||||
<MenuItem
|
||||
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
||||
disabled={menuItem.isDisabled}
|
||||
key={menuItem.label}
|
||||
onClick={menuItem.onClick}
|
||||
>
|
||||
{menuItem.label}
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
// We use the expandIcon slot to hold the node icon too.
|
||||
// We only show a node icon for leaf nodes, even if a branch node has an iconSrc.
|
||||
const treeIcon =
|
||||
node.iconSrc === undefined ? undefined : typeof node.iconSrc === "string" ? (
|
||||
<img src={node.iconSrc} className={treeStyles.nodeIcon} alt="" />
|
||||
) : (
|
||||
node.iconSrc
|
||||
);
|
||||
|
||||
const expandIcon = isLoading ? (
|
||||
<Spinner size="extra-tiny" />
|
||||
) : !isBranch ? undefined : openItems.includes(treeNodeId) ? (
|
||||
<ChevronDown20Regular data-test="TreeNode/CollapseIcon" />
|
||||
) : (
|
||||
<ChevronRight20Regular data-text="TreeNode/ExpandIcon" />
|
||||
);
|
||||
|
||||
const treeItem = (
|
||||
<TreeItem
|
||||
data-test={`TreeNodeContainer:${treeNodeId}`}
|
||||
value={treeNodeId}
|
||||
itemType={isBranch ? "branch" : "leaf"}
|
||||
onOpenChange={onOpenChange}
|
||||
className={treeStyles.treeItem}
|
||||
>
|
||||
<TreeItemLayout
|
||||
className={mergeClasses(
|
||||
treeStyles.treeItemLayout,
|
||||
shouldShowAsSelected && treeStyles.selectedItem,
|
||||
node.className && treeStyles[node.className],
|
||||
)}
|
||||
data-test={`TreeNode:${treeNodeId}`}
|
||||
actions={
|
||||
contextMenuItems.length > 0 && {
|
||||
className: treeStyles.actionsButtonContainer,
|
||||
children: (
|
||||
<Menu onOpenChange={onMenuOpenChange}>
|
||||
<MenuTrigger disableButtonEnhancement>
|
||||
<Button
|
||||
aria-label="More options"
|
||||
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
|
||||
data-test="TreeNode/ContextMenuTrigger"
|
||||
appearance="subtle"
|
||||
icon={<MoreHorizontal20Regular />}
|
||||
/>
|
||||
</MenuTrigger>
|
||||
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
|
||||
<MenuList>{contextMenuItems}</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
),
|
||||
}
|
||||
}
|
||||
iconBefore={treeIcon}
|
||||
expandIcon={expandIcon}
|
||||
>
|
||||
<span className={treeStyles.nodeLabel}>{node.label}</span>
|
||||
</TreeItemLayout>
|
||||
{!node.isLoading && node.children?.length > 0 && (
|
||||
<Tree data-test={`Tree:${treeNodeId}`} className={treeStyles.tree}>
|
||||
{getSortedChildren(node).map((childNode: TreeNode) => (
|
||||
<TreeNodeComponent
|
||||
openItems={openItems}
|
||||
key={childNode.label}
|
||||
node={childNode}
|
||||
treeNodeId={`${treeNodeId}/${childNode.label}`}
|
||||
/>
|
||||
))}
|
||||
</Tree>
|
||||
)}
|
||||
</TreeItem>
|
||||
);
|
||||
|
||||
if (contextMenuItems.length === 0) {
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
// For accessibility, it's highly recommended that any 'actions' also be available in the context menu.
|
||||
// See https://react.fluentui.dev/?path=/docs/components-tree--default#actions
|
||||
return (
|
||||
<Menu positioning="below-end" openOnContext onOpenChange={onMenuOpenChange}>
|
||||
<MenuTrigger disableButtonEnhancement>{treeItem}</MenuTrigger>
|
||||
<MenuPopover>
|
||||
<MenuList>{contextMenuItems}</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,27 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TreeComponent renders a simple tree 1`] = `
|
||||
exports[`LegacyTreeComponent renders a simple tree 1`] = `
|
||||
<div
|
||||
className="treeComponent tree"
|
||||
role="tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={0}
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
"label": "Bchild1",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "2child2",
|
||||
},
|
||||
],
|
||||
@@ -33,18 +33,19 @@ exports[`TreeComponent renders a simple tree 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
|
||||
<div
|
||||
className=" main2 nodeItem "
|
||||
data-test="Tree/TreeNode:label"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
data-test="Tree/TreeNode/Header:label"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"paddingLeft": 9,
|
||||
}
|
||||
}
|
||||
@@ -55,7 +56,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src={Object {}}
|
||||
src={{}}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
@@ -77,7 +78,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
{
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
@@ -95,23 +96,23 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height={0}
|
||||
style={Object {}}
|
||||
style={{}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={3}
|
||||
key="Bchild1-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
@@ -120,11 +121,11 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
}
|
||||
paddingLeft={32}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={3}
|
||||
key="2child2-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
{
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
@@ -135,9 +136,10 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
|
||||
exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
|
||||
<div
|
||||
className="nodeClassname main12 nodeItem "
|
||||
data-test="Tree/TreeNode:label"
|
||||
id="id"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
@@ -145,9 +147,9 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
data-test="Tree/TreeNode/Header:label"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"paddingLeft": 23,
|
||||
}
|
||||
}
|
||||
@@ -158,7 +160,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src={Object {}}
|
||||
src={{}}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
@@ -175,10 +177,10 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
ariaLabel="More options"
|
||||
className="treeMenuEllipsis"
|
||||
menuIconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "More",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"styles": {
|
||||
"root": {
|
||||
"fontSize": "18px",
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
@@ -186,13 +188,13 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
}
|
||||
}
|
||||
menuProps={
|
||||
Object {
|
||||
{
|
||||
"contextualMenuItemAs": [Function],
|
||||
"coverTarget": true,
|
||||
"directionalHint": 3,
|
||||
"isBeakVisible": false,
|
||||
"items": Array [
|
||||
Object {
|
||||
"items": [
|
||||
{
|
||||
"className": undefined,
|
||||
"disabled": true,
|
||||
"key": "menuLabel",
|
||||
@@ -207,8 +209,8 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
}
|
||||
name="More"
|
||||
styles={
|
||||
Object {
|
||||
"rootFocused": Object {
|
||||
{
|
||||
"rootFocused": {
|
||||
"outline": "1px dashed undefined",
|
||||
},
|
||||
}
|
||||
@@ -229,7 +231,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
{
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
@@ -247,33 +249,33 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
style={{}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="2child2-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
{
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
paddingLeft={214}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="Bchild1-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
@@ -287,18 +289,19 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
|
||||
<div
|
||||
className=" main2 nodeItem "
|
||||
data-test="Tree/TreeNode:label"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
data-test="Tree/TreeNode/Header:label"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"paddingLeft": 9,
|
||||
}
|
||||
}
|
||||
@@ -309,7 +312,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src={Object {}}
|
||||
src={{}}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
@@ -331,7 +334,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
{
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
@@ -349,7 +352,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
style={{}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
@@ -360,9 +363,10 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
||||
exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
|
||||
<div
|
||||
className="nodeClassname main12 nodeItem "
|
||||
data-test="Tree/TreeNode:label"
|
||||
id="id"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
@@ -370,9 +374,9 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
data-test="Tree/TreeNode/Header:label"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"paddingLeft": 23,
|
||||
}
|
||||
}
|
||||
@@ -383,7 +387,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src={Object {}}
|
||||
src={{}}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
@@ -400,10 +404,10 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
ariaLabel="More options"
|
||||
className="treeMenuEllipsis"
|
||||
menuIconProps={
|
||||
Object {
|
||||
{
|
||||
"iconName": "More",
|
||||
"styles": Object {
|
||||
"root": Object {
|
||||
"styles": {
|
||||
"root": {
|
||||
"fontSize": "18px",
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
@@ -411,20 +415,20 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
}
|
||||
}
|
||||
menuProps={
|
||||
Object {
|
||||
{
|
||||
"contextualMenuItemAs": [Function],
|
||||
"coverTarget": true,
|
||||
"directionalHint": 3,
|
||||
"isBeakVisible": false,
|
||||
"items": Array [],
|
||||
"items": [],
|
||||
"onMenuDismissed": [Function],
|
||||
"onMenuOpened": [Function],
|
||||
}
|
||||
}
|
||||
name="More"
|
||||
styles={
|
||||
Object {
|
||||
"rootFocused": Object {
|
||||
{
|
||||
"rootFocused": {
|
||||
"outline": "1px dashed undefined",
|
||||
},
|
||||
}
|
||||
@@ -445,7 +449,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
{
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
@@ -463,23 +467,23 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
style={{}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="bchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
@@ -488,16 +492,16 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
}
|
||||
paddingLeft={192}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="dchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
@@ -506,21 +510,21 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
}
|
||||
paddingLeft={192}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="aChild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
{
|
||||
"label": "aChild",
|
||||
}
|
||||
}
|
||||
paddingLeft={214}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={13}
|
||||
key="cchild-13-undefined"
|
||||
node={
|
||||
Object {
|
||||
{
|
||||
"label": "cchild",
|
||||
}
|
||||
}
|
||||
@@ -531,18 +535,19 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
|
||||
<div
|
||||
className=" main2 nodeItem "
|
||||
data-test="Tree/TreeNode:label"
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
className="treeNodeHeader "
|
||||
data-test="label"
|
||||
data-test="Tree/TreeNode/Header:label"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"paddingLeft": 9,
|
||||
}
|
||||
}
|
||||
@@ -553,7 +558,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
className="expandCollapseIcon"
|
||||
onKeyPress={[Function]}
|
||||
role="button"
|
||||
src={Object {}}
|
||||
src={{}}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span
|
||||
@@ -575,7 +580,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
<AnimateHeight
|
||||
animateOpacity={false}
|
||||
animationStateClasses={
|
||||
Object {
|
||||
{
|
||||
"animating": "rah-animating",
|
||||
"animatingDown": "rah-animating--down",
|
||||
"animatingToHeightAuto": "rah-animating--to-height-auto",
|
||||
@@ -593,23 +598,23 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
duration={200}
|
||||
easing="ease"
|
||||
height="auto"
|
||||
style={Object {}}
|
||||
style={{}}
|
||||
>
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={3}
|
||||
key="Bchild1-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"label": "ZgrandChild11",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"label": "AgrandChild12",
|
||||
},
|
||||
],
|
||||
@@ -618,11 +623,11 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
|
||||
}
|
||||
paddingLeft={32}
|
||||
/>
|
||||
<TreeNodeComponent
|
||||
<LegacyTreeNodeComponent
|
||||
generation={3}
|
||||
key="2child2-3-undefined"
|
||||
node={
|
||||
Object {
|
||||
{
|
||||
"label": "2child2",
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user