mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
1 Commits
ashleyst/r
...
master_clo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7680c6c9e |
@@ -1,16 +0,0 @@
|
||||
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
|
||||
|
||||
# Install pre-reqs for gyp, and 'canvas' npm module
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
make \
|
||||
gcc \
|
||||
g++ \
|
||||
python3-minimal \
|
||||
libcairo2-dev \
|
||||
libpango1.0-dev \
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install node-gyp to build native modules
|
||||
RUN npm install -g node-gyp
|
||||
@@ -1,32 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||
{
|
||||
"name": "Azure Cosmos DB Explorer",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"onCreateCommand": ".devcontainer/oncreate",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/azure-cli:1": {
|
||||
"version": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {
|
||||
"installDirectlyFromGitHubRelease": true,
|
||||
"version": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "yarn install",
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Install packages once, to prime the node_modules directory.
|
||||
npm ci
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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,5 +1,3 @@
|
||||
playwright.config.ts
|
||||
|
||||
**/node_modules/
|
||||
src/**/__mocks__/**/*
|
||||
dist/
|
||||
@@ -91,7 +89,10 @@ 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
|
||||
@@ -127,7 +128,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/LegacyTreeComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
|
||||
136
.github/workflows/ci.yml
vendored
136
.github/workflows/ci.yml
vendored
@@ -8,9 +8,6 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
codemetrics:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -104,6 +101,72 @@ 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 }}
|
||||
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/')
|
||||
@@ -153,70 +216,3 @@ 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,10 +9,6 @@ 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"
|
||||
@@ -20,17 +16,10 @@ jobs:
|
||||
name: "Cleanup Test Database Accounts"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||
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:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,7 +17,3 @@ Contracts/*
|
||||
failure.png
|
||||
screenshots/*
|
||||
GettingStarted-ignore*.ipynb
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1,4 +1 @@
|
||||
save-exact=true
|
||||
|
||||
# Ignore peer dependency conflicts
|
||||
force=true # TODO: Remove this when we update to React 17 or higher!
|
||||
7
canvas/README.md
Normal file
7
canvas/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Why?
|
||||
|
||||
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
|
||||
|
||||
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
|
||||
|
||||
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved
|
||||
1
canvas/index.js
Normal file
1
canvas/index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
11
canvas/package.json
Normal file
11
canvas/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "canvas",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="uuid-f8d4d392-7c12-4bd9-baff-66fbf7814b91" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<path d="m3.802,14.032c.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128v4.073c-.286,0-.574-.078-.824-.234l-4.374-2.734Z" fill="#225086"/>
|
||||
<path d="m7.853,1.507L.353,9.967c-.579.654-.428,1.642.323,2.111,0,0,2.776,1.735,3.126,1.954.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128-4.364-2.728,4.365-4.924V1s0,0,0,0c-.424,0-.847.169-1.147.507Z" fill="#6df"/>
|
||||
<polygon points="4.636 10.199 4.688 10.231 9 12.927 9.001 12.927 9.001 12.927 9.001 5.276 9 5.275 4.636 10.199" fill="#cbf8ff"/>
|
||||
<path d="m17.324,12.078c.751-.469.902-1.457.323-2.111l-4.921-5.551c-.397-.185-.842-.291-1.313-.291-.925,0-1.752.399-2.302,1.026l-.109.123h0s4.364,4.924,4.364,4.924h0s0,0,0,0l-4.365,2.728v4.073c.287,0,.573-.078.823-.234l7.5-4.688Z" fill="#074793"/>
|
||||
<path d="m9.001,1v4.275s.109-.123.109-.123c.55-.627,1.377-1.026,2.302-1.026.472,0,.916.107,1.313.291l-2.579-2.909c-.299-.338-.723-.507-1.146-.507Z" fill="#0294e4"/>
|
||||
<polygon points="13.365 10.199 13.365 10.199 13.365 10.199 9.001 5.276 9.001 12.926 13.365 10.199" fill="#96bcc2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
13
jest-playwright.config.js
Normal file
13
jest-playwright.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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", "lcov"],
|
||||
coverageReporters: ["json", "text", "cobertura"],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
coverageThreshold: {
|
||||
@@ -76,11 +76,6 @@ 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
|
||||
@@ -134,7 +129,8 @@ module.exports = {
|
||||
snapshotSerializers: ["enzyme-to-json/serializer"],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "jsdom",
|
||||
// testEnvironment: "jest-environment-jsdom",
|
||||
|
||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
@@ -158,7 +154,7 @@ module.exports = {
|
||||
// testResultsProcessor: "./trxProcessor.js",
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
testRunner: "jest-circus/runner",
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
@@ -168,13 +164,13 @@ module.exports = {
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
transform: {
|
||||
"^.+\\.html?$": "jest-html-loader",
|
||||
"^.+\\.html?$": "html-loader-jest",
|
||||
"^.+\\.[t|j]sx?$": "babel-jest",
|
||||
"^.+\\.svg$": "<rootDir>/jest/svgTransform.js",
|
||||
"^.+\\.svg$": "<rootDir>/svgTransform.js",
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
transformIgnorePatterns: ["/node_modules/(?!@fluentui/react-icons)", "/externals/"],
|
||||
transformIgnorePatterns: ["/node_modules/", "/externals/"],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
@@ -187,7 +183,4 @@ module.exports = {
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
// TODO: toMatchInlineSnapshot() does not work with prettier 3. Remove when fixed: https://github.com/jestjs/jest/issues/14305
|
||||
prettierPath: null,
|
||||
};
|
||||
|
||||
7
jest.config.playwright.js
Normal file
7
jest.config.playwright.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: "jest-playwright-preset",
|
||||
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
||||
setupFiles: ["dotenv/config"],
|
||||
testEnvironment: "./test/playwrightEnv.js",
|
||||
setupFilesAfterEnv: ["expect-playwright"],
|
||||
};
|
||||
@@ -130,7 +130,6 @@
|
||||
@ActiveTabWidth: 141px;
|
||||
@TabsHeight: 30px;
|
||||
@TabsWidth: 140px;
|
||||
@ContentWrapper: 111px;
|
||||
@StatusIconContainerSize: 18px;
|
||||
@LoadingErrorIconSize: 14px;
|
||||
@ErrorIconContainer: 16px;
|
||||
@@ -168,7 +167,7 @@
|
||||
|
||||
@FabricBoxBorderRadius: 8px;
|
||||
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||
@FabricBoxMargin: 4px 8px 4px 8px;
|
||||
@FabricBoxMargin: 4px 3px 4px 3px;
|
||||
|
||||
@FabricAccentMediumHigh: #0c695a;
|
||||
@FabricAccentMedium: #117865;
|
||||
@@ -337,10 +336,3 @@
|
||||
height: 0;
|
||||
border-color: @InfoPointerColor transparent;
|
||||
}
|
||||
/*********************************************************************************************************
|
||||
Screen Reader Only
|
||||
**********************************************************************************************************/
|
||||
.screenReaderOnly {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
@@ -1906,14 +1906,8 @@ input::-webkit-calendar-picker-indicator::after {
|
||||
}
|
||||
|
||||
.nav-tabs-margin {
|
||||
height: 32px;
|
||||
padding-top: 8px;
|
||||
background-color: #f2f2f2;
|
||||
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.navTabHeight {
|
||||
@@ -2080,6 +2074,14 @@ a:link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.resourceTreeAndTabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: clip;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.collectiontitle {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
@@ -2262,49 +2264,38 @@ 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;
|
||||
}
|
||||
|
||||
.monaco-editor .quick-input-list-label {
|
||||
/* Restore some of Monaco's default styles that are clobbered by our global styles */
|
||||
padding: 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.monaco-editor .quick-input-list .highlight {
|
||||
/* Padding in highlighted text within the quick input list breaks the flow of the text */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
td a {
|
||||
color: #393939;
|
||||
}
|
||||
@@ -2314,15 +2305,21 @@ td a:hover {
|
||||
}
|
||||
|
||||
.loadMore {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-left: 30%;
|
||||
padding-top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loadMore > a:focus {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
|
||||
#content.active .tabdocuments .scrollable {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-fixed thead {
|
||||
width: 97%;
|
||||
padding-left: 18px;
|
||||
@@ -2358,9 +2355,10 @@ a:link {
|
||||
|
||||
.tabsManagerContainer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
min-height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@@ -2549,12 +2547,10 @@ a:link {
|
||||
}
|
||||
|
||||
.filterdivs {
|
||||
margin: 10px 0px;
|
||||
padding-top: 15px;
|
||||
height: 45px;
|
||||
margin-bottom: 8px;
|
||||
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 {
|
||||
@@ -2617,8 +2613,7 @@ a:link {
|
||||
|
||||
.tabPanesContainer {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -2652,7 +2647,7 @@ a:link {
|
||||
width: @ActiveTabWidth;
|
||||
}
|
||||
|
||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText {
|
||||
font-weight: bolder;
|
||||
border-bottom: 2px solid rgba(0, 120, 212, 1);
|
||||
}
|
||||
@@ -2688,9 +2683,6 @@ a:link {
|
||||
width: @TabsWidth;
|
||||
border-right: @ButtonBorderWidth solid @BaseMedium;
|
||||
white-space: nowrap;
|
||||
.contentWrapper {
|
||||
.flex-display();
|
||||
width: @ContentWrapper;
|
||||
|
||||
.statusIconContainer {
|
||||
width: @StatusIconContainerSize;
|
||||
@@ -2749,10 +2741,9 @@ a:link {
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tabIconSection {
|
||||
width: 29px;
|
||||
width: 30px;
|
||||
position: relative;
|
||||
padding-top: 2px;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ a:focus {
|
||||
}
|
||||
|
||||
.nav-tabs-margin {
|
||||
padding-top: 5px;
|
||||
padding-top: 8px;
|
||||
background-color: #ffffff
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ a:focus {
|
||||
border-bottom: 2px solid @FabricAccentMedium;
|
||||
}
|
||||
|
||||
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.contentWrapper>.tabNavText {
|
||||
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.tabNavText {
|
||||
border-bottom: 0px none transparent;
|
||||
}
|
||||
|
||||
@@ -93,11 +93,9 @@ a:focus {
|
||||
width: calc(@TabsWidth - (@SmallSpace * 2));
|
||||
padding-bottom: @SmallSpace;
|
||||
|
||||
.contentWrapper {
|
||||
.statusIconContainer {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabIconSection {
|
||||
.cancelButton {
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
.dataResourceTree {
|
||||
margin-left: @MediumSpace;
|
||||
overflow: auto;
|
||||
|
||||
.databaseHeader {
|
||||
padding: 1px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.collectionHeader {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loadMoreHeader {
|
||||
color: RGB(5, 99, 193);
|
||||
}
|
||||
}
|
||||
|
||||
.notebookResourceTree {
|
||||
|
||||
22652
package-lock.json
generated
22652
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
83
package.json
83
package.json
@@ -5,21 +5,21 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@azure/arm-cosmosdb": "9.1.0",
|
||||
"@azure/cosmos": "4.0.1-beta.3",
|
||||
"@azure/cosmos": "4.0.1-beta.2",
|
||||
"@azure/cosmos-language-service": "0.0.5",
|
||||
"@azure/identity": "1.5.2",
|
||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
||||
"@azure/identity": "1.2.1",
|
||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||
"@azure/msal-browser": "2.14.2",
|
||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||
"@fluentui/react": "8.119.0",
|
||||
"@fluentui/react-components": "9.54.2",
|
||||
"@fluentui/react": "8.112.1",
|
||||
"@fluentui/react-components": "9.34.0",
|
||||
"@jupyterlab/services": "6.0.2",
|
||||
"@jupyterlab/terminal": "3.0.3",
|
||||
"@microsoft/applicationinsights-web": "2.6.1",
|
||||
"@nteract/commutable": "7.5.1",
|
||||
"@nteract/connected-components": "6.8.2",
|
||||
"@nteract/core": "15.1.9",
|
||||
"@nteract/core": "15.1.0",
|
||||
"@nteract/data-explorer": "8.0.3",
|
||||
"@nteract/directory-listing": "2.0.6",
|
||||
"@nteract/dropdown-menu": "1.0.1",
|
||||
@@ -42,21 +42,19 @@
|
||||
"@nteract/transform-vega": "7.0.6",
|
||||
"@octokit/rest": "17.9.2",
|
||||
"@phosphor/widgets": "1.9.3",
|
||||
"@testing-library/jest-dom": "6.4.6",
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@types/lodash": "4.14.171",
|
||||
"@types/mkdirp": "1.0.1",
|
||||
"@types/node-fetch": "2.5.7",
|
||||
"@xmldom/xmldom": "0.7.13",
|
||||
"allotment": "1.20.2",
|
||||
"applicationinsights": "1.8.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"canvas": "2.11.2",
|
||||
"canvas": "file:./canvas",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"clipboard-copy": "4.0.1",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"crossroads": "0.12.2",
|
||||
"css-element-queries": "1.1.1",
|
||||
"d3": "7.8.5",
|
||||
"d3": "6.1.1",
|
||||
"datatables.net-colreorder-dt": "1.7.0",
|
||||
"datatables.net-dt": "1.13.8",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -67,18 +65,16 @@
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"hasher": "1.2.0",
|
||||
"html2canvas": "1.0.0-rc.5",
|
||||
"i18next": "23.11.5",
|
||||
"i18next": "19.8.4",
|
||||
"i18next-browser-languagedetector": "6.0.1",
|
||||
"i18next-http-backend": "1.0.23",
|
||||
"iframe-resizer-react": "1.1.0",
|
||||
"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",
|
||||
@@ -93,31 +89,27 @@
|
||||
"react-dnd-html5-backend": "14.0.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-hotkeys": "2.0.0",
|
||||
"react-i18next": "14.1.2",
|
||||
"react-i18next": "11.8.5",
|
||||
"react-notification-system": "0.2.17",
|
||||
"react-redux": "7.1.3",
|
||||
"react-splitter-layout": "4.0.0",
|
||||
"react-string-format": "1.0.1",
|
||||
"react-window": "1.8.10",
|
||||
"react-youtube": "9.0.1",
|
||||
"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",
|
||||
"tinykeys": "2.1.0",
|
||||
"underscore": "1.12.1",
|
||||
"underscore": "1.9.1",
|
||||
"utility-types": "3.10.0",
|
||||
"zustand": "3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "7.24.7",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@playwright/test": "1.44.0",
|
||||
"@babel/core": "7.9.0",
|
||||
"@babel/preset-env": "7.9.0",
|
||||
"@babel/preset-react": "7.9.4",
|
||||
"@babel/preset-typescript": "7.9.0",
|
||||
"@testing-library/react": "11.2.3",
|
||||
"@types/applicationinsights-js": "1.0.7",
|
||||
"@types/codemirror": "0.0.56",
|
||||
@@ -126,20 +118,19 @@
|
||||
"@types/datatables.net": "1.10.28",
|
||||
"@types/datatables.net-colreorder": "1.4.5",
|
||||
"@types/dom-to-image": "2.6.2",
|
||||
"@types/enzyme": "3.10.12",
|
||||
"@types/enzyme-adapter-react-16": "1.0.9",
|
||||
"@types/enzyme": "3.10.7",
|
||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/post-robot": "10.0.1",
|
||||
"@types/q": "1.5.1",
|
||||
"@types/react": "17.0.44",
|
||||
"@types/react-dom": "17.0.15",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.3",
|
||||
"@types/react-notification-system": "0.2.39",
|
||||
"@types/react-redux": "7.1.7",
|
||||
"@types/react-splitter-layout": "3.0.1",
|
||||
"@types/react-window": "1.8.8",
|
||||
"@types/sanitize-html": "1.27.2",
|
||||
"@types/sinon": "2.3.3",
|
||||
"@types/styled-components": "5.1.1",
|
||||
@@ -148,55 +139,56 @@
|
||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||
"@typescript-eslint/parser": "6.7.4",
|
||||
"@webpack-cli/serve": "2.0.5",
|
||||
"babel-jest": "29.7.0",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"buffer": "5.1.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||
"create-file-webpack": "1.0.2",
|
||||
"css-loader": "6.8.1",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.8",
|
||||
"enzyme-to-json": "3.6.2",
|
||||
"enzyme-adapter-react-16": "1.15.5",
|
||||
"enzyme-to-json": "3.6.1",
|
||||
"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": "5.0.0",
|
||||
"html-loader": "0.5.5",
|
||||
"html-loader-jest": "0.2.1",
|
||||
"html-webpack-plugin": "5.5.3",
|
||||
"jest": "29.7.0",
|
||||
"jest-canvas-mock": "2.5.2",
|
||||
"jest-circus": "29.7.0",
|
||||
"jest-html-loader": "1.0.0",
|
||||
"jest": "26.6.3",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"jest-playwright-preset": "1.5.1",
|
||||
"jest-react-hooks-shallow": "1.5.1",
|
||||
"jest-trx-results-processor": "3.0.2",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-trx-results-processor": "0.0.7",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "11.1.3",
|
||||
"less-vars-loader": "1.1.0",
|
||||
"mini-css-extract-plugin": "2.1.0",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-fetch": "2.6.1",
|
||||
"playwright": "1.13.0",
|
||||
"prettier": "3.0.3",
|
||||
"process": "0.11.10",
|
||||
"querystring-es3": "0.2.1",
|
||||
"raw-loader": "0.5.1",
|
||||
"react-dev-utils": "12.0.1",
|
||||
"react-dev-utils": "11.0.4",
|
||||
"rimraf": "3.0.0",
|
||||
"sinon": "3.2.1",
|
||||
"style-loader": "0.23.0",
|
||||
"ts-loader": "9.2.4",
|
||||
"typedoc": "0.26.2",
|
||||
"typescript": "4.9.5",
|
||||
"typedoc": "0.21.5",
|
||||
"typescript": "4.3.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.2"
|
||||
"webpack-dev-server": "4.15.1"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
@@ -211,7 +203,6 @@
|
||||
"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",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/node_modules/@phosphor/virtualdom/lib/index.d.ts b/node_modules/@phosphor/virtualdom/lib/index.d.ts
|
||||
index 95682b9..73e2daa 100644
|
||||
--- a/node_modules/@phosphor/virtualdom/lib/index.d.ts
|
||||
+++ b/node_modules/@phosphor/virtualdom/lib/index.d.ts
|
||||
@@ -58,7 +58,7 @@ export declare type ElementEventMap = {
|
||||
ondrop: DragEvent;
|
||||
ondurationchange: Event;
|
||||
onemptied: Event;
|
||||
- onended: MediaStreamErrorEvent;
|
||||
+ onended: ErrorEvent;
|
||||
onerror: ErrorEvent;
|
||||
onfocus: FocusEvent;
|
||||
oninput: Event;
|
||||
@@ -1,60 +0,0 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
55
src/Common/CollapsedResourceTree.tsx
Normal file
55
src/Common/CollapsedResourceTree.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
|
||||
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
|
||||
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface CollapsedResourceTreeProps {
|
||||
toggleLeftPaneExpanded: () => void;
|
||||
isLeftPaneExpanded: boolean;
|
||||
}
|
||||
|
||||
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
|
||||
toggleLeftPaneExpanded,
|
||||
isLeftPaneExpanded,
|
||||
}: CollapsedResourceTreeProps): JSX.Element => {
|
||||
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
|
||||
|
||||
useEffect(() => {
|
||||
if (focusButton.current) {
|
||||
focusButton.current.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
toggleLeftPaneExpanded();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||
<div className="main-nav nav">
|
||||
<ul className="nav">
|
||||
<li
|
||||
className="resourceTreeCollapse"
|
||||
id="collapseToggleLeftPaneButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={getApiShortDisplayName() + `Expand tree`}
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
ref={focusButton}
|
||||
>
|
||||
<span className="leftarrowCollapsed">
|
||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||
</span>
|
||||
<span className="collectionCollapsed">
|
||||
<span>{getApiShortDisplayName()}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -88,12 +88,6 @@ 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
|
||||
@@ -130,39 +124,6 @@ export enum MongoBackendEndpointType {
|
||||
remote,
|
||||
}
|
||||
|
||||
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";
|
||||
public static readonly SampleData: string = "SampleData";
|
||||
}
|
||||
|
||||
export class PortalBackendEndpoints {
|
||||
public static readonly Development: string = "https://localhost:7235";
|
||||
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
|
||||
public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com";
|
||||
public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us";
|
||||
public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn";
|
||||
}
|
||||
|
||||
export class MongoProxyEndpoints {
|
||||
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";
|
||||
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
|
||||
}
|
||||
|
||||
export class CassandraProxyEndpoints {
|
||||
public static readonly Development: string = "https://localhost:7240";
|
||||
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
|
||||
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
|
||||
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
|
||||
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
|
||||
}
|
||||
|
||||
//TODO: Remove this when new backend is migrated over
|
||||
export class CassandraBackend {
|
||||
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||
@@ -178,7 +139,7 @@ export class CassandraBackend {
|
||||
export class CassandraProxyAPIs {
|
||||
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
|
||||
public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete";
|
||||
public static readonly queryApi: string = "api/cassandra";
|
||||
public static readonly queryApi: string = "api/cassandra/postquery";
|
||||
public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra";
|
||||
public static readonly keysApi: string = "api/cassandra/keys";
|
||||
public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys";
|
||||
@@ -186,12 +147,6 @@ export class CassandraProxyAPIs {
|
||||
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
|
||||
}
|
||||
|
||||
export class AadEndpoints {
|
||||
public static readonly Prod: string = "https://login.microsoftonline.com/";
|
||||
public static readonly Fairfax: string = "https://login.microsoftonline.us/";
|
||||
public static readonly Mooncake: string = "https://login.partner.microsoftonline.cn/";
|
||||
}
|
||||
|
||||
export class Queries {
|
||||
public static CustomPageOption: string = "custom";
|
||||
public static UnlimitedPageOption: string = "unlimited";
|
||||
@@ -206,12 +161,6 @@ export class Queries {
|
||||
public static readonly DefaultMaxWaitTimeInSeconds = 30;
|
||||
}
|
||||
|
||||
export class RBACOptions {
|
||||
public static setAutomaticRBACOption: string = "Automatic";
|
||||
public static setTrueRBACOption: string = "True";
|
||||
public static setFalseRBACOption: string = "False";
|
||||
}
|
||||
|
||||
export class SavedQueries {
|
||||
public static readonly CollectionName: string = "___Query";
|
||||
public static readonly DatabaseName: string = "___Cosmos";
|
||||
@@ -271,7 +220,6 @@ 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 {
|
||||
@@ -293,7 +241,6 @@ export class HttpStatusCodes {
|
||||
public static readonly Accepted: number = 202;
|
||||
public static readonly NoContent: number = 204;
|
||||
public static readonly NotModified: number = 304;
|
||||
public static readonly BadRequest: number = 400;
|
||||
public static readonly Unauthorized: number = 401;
|
||||
public static readonly Forbidden: number = 403;
|
||||
public static readonly NotFound: number = 404;
|
||||
@@ -499,13 +446,29 @@ export class JunoEndpoints {
|
||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
||||
}
|
||||
|
||||
export class MongoProxyEndpoints {
|
||||
public static readonly Development: 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";
|
||||
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
|
||||
}
|
||||
|
||||
export class CassandraProxyEndpoints {
|
||||
public static readonly Development: string = "https://localhost:7240";
|
||||
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
|
||||
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
|
||||
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
|
||||
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
|
||||
}
|
||||
|
||||
export class PriorityLevel {
|
||||
public static readonly High = "high";
|
||||
public static readonly Low = "low";
|
||||
public static readonly Default = "low";
|
||||
}
|
||||
|
||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||
|
||||
export const QueryCopilotSampleContainerSchema = {
|
||||
|
||||
@@ -1,6 +1,47 @@
|
||||
import { ResourceType } from "@azure/cosmos";
|
||||
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
|
||||
import { updateUserContext } from "../UserContext";
|
||||
import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient";
|
||||
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
|
||||
|
||||
describe("tokenProvider", () => {
|
||||
const options = {
|
||||
verb: "GET" as any,
|
||||
path: "/",
|
||||
resourceId: "",
|
||||
resourceType: "dbs" as ResourceType,
|
||||
headers: {},
|
||||
getAuthorizationTokenUsingMasterKey: () => "",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
updateConfigContext({
|
||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||
});
|
||||
window.fetch = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
json: () => "{}",
|
||||
headers: new Map(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("calls the auth token service if no master key is set", async () => {
|
||||
await tokenProvider(options);
|
||||
expect((window.fetch as any).mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("does not call the auth service if a master key is set", async () => {
|
||||
updateUserContext({
|
||||
masterKey: "foo",
|
||||
});
|
||||
await tokenProvider(options);
|
||||
expect((window.fetch as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTokenFromAuthService", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
|
||||
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BackendApi, PriorityLevel } from "../Common/Constants";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { PriorityLevel } from "../Common/Constants";
|
||||
import { Platform, configContext } from "../ConfigContext";
|
||||
import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
@@ -19,18 +18,7 @@ const _global = typeof self === "undefined" ? window : self;
|
||||
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||
|
||||
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
|
||||
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
||||
Logger.logInfo(
|
||||
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
||||
"Explorer/tokenProvider",
|
||||
);
|
||||
if (!userContext.aadToken) {
|
||||
logConsoleError(
|
||||
`AAD token does not exist. Please click on "Login for Entra ID" button prior to performing Entra ID RBAC operations`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
|
||||
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
|
||||
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
|
||||
return authorizationToken;
|
||||
@@ -63,36 +51,21 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||
case Cosmos.ResourceType.offer:
|
||||
case Cosmos.ResourceType.user:
|
||||
case Cosmos.ResourceType.permission:
|
||||
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
||||
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
||||
// (which is a valid token, but won't work for these operations).
|
||||
const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
||||
|
||||
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
||||
// User master tokens
|
||||
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
||||
FabricMessageTypes.GetAuthorizationToken,
|
||||
MessageTypes.GetAuthorizationToken,
|
||||
[requestInfo],
|
||||
userContext.fabricContext.connectionId,
|
||||
);
|
||||
console.log("Response from Fabric: ", authorizationToken);
|
||||
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
||||
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
||||
***********************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
if (userContext.masterKey) {
|
||||
Logger.logInfo(`Master Key exists`, "Explorer/tokenProvider");
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(
|
||||
verb,
|
||||
resourceId,
|
||||
resourceType,
|
||||
headers,
|
||||
userContext.masterKey,
|
||||
);
|
||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
}
|
||||
|
||||
@@ -124,37 +97,6 @@ export async function getTokenFromAuthService(
|
||||
verb: string,
|
||||
resourceType: string,
|
||||
resourceId?: string,
|
||||
): Promise<AuthorizationToken> {
|
||||
if (!useNewPortalBackendEndpoint(BackendApi.RuntimeProxy)) {
|
||||
return getTokenFromAuthService_ToBeDeprecated(verb, resourceType, resourceId);
|
||||
}
|
||||
|
||||
try {
|
||||
const host: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||
const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-encrypted-auth-token": userContext.accessToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
verb,
|
||||
resourceType,
|
||||
resourceId,
|
||||
}),
|
||||
});
|
||||
const result: AuthorizationToken = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenFromAuthService_ToBeDeprecated(
|
||||
verb: string,
|
||||
resourceType: string,
|
||||
resourceId?: string,
|
||||
): Promise<AuthorizationToken> {
|
||||
try {
|
||||
const host = configContext.BACKEND_ENDPOINT;
|
||||
@@ -188,11 +130,8 @@ enum SDKSupportedCapabilities {
|
||||
let _client: Cosmos.CosmosClient;
|
||||
|
||||
export function client(): Cosmos.CosmosClient {
|
||||
if (_client) {
|
||||
if (!userContext.hasDataPlaneRbacSettingChanged) {
|
||||
return _client;
|
||||
}
|
||||
}
|
||||
if (_client) return _client;
|
||||
|
||||
let _defaultHeaders: Cosmos.CosmosHeaders = {};
|
||||
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
|
||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
||||
@@ -211,7 +150,7 @@ export function client(): Cosmos.CosmosClient {
|
||||
|
||||
const options: Cosmos.CosmosClientOptions = {
|
||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||
key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey,
|
||||
key: userContext.masterKey,
|
||||
tokenProvider,
|
||||
userAgentSuffix: "Azure Portal",
|
||||
defaultHeaders: _defaultHeaders,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { userContext } from "../UserContext";
|
||||
|
||||
export const getEntityName = (multiple?: boolean): string => {
|
||||
export const getEntityName = (): string => {
|
||||
if (userContext.apiType === "Mongo") {
|
||||
return multiple ? "documents" : "document";
|
||||
return "document";
|
||||
}
|
||||
|
||||
return multiple ? "items" : "item";
|
||||
return "item";
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { PortalBackendEndpoints } from "Common/Constants";
|
||||
import { updateConfigContext } from "ConfigContext";
|
||||
import * as EnvironmentUtility from "./EnvironmentUtility";
|
||||
|
||||
describe("Environment Utility Test", () => {
|
||||
@@ -13,18 +11,4 @@ describe("Environment Utility Test", () => {
|
||||
const expectedResult = "test/";
|
||||
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it("Detect environment is Mpac", () => {
|
||||
updateConfigContext({
|
||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
||||
});
|
||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Mpac);
|
||||
});
|
||||
|
||||
it("Detect environment is Development", () => {
|
||||
updateConfigContext({
|
||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Development,
|
||||
});
|
||||
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Development);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,6 @@
|
||||
import { PortalBackendEndpoints } from "Common/Constants";
|
||||
import { configContext } from "ConfigContext";
|
||||
|
||||
export function normalizeArmEndpoint(uri: string): string {
|
||||
if (uri && uri.slice(-1) !== "/") {
|
||||
return `${uri}/`;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
export enum Environment {
|
||||
Development = "Development",
|
||||
Mpac = "MPAC",
|
||||
Prod = "Prod",
|
||||
Fairfax = "Fairfax",
|
||||
Mooncake = "Mooncake",
|
||||
}
|
||||
|
||||
export const getEnvironment = (): Environment => {
|
||||
const environmentMap: { [key: string]: Environment } = {
|
||||
[PortalBackendEndpoints.Development]: Environment.Development,
|
||||
[PortalBackendEndpoints.Mpac]: Environment.Mpac,
|
||||
[PortalBackendEndpoints.Prod]: Environment.Prod,
|
||||
[PortalBackendEndpoints.Fairfax]: Environment.Fairfax,
|
||||
[PortalBackendEndpoints.Mooncake]: Environment.Mooncake,
|
||||
};
|
||||
|
||||
return environmentMap[configContext.PORTAL_BACKEND_ENDPOINT];
|
||||
};
|
||||
|
||||
@@ -53,8 +53,7 @@ const replaceKnownError = (errorMessage: string): string => {
|
||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||
} else if (
|
||||
errorMessage?.indexOf("The user aborted a request") >= 0 ||
|
||||
errorMessage?.indexOf("The operation was aborted") >= 0 ||
|
||||
errorMessage === "signal is aborted without reason"
|
||||
errorMessage?.indexOf("The operation was aborted") >= 0
|
||||
) {
|
||||
return "User aborted query.";
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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";
|
||||
@@ -38,7 +36,7 @@ export function handleCachedDataMessage(message: any): void {
|
||||
* @returns
|
||||
*/
|
||||
export function sendCachedDataMessage<TResponseDataModel>(
|
||||
messageType: MessageTypes | FabricMessageTypes,
|
||||
messageType: MessageTypes,
|
||||
params: Object[],
|
||||
scope?: string,
|
||||
timeoutInMs?: number,
|
||||
@@ -97,18 +95,10 @@ const _sendMessage = (message: any): void => {
|
||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
||||
if (portalChildWindow === window) {
|
||||
// Current window is a child of portal, send message to portal window
|
||||
if (portalChildWindow.document.referrer) {
|
||||
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer);
|
||||
} else {
|
||||
Logger.logError("Iframe failed to send message to portal", "MessageHandler");
|
||||
}
|
||||
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
|
||||
} else {
|
||||
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
|
||||
if (portalChildWindow.location.origin) {
|
||||
portalChildWindow.postMessage(message, portalChildWindow.location.origin);
|
||||
} else {
|
||||
Logger.logError("Iframe failed to send message to data explorer", "MessageHandler");
|
||||
}
|
||||
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ export function queryDocuments(
|
||||
query: string,
|
||||
continuationToken?: string,
|
||||
): Promise<QueryResponse> {
|
||||
if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) {
|
||||
if (!useMongoProxyEndpoint("resourcelist")) {
|
||||
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export function queryDocuments(
|
||||
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
|
||||
}
|
||||
|
||||
const path = isResourceList ? "/resourcelist" : "/queryDocuments";
|
||||
const path = isResourceList ? "/resourcelist" : "";
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}${path}`, {
|
||||
@@ -550,49 +550,6 @@ export function deleteDocument_ToBeDeprecated(
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteDocuments(
|
||||
databaseId: string,
|
||||
collection: Collection,
|
||||
documentIds: DocumentId[],
|
||||
): Promise<{
|
||||
deletedCount: number;
|
||||
isAcknowledged: boolean;
|
||||
}> {
|
||||
const { databaseAccount } = userContext;
|
||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||
|
||||
const rids = documentIds.map((documentId) => documentId.id());
|
||||
|
||||
const params = {
|
||||
databaseID: databaseId,
|
||||
collectionID: collection.id(),
|
||||
resourceUrl: `${resourceEndpoint}`,
|
||||
resourceIDs: rids,
|
||||
subscriptionID: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseAccountName: databaseAccount.name,
|
||||
};
|
||||
const endpoint = getFeatureEndpointOrDefault("bulkdelete");
|
||||
|
||||
return window
|
||||
.fetch(`${endpoint}/bulkdelete`, {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
...authHeaders(),
|
||||
[HttpHeaders.contentType]: ContentType.applicationJson,
|
||||
},
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
return await errorHandling(response, "deleting documents", params);
|
||||
});
|
||||
}
|
||||
|
||||
export function createMongoCollectionWithProxy(
|
||||
params: DataModels.CreateCollectionParams,
|
||||
): Promise<DataModels.Collection> {
|
||||
@@ -715,27 +672,6 @@ 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)
|
||||
);
|
||||
}
|
||||
|
||||
export class ThrottlingError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This function throws most of the time except on Forbidden which is a bit strange
|
||||
// It causes problems for TypeScript understanding the types
|
||||
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
||||
@@ -745,14 +681,6 @@ async function errorHandling(response: Response, action: string, params: unknown
|
||||
if (response.status === HttpStatusCodes.Forbidden) {
|
||||
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
|
||||
return;
|
||||
} else if (
|
||||
response.status === HttpStatusCodes.BadRequest &&
|
||||
errorMessage.includes("Error=16500") &&
|
||||
errorMessage.includes("RetryAfterMs=")
|
||||
) {
|
||||
// If throttling is happening, Cosmos DB will return a 400 with a body of:
|
||||
// A write operation resulted in an error. Error=16500, RetryAfterMs=4, Details='Batch write error.
|
||||
throw new ThrottlingError(errorMessage);
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
@@ -760,3 +688,16 @@ 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 {
|
||||
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) &&
|
||||
[MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac].includes(configContext.MONGO_PROXY_ENDPOINT)
|
||||
);
|
||||
}
|
||||
|
||||
39
src/Common/PortalNotifications.ts
Normal file
39
src/Common/PortalNotifications.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
|
||||
const notificationsPath = () => {
|
||||
switch (configContext.platform) {
|
||||
case Platform.Hosted:
|
||||
return "/api/guest/notifications";
|
||||
case Platform.Portal:
|
||||
return "/api/notifications";
|
||||
default:
|
||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
|
||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||
databaseAccount.name
|
||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as DataModels.Notification[];
|
||||
};
|
||||
@@ -3,7 +3,8 @@ import * as _ from "underscore";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId";
|
||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { useDatabases } from "../Explorer/useDatabases";
|
||||
import { userContext } from "../UserContext";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
@@ -161,10 +162,10 @@ export class QueriesClient {
|
||||
{
|
||||
partitionKey: QueriesClient.PartitionKey,
|
||||
partitionKeyProperties: ["id"],
|
||||
} as IDocumentIdContainer,
|
||||
} as DocumentsTab,
|
||||
query,
|
||||
[query.queryName],
|
||||
);
|
||||
); // TODO: Remove DocumentId's dependency on DocumentsTab
|
||||
const options: any = { partitionKey: query.resourceId };
|
||||
return deleteDocument(queriesCollection, documentId)
|
||||
.then(
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import QueryError, { QueryErrorLocation, QueryErrorSeverity } from "Common/QueryError";
|
||||
|
||||
describe("QueryError.tryParse", () => {
|
||||
const testErrorLocationResolver = ({ start, end }: { start: number; end: number }) =>
|
||||
new QueryErrorLocation(
|
||||
{ offset: start, lineNumber: start, column: start },
|
||||
{ offset: end, lineNumber: end, column: end },
|
||||
);
|
||||
|
||||
it("handles a string error", () => {
|
||||
const error = "error";
|
||||
const result = QueryError.tryParse(error, testErrorLocationResolver);
|
||||
expect(result).toEqual([new QueryError("error", QueryErrorSeverity.Error)]);
|
||||
});
|
||||
|
||||
it("handles an error object", () => {
|
||||
const error = {
|
||||
message: "error",
|
||||
severity: "Warning",
|
||||
location: { start: 0, end: 1 },
|
||||
code: "code",
|
||||
};
|
||||
const result = QueryError.tryParse(error, testErrorLocationResolver);
|
||||
expect(result).toEqual([
|
||||
new QueryError(
|
||||
"error",
|
||||
QueryErrorSeverity.Warning,
|
||||
"code",
|
||||
new QueryErrorLocation({ offset: 0, lineNumber: 0, column: 0 }, { offset: 1, lineNumber: 1, column: 1 }),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles a JSON message without syntax errors", () => {
|
||||
const innerError = {
|
||||
code: "BadRequest",
|
||||
message: "Your query is bad, and you should feel bad",
|
||||
};
|
||||
const message = JSON.stringify(innerError);
|
||||
const outerError = {
|
||||
code: "BadRequest",
|
||||
message,
|
||||
};
|
||||
|
||||
const result = QueryError.tryParse(outerError, testErrorLocationResolver);
|
||||
expect(result).toEqual([
|
||||
new QueryError("Your query is bad, and you should feel bad", QueryErrorSeverity.Error, "BadRequest"),
|
||||
]);
|
||||
});
|
||||
|
||||
// Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message.
|
||||
it("handles single-nested error", () => {
|
||||
const errors = [
|
||||
{
|
||||
message: "error1",
|
||||
severity: "Warning",
|
||||
location: { start: 0, end: 1 },
|
||||
code: "code1",
|
||||
},
|
||||
{
|
||||
message: "error2",
|
||||
severity: "Error",
|
||||
location: { start: 2, end: 3 },
|
||||
code: "code2",
|
||||
},
|
||||
];
|
||||
const innerError = {
|
||||
code: "BadRequest",
|
||||
message: "Your query is bad, and you should feel bad",
|
||||
errors,
|
||||
};
|
||||
const message = JSON.stringify(innerError);
|
||||
const outerError = {
|
||||
code: "BadRequest",
|
||||
message,
|
||||
};
|
||||
|
||||
const result = QueryError.tryParse(outerError, testErrorLocationResolver);
|
||||
expect(result).toEqual([
|
||||
new QueryError(
|
||||
"error1",
|
||||
QueryErrorSeverity.Warning,
|
||||
"code1",
|
||||
new QueryErrorLocation({ offset: 0, lineNumber: 0, column: 0 }, { offset: 1, lineNumber: 1, column: 1 }),
|
||||
),
|
||||
new QueryError(
|
||||
"error2",
|
||||
QueryErrorSeverity.Error,
|
||||
"code2",
|
||||
new QueryErrorLocation({ offset: 2, lineNumber: 2, column: 2 }, { offset: 3, lineNumber: 3, column: 3 }),
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,247 +0,0 @@
|
||||
import { monaco } from "Explorer/LazyMonaco";
|
||||
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
|
||||
|
||||
export enum QueryErrorSeverity {
|
||||
Error = "Error",
|
||||
Warning = "Warning",
|
||||
}
|
||||
|
||||
export class QueryErrorLocation {
|
||||
constructor(
|
||||
public start: ErrorPosition,
|
||||
public end: ErrorPosition,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class ErrorPosition {
|
||||
constructor(
|
||||
public offset: number,
|
||||
public lineNumber?: number,
|
||||
public column?: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
// Maps severities to numbers for sorting.
|
||||
const severityMap: Record<QueryErrorSeverity, number> = {
|
||||
Error: 1,
|
||||
Warning: 0,
|
||||
};
|
||||
|
||||
export function compareSeverity(left: QueryErrorSeverity, right: QueryErrorSeverity): number {
|
||||
return severityMap[left] - severityMap[right];
|
||||
}
|
||||
|
||||
export function createMonacoErrorLocationResolver(
|
||||
editor: monaco.editor.IStandaloneCodeEditor,
|
||||
selection?: monaco.Selection,
|
||||
): (location: { start: number; end: number }) => QueryErrorLocation {
|
||||
return ({ start, end }) => {
|
||||
// Start and end are absolute offsets (character index) in the document.
|
||||
// But we need line numbers and columns for the monaco editor.
|
||||
// To get those, we use the editor's model to convert the offsets to positions.
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end));
|
||||
}
|
||||
|
||||
// If the error was found in a selection, adjust the start and end positions to be relative to the document.
|
||||
if (selection) {
|
||||
// Get the character index of the start of the selection.
|
||||
const selectionStartOffset = model.getOffsetAt(selection.getStartPosition());
|
||||
|
||||
// Adjust the start and end positions to be relative to the document.
|
||||
start = selectionStartOffset + start;
|
||||
end = selectionStartOffset + end;
|
||||
|
||||
// Now, when we resolve the positions, they will be relative to the document and appear in the correct location.
|
||||
}
|
||||
|
||||
const startPos = model.getPositionAt(start);
|
||||
const endPos = model.getPositionAt(end);
|
||||
return new QueryErrorLocation(
|
||||
new ErrorPosition(start, startPos.lineNumber, startPos.column),
|
||||
new ErrorPosition(end, endPos.lineNumber, endPos.column),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const createMonacoMarkersForQueryErrors = (errors: QueryError[]) => {
|
||||
if (!errors) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return errors
|
||||
.map((error): monaco.editor.IMarkerData => {
|
||||
// Validate that we have what we need to make a marker
|
||||
if (
|
||||
error.location === undefined ||
|
||||
error.location.start === undefined ||
|
||||
error.location.end === undefined ||
|
||||
error.location.start.lineNumber === undefined ||
|
||||
error.location.end.lineNumber === undefined ||
|
||||
error.location.start.column === undefined ||
|
||||
error.location.end.column === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
message: error.message,
|
||||
severity: error.getMonacoSeverity(),
|
||||
startLineNumber: error.location.start.lineNumber,
|
||||
startColumn: error.location.start.column,
|
||||
endLineNumber: error.location.end.lineNumber,
|
||||
endColumn: error.location.end.column,
|
||||
};
|
||||
})
|
||||
.filter((marker) => !!marker);
|
||||
};
|
||||
|
||||
export interface ErrorEnrichment {
|
||||
title?: string;
|
||||
message: string;
|
||||
learnMoreUrl?: string;
|
||||
}
|
||||
|
||||
const REPLACEMENT_MESSAGES: Record<string, (original: string) => string> = {
|
||||
OPERATION_RU_LIMIT_EXCEEDED: (original) => {
|
||||
if (ruThresholdEnabled()) {
|
||||
const threshold = getRUThreshold();
|
||||
return `Query exceeded the Request Unit (RU) limit of ${threshold} RUs. You can change this limit in Data Explorer settings.`;
|
||||
}
|
||||
return original;
|
||||
},
|
||||
};
|
||||
|
||||
const HELP_LINKS: Record<string, string> = {
|
||||
OPERATION_RU_LIMIT_EXCEEDED:
|
||||
"https://learn.microsoft.com/en-us/azure/cosmos-db/data-explorer#configure-request-unit-threshold",
|
||||
};
|
||||
|
||||
export default class QueryError {
|
||||
message: string;
|
||||
helpLink?: string;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
public severity: QueryErrorSeverity,
|
||||
public code?: string,
|
||||
public location?: QueryErrorLocation,
|
||||
helpLink?: string,
|
||||
) {
|
||||
// Automatically replace the message with a more Data Explorer-specific message if we have for this error code.
|
||||
this.message = REPLACEMENT_MESSAGES[code] ? REPLACEMENT_MESSAGES[code](message) : message;
|
||||
|
||||
// Automatically set the help link if we have one for this error code.
|
||||
this.helpLink = helpLink ?? HELP_LINKS[code];
|
||||
}
|
||||
|
||||
getMonacoSeverity(): monaco.MarkerSeverity {
|
||||
// It's very difficult to use the monaco.MarkerSeverity enum from here, so we'll just use the numbers directly.
|
||||
// See: https://microsoft.github.io/monaco-editor/typedoc/enums/MarkerSeverity.html
|
||||
switch (this.severity) {
|
||||
case QueryErrorSeverity.Error:
|
||||
return 8;
|
||||
case QueryErrorSeverity.Warning:
|
||||
return 4;
|
||||
default:
|
||||
return 2; // Info
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempts to parse a query error from a string or object.
|
||||
*
|
||||
* @param error The error to parse.
|
||||
* @returns An array of query errors if the error could be parsed, or null otherwise.
|
||||
*/
|
||||
static tryParse(
|
||||
error: unknown,
|
||||
locationResolver?: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError[] {
|
||||
locationResolver =
|
||||
locationResolver ||
|
||||
(({ start, end }) => new QueryErrorLocation(new ErrorPosition(start), new ErrorPosition(end)));
|
||||
const errors = QueryError.tryParseObject(error, locationResolver);
|
||||
if (errors !== null) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const errorMessage = error as string;
|
||||
|
||||
// Map some well known messages to richer errors
|
||||
const knownError = knownErrors[errorMessage];
|
||||
if (knownError) {
|
||||
return [knownError];
|
||||
} else {
|
||||
return [new QueryError(errorMessage, QueryErrorSeverity.Error)];
|
||||
}
|
||||
}
|
||||
|
||||
static read(
|
||||
error: unknown,
|
||||
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError | null {
|
||||
if (typeof error !== "object" || error === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = "message" in error && typeof error.message === "string" ? error.message : undefined;
|
||||
if (!message) {
|
||||
return null; // Invalid error (no message).
|
||||
}
|
||||
|
||||
const severity =
|
||||
"severity" in error && typeof error.severity === "string"
|
||||
? (error.severity as QueryErrorSeverity)
|
||||
: QueryErrorSeverity.Error;
|
||||
const location =
|
||||
"location" in error && typeof error.location === "object"
|
||||
? locationResolver(error.location as { start: number; end: number })
|
||||
: undefined;
|
||||
const code = "code" in error && typeof error.code === "string" ? error.code : undefined;
|
||||
return new QueryError(message, severity, code, location);
|
||||
}
|
||||
|
||||
private static tryParseObject(
|
||||
error: unknown,
|
||||
locationResolver: (location: { start: number; end: number }) => QueryErrorLocation,
|
||||
): QueryError[] | null {
|
||||
let message: string | undefined;
|
||||
if (typeof error === "object" && "message" in error && typeof error.message === "string") {
|
||||
message = error.message;
|
||||
} else {
|
||||
// Unsupported error format.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign to a new variable because of a TypeScript flow typing quirk, see below.
|
||||
if (message.startsWith("Message: ")) {
|
||||
// Reassigning this to 'error' restores the original type of 'error', which is 'unknown'.
|
||||
// So we use a separate variable to avoid this.
|
||||
message = message.substring("Message: ".length);
|
||||
}
|
||||
|
||||
const lines = message.split("\n");
|
||||
message = lines[0].trim();
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(message);
|
||||
} catch (e) {
|
||||
// The message doesn't contain a nested error.
|
||||
return [QueryError.read(error, locationResolver)];
|
||||
}
|
||||
|
||||
if (typeof parsed === "object") {
|
||||
if ("errors" in parsed && Array.isArray(parsed.errors)) {
|
||||
return parsed.errors.map((e) => QueryError.read(e, locationResolver)).filter((e) => e !== null);
|
||||
}
|
||||
return [QueryError.read(parsed, locationResolver)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const knownErrors: Record<string, QueryError> = {
|
||||
"User aborted query.": new QueryError("User aborted query.", QueryErrorSeverity.Warning),
|
||||
};
|
||||
90
src/Common/ResourceTreeContainer.tsx
Normal file
90
src/Common/ResourceTreeContainer.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
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={`Edit ${entityProperty} entity`}
|
||||
alt="editEntity"
|
||||
onClick={onEditEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPress}
|
||||
@@ -156,7 +156,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={DeleteIcon}
|
||||
alt={`Delete ${entityProperty} entity`}
|
||||
alt="delete entity"
|
||||
id="deleteEntity"
|
||||
onClick={onDeleteEntity}
|
||||
tabIndex={0}
|
||||
|
||||
@@ -3,12 +3,11 @@ import * as React from "react";
|
||||
|
||||
export interface TooltipProps {
|
||||
children: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => {
|
||||
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
|
||||
return (
|
||||
<span className={className}>
|
||||
<span>
|
||||
<TooltipHost content={children}>
|
||||
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
|
||||
{
|
||||
Object {
|
||||
"endpoint": "http://localhost/proxy",
|
||||
"headers": {
|
||||
"headers": Object {
|
||||
"x-ms-proxy-target": "http://localhost",
|
||||
},
|
||||
"path": "/dbs/foo",
|
||||
@@ -11,9 +11,9 @@ exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`]
|
||||
`;
|
||||
|
||||
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
|
||||
{
|
||||
Object {
|
||||
"endpoint": "http://localhost/proxy",
|
||||
"headers": {
|
||||
"headers": Object {
|
||||
"x-ms-proxy-target": "baz",
|
||||
},
|
||||
"path": "/dbs/foo",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`nextPage returns results for the next page 1`] = `
|
||||
{
|
||||
Object {
|
||||
"activityId": "foo",
|
||||
"documents": [],
|
||||
"documents": Array [],
|
||||
"firstItemIndex": 11,
|
||||
"hasMoreResults": false,
|
||||
"headers": {},
|
||||
"headers": Object {},
|
||||
"itemCount": 0,
|
||||
"lastItemIndex": 10,
|
||||
"requestCharge": 1,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getCommonQueryOptions builds the correct default options objects 1`] = `
|
||||
{
|
||||
"disableNonStreamingOrderByQuery": true,
|
||||
Object {
|
||||
"enableScanInQuery": true,
|
||||
"forceQueryPlan": true,
|
||||
"maxDegreeOfParallelism": 0,
|
||||
@@ -12,8 +11,7 @@ exports[`getCommonQueryOptions builds the correct default options objects 1`] =
|
||||
`;
|
||||
|
||||
exports[`getCommonQueryOptions reads from localStorage 1`] = `
|
||||
{
|
||||
"disableNonStreamingOrderByQuery": true,
|
||||
Object {
|
||||
"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,9 +96,6 @@ 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: {
|
||||
@@ -269,7 +266,6 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
|
||||
indexingPolicy: params.indexingPolicy || undefined,
|
||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
||||
analyticalStorageTtl: params.analyticalStorageTtl,
|
||||
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
|
||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||
const collectionOptions: RequestOptions = {};
|
||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { useDatabases } from "../../Explorer/useDatabases";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getDatabaseName } from "../../Utils/APITypeUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
MongoDBDatabaseCreateUpdateParameters,
|
||||
SqlDatabaseCreateUpdateParameters,
|
||||
} from "../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
@@ -152,18 +152,8 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
|
||||
createBody.throughput = params.offerThroughput;
|
||||
}
|
||||
}
|
||||
let response: DatabaseResponse;
|
||||
try {
|
||||
response = await client().databases.create(createBody);
|
||||
} catch (error) {
|
||||
if (error.message.includes("Shared throughput database creation is not supported for serverless accounts")) {
|
||||
createBody.maxThroughput = undefined;
|
||||
createBody.throughput = undefined;
|
||||
response = await client().databases.create(createBody);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const response: DatabaseResponse = await client().databases.create(createBody);
|
||||
return response.resource;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,21 +122,14 @@ const pollDataTransferJobOperation = async (
|
||||
|
||||
updateDataTransferJob(body);
|
||||
|
||||
if (status === "Cancelled") {
|
||||
removeFromPolling(jobName);
|
||||
clearMessage && clearMessage();
|
||||
const cancelMessage = `Data transfer job ${jobName} cancelled`;
|
||||
NotificationConsoleUtils.logConsoleError(cancelMessage);
|
||||
throw new AbortError(cancelMessage);
|
||||
}
|
||||
if (status === "Failed" || status === "Faulted") {
|
||||
if (status === "Cancelled" || status === "Failed" || status === "Faulted") {
|
||||
removeFromPolling(jobName);
|
||||
const errorMessage = body?.properties?.error
|
||||
? JSON.stringify(body?.properties?.error)
|
||||
: "Operation could not be completed";
|
||||
const error = new Error(errorMessage);
|
||||
clearMessage && clearMessage();
|
||||
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`);
|
||||
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`);
|
||||
throw new AbortError(error);
|
||||
}
|
||||
if (status === "Completed") {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BulkOperationType, OperationInput } from "@azure/cosmos";
|
||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
@@ -25,66 +24,3 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
||||
clearMessage();
|
||||
}
|
||||
};
|
||||
|
||||
export interface IBulkDeleteResult {
|
||||
documentId: DocumentId;
|
||||
requestCharge: number;
|
||||
statusCode: number;
|
||||
retryAfterMilliseconds?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk delete documents
|
||||
* @param collection
|
||||
* @param documentId
|
||||
* @returns array of results and status codes
|
||||
*/
|
||||
export const deleteDocuments = async (
|
||||
collection: CollectionBase,
|
||||
documentIds: DocumentId[],
|
||||
): Promise<IBulkDeleteResult[]> => {
|
||||
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((bulkResults) => {
|
||||
return bulkResults.map((bulkResult, index) => {
|
||||
const documentId = documentIdsChunk[index];
|
||||
return { ...bulkResult, documentId };
|
||||
});
|
||||
});
|
||||
promiseArray.push(promise);
|
||||
}
|
||||
|
||||
const allResult = await Promise.all(promiseArray);
|
||||
const flatAllResult = Array.prototype.concat.apply([], allResult);
|
||||
return flatAllResult;
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
"DeleteDocuments",
|
||||
`Error while deleting ${documentIds.length} ${getEntityName(documentIds.length > 1)}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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";
|
||||
@@ -27,6 +26,5 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
|
||||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
||||
Queries.itemsPerPage;
|
||||
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
||||
options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled();
|
||||
return options;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CosmosClient } from "@azure/cosmos";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import { userContext } from "UserContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
@@ -30,6 +31,7 @@ export async function readCollectionInternal(
|
||||
collectionId: string,
|
||||
): Promise<DataModels.Collection> {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
|
||||
try {
|
||||
const response = await cosmosClient.database(databaseId).container(collectionId).read();
|
||||
collection = response.resource as DataModels.Collection;
|
||||
@@ -37,5 +39,6 @@ export async function readCollectionInternal(
|
||||
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return collection;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
BackendApi,
|
||||
CassandraProxyEndpoints,
|
||||
JunoEndpoints,
|
||||
MongoProxyEndpoints,
|
||||
PortalBackendEndpoints,
|
||||
} from "Common/Constants";
|
||||
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
||||
import {
|
||||
allowedAadEndpoints,
|
||||
allowedArcadiaEndpoints,
|
||||
@@ -42,19 +36,15 @@ 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;
|
||||
PORTAL_BACKEND_ENDPOINT: string;
|
||||
NEW_BACKEND_APIS?: BackendApi[];
|
||||
MONGO_BACKEND_ENDPOINT?: string;
|
||||
MONGO_PROXY_ENDPOINT: string;
|
||||
MONGO_PROXY_ENDPOINT?: string;
|
||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
||||
NEW_MONGO_APIS?: string[];
|
||||
CASSANDRA_PROXY_ENDPOINT: string;
|
||||
CASSANDRA_PROXY_ENDPOINT?: string;
|
||||
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
|
||||
NEW_CASSANDRA_APIS?: string[];
|
||||
PROXY_PATH?: string;
|
||||
JUNO_ENDPOINT: string;
|
||||
@@ -85,7 +75,6 @@ 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/",
|
||||
@@ -95,30 +84,30 @@ 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
|
||||
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
||||
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||
NEW_MONGO_APIS: [
|
||||
"resourcelist",
|
||||
"queryDocuments",
|
||||
"createDocument",
|
||||
"readDocument",
|
||||
"updateDocument",
|
||||
"deleteDocument",
|
||||
"createCollectionWithProxy",
|
||||
"legacyMongoShell",
|
||||
// "bulkdelete",
|
||||
// "resourcelist",
|
||||
// "createDocument",
|
||||
// "readDocument",
|
||||
// "updateDocument",
|
||||
// "deleteDocument",
|
||||
// "createCollectionWithProxy",
|
||||
],
|
||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
||||
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
|
||||
NEW_CASSANDRA_APIS: [
|
||||
// "postQuery",
|
||||
// "createOrDelete",
|
||||
// "getKeys",
|
||||
// "getSchema",
|
||||
],
|
||||
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
|
||||
isTerminalEnabled: false,
|
||||
isPhoenixEnabled: false,
|
||||
};
|
||||
@@ -196,9 +185,6 @@ 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,8 +1,7 @@
|
||||
export interface QueryRequestOptions {
|
||||
$skipToken?: string;
|
||||
$top?: number;
|
||||
$allowPartialScopes: boolean;
|
||||
subscriptions?: string[];
|
||||
subscriptions: string[];
|
||||
}
|
||||
|
||||
export interface QueryResponse {
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
import { FabricMessageTypes } from "./FabricMessageTypes";
|
||||
import { MessageTypes } from "./MessageTypes";
|
||||
|
||||
// This is the current version of these messages
|
||||
export const DATA_EXPLORER_RPC_VERSION = "3";
|
||||
export const DATA_EXPLORER_RPC_VERSION = "2";
|
||||
|
||||
// Data Explorer to Fabric
|
||||
export type DataExploreMessageV3 =
|
||||
|
||||
// TODO Remove when upgrading to Fabric v2
|
||||
export type DataExploreMessageV1 =
|
||||
| "ready"
|
||||
| {
|
||||
type: FabricMessageTypes.Ready;
|
||||
id: string;
|
||||
params: [string]; // version
|
||||
}
|
||||
| {
|
||||
type: FabricMessageTypes.GetAuthorizationToken;
|
||||
type: MessageTypes.GetAuthorizationToken;
|
||||
id: string;
|
||||
params: GetCosmosTokenMessageOptions[];
|
||||
}
|
||||
| {
|
||||
type: FabricMessageTypes.GetAllResourceTokens;
|
||||
type: MessageTypes.GetAllResourceTokens;
|
||||
id: string;
|
||||
};
|
||||
// -----------------------------
|
||||
|
||||
export type DataExploreMessageV2 =
|
||||
| {
|
||||
type: MessageTypes.Ready;
|
||||
id: string;
|
||||
params: [string]; // version
|
||||
}
|
||||
| {
|
||||
type: MessageTypes.GetAuthorizationToken;
|
||||
id: string;
|
||||
params: GetCosmosTokenMessageOptions[];
|
||||
}
|
||||
| {
|
||||
type: MessageTypes.GetAllResourceTokens;
|
||||
id: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CapacityMode, ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
|
||||
export interface ArmEntity {
|
||||
id: string;
|
||||
@@ -35,7 +35,6 @@ export interface DatabaseAccountExtendedProperties {
|
||||
ipRules?: IpRule[];
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
capacityMode?: CapacityMode;
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
postgresqlEndpoint?: string;
|
||||
publicNetworkAccess?: string;
|
||||
@@ -158,7 +157,6 @@ export interface Collection extends Resource {
|
||||
changeFeedPolicy?: ChangeFeedPolicy;
|
||||
analyticalStorageTtl?: number;
|
||||
geospatialConfig?: GeospatialConfig;
|
||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||
schema?: ISchema;
|
||||
requestSchema?: () => void;
|
||||
computedProperties?: ComputedProperties;
|
||||
@@ -196,14 +194,8 @@ export interface IndexingPolicy {
|
||||
indexingMode: "consistent" | "lazy" | "none";
|
||||
includedPaths: any;
|
||||
excludedPaths: any;
|
||||
compositeIndexes?: any[];
|
||||
spatialIndexes?: any[];
|
||||
vectorIndexes?: VectorIndex[];
|
||||
}
|
||||
|
||||
export interface VectorIndex {
|
||||
path: string;
|
||||
type: "flat" | "diskANN" | "quantizedFlat";
|
||||
compositeIndexes?: any;
|
||||
spatialIndexes?: any;
|
||||
}
|
||||
|
||||
export interface ComputedProperty {
|
||||
@@ -341,18 +333,6 @@ 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 {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* 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 "Contracts/FabricMessageTypes";
|
||||
import { AuthorizationToken } from "./MessageTypes";
|
||||
|
||||
// This is the version of these messages
|
||||
export const FABRIC_RPC_VERSION = "2";
|
||||
@@ -53,7 +53,6 @@ export type FabricMessageV2 =
|
||||
id: string;
|
||||
message: {
|
||||
connectionId: string;
|
||||
isVisible: boolean;
|
||||
};
|
||||
}
|
||||
| {
|
||||
@@ -73,7 +72,7 @@ export type FabricMessageV2 =
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: "explorerVisible";
|
||||
type: "setToolbarStatus";
|
||||
message: {
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/**
|
||||
* Messaging types used with Data Explorer <-> Portal communication,
|
||||
* Hosted <-> Explorer communication
|
||||
* Hosted <-> Explorer communication and Data Explorer -> Fabric 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,
|
||||
@@ -44,9 +43,13 @@ export enum MessageTypes {
|
||||
DisplayNPSSurvey,
|
||||
OpenVCoreMongoNetworkingBlade,
|
||||
OpenVCoreMongoConnectionStringsBlade,
|
||||
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.
|
||||
GetAuthorizationToken, // Data Explorer -> Fabric
|
||||
GetAllResourceTokens, // Data Explorer -> Fabric
|
||||
Ready, // Data Explorer -> Fabric
|
||||
OpenCESCVAFeedbackBlade,
|
||||
ActivateTab,
|
||||
}
|
||||
|
||||
export interface AuthorizationToken {
|
||||
XDate: string;
|
||||
PrimaryReadWriteToken: string;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ export interface Database extends TreeNode {
|
||||
openAddCollection(database: Database, event: MouseEvent): void;
|
||||
onSettingsClick: () => void;
|
||||
loadOffer(): Promise<void>;
|
||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
export interface CollectionBase extends TreeNode {
|
||||
@@ -175,11 +176,6 @@ 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;
|
||||
@@ -190,6 +186,8 @@ export interface Collection extends CollectionBase {
|
||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||
|
||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,9 +324,9 @@ export enum DocumentExplorerState {
|
||||
noDocumentSelected,
|
||||
newDocumentValid,
|
||||
newDocumentInvalid,
|
||||
existingDocumentNoEdits,
|
||||
existingDocumentDirtyValid,
|
||||
existingDocumentDirtyInvalid,
|
||||
exisitingDocumentNoEdits,
|
||||
exisitingDocumentDirtyValid,
|
||||
exisitingDocumentDirtyInvalid,
|
||||
}
|
||||
|
||||
export enum IndexingPolicyEditorState {
|
||||
@@ -341,9 +339,9 @@ export enum IndexingPolicyEditorState {
|
||||
export enum ScriptEditorState {
|
||||
newInvalid,
|
||||
newValid,
|
||||
existingNoEdits,
|
||||
existingDirtyValid,
|
||||
existingDirtyInvalid,
|
||||
exisitingNoEdits,
|
||||
exisitingDirtyValid,
|
||||
exisitingDirtyInvalid,
|
||||
}
|
||||
|
||||
export enum CollectionTabKind {
|
||||
@@ -389,7 +387,6 @@ export interface DataExplorerInputsFrame {
|
||||
dnsSuffix?: string;
|
||||
serverId?: string;
|
||||
extensionEndpoint?: string;
|
||||
portalBackendEndpoint?: string;
|
||||
mongoProxyEndpoint?: string;
|
||||
cassandraProxyEndpoint?: string;
|
||||
subscriptionType?: SubscriptionType;
|
||||
@@ -422,7 +419,6 @@ 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 = jest.spyOn(heatmap, "_getChartSettings");
|
||||
const _getChartSettings = spyOn<any>(heatmap, "_getChartSettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getChartSettings).toHaveBeenCalled();
|
||||
expect(_getChartSettings.calls.any()).toBe(true);
|
||||
});
|
||||
|
||||
it("should call _getLayoutSettings when drawHeatmap is invoked", () => {
|
||||
const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings");
|
||||
const _getLayoutSettings = spyOn<any>(heatmap, "_getLayoutSettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getLayoutSettings).toHaveBeenCalled();
|
||||
expect(_getLayoutSettings.calls.any()).toBe(true);
|
||||
});
|
||||
|
||||
it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => {
|
||||
const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings");
|
||||
const _getChartDisplaySettings = spyOn<any>(heatmap, "_getChartDisplaySettings");
|
||||
heatmap.drawHeatmap();
|
||||
expect(_getChartDisplaySettings).toHaveBeenCalled();
|
||||
expect(_getChartDisplaySettings.calls.any()).toBe(true);
|
||||
});
|
||||
|
||||
it("drawHeatmap should render a Heatmap inside the div element", () => {
|
||||
|
||||
@@ -96,8 +96,7 @@ export class Heatmap {
|
||||
return output;
|
||||
}
|
||||
|
||||
// public for testing purposes
|
||||
public _getChartSettings(): ChartSettings[] {
|
||||
private _getChartSettings(): ChartSettings[] {
|
||||
return [
|
||||
{
|
||||
z: this._chartData.dataPoints,
|
||||
@@ -132,8 +131,7 @@ export class Heatmap {
|
||||
];
|
||||
}
|
||||
|
||||
// public for testing purposes
|
||||
public _getLayoutSettings(): LayoutSettings {
|
||||
private _getLayoutSettings(): LayoutSettings {
|
||||
return {
|
||||
margin: {
|
||||
l: 40,
|
||||
@@ -179,8 +177,7 @@ export class Heatmap {
|
||||
};
|
||||
}
|
||||
|
||||
// public for testing purposes
|
||||
public _getChartDisplaySettings(): DisplaySettings {
|
||||
private _getChartDisplaySettings(): DisplaySettings {
|
||||
return {
|
||||
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
|
||||
responsive: true,*/
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -20,6 +19,7 @@ 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,10 +41,6 @@ export interface DatabaseContextMenuButtonParams {
|
||||
* New resource tree (in ReactJS)
|
||||
*/
|
||||
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items: TreeNodeMenuItem[] = [
|
||||
{
|
||||
iconSrc: AddCollectionIcon,
|
||||
@@ -104,16 +100,6 @@ export const createCollectionContextMenuButton = (
|
||||
});
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isShellEnabled && userContext.apiType === "Cassandra") {
|
||||
items.push({
|
||||
iconSrc: HostedTerminalIcon,
|
||||
onClick: () => {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||
},
|
||||
label: "Open Cassandra Shell",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
configContext.platform !== Platform.Fabric &&
|
||||
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DirectionalHint, Icon, Label, Stack, TooltipHost } from "@fluentui/react";
|
||||
import { Icon, Label, Stack } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||
@@ -8,7 +8,6 @@ export interface CollapsibleSectionProps {
|
||||
isExpandedByDefault: boolean;
|
||||
onExpand?: () => void;
|
||||
children: JSX.Element;
|
||||
tooltipContent?: string | JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
export interface CollapsibleSectionState {
|
||||
@@ -27,8 +26,8 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
public componentDidUpdate(_prevProps: CollapsibleSectionProps, prevState: CollapsibleSectionState): void {
|
||||
if (!prevState.isExpanded && this.state.isExpanded && this.props.onExpand) {
|
||||
public componentDidUpdate(): void {
|
||||
if (this.state.isExpanded && this.props.onExpand) {
|
||||
this.props.onExpand();
|
||||
}
|
||||
}
|
||||
@@ -44,7 +43,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
className={"collapsibleSection"}
|
||||
className="collapsibleSection"
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
tokens={accordionStackTokens}
|
||||
@@ -56,19 +55,6 @@ 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,14 +1,22 @@
|
||||
/**
|
||||
* React component for Command button component.
|
||||
*/
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import * as React from "react";
|
||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as StringUtils from "../../../Utils/StringUtils";
|
||||
|
||||
/**
|
||||
* Options for this component
|
||||
*/
|
||||
export interface CommandButtonComponentProps {
|
||||
/**
|
||||
* font icon name for the button
|
||||
*/
|
||||
iconName?: string;
|
||||
|
||||
/**
|
||||
* image source for the button icon
|
||||
*/
|
||||
@@ -22,7 +30,7 @@ export interface CommandButtonComponentProps {
|
||||
/**
|
||||
* Click handler for command button click
|
||||
*/
|
||||
onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent, container: Explorer) => void;
|
||||
onCommandClick: (e: React.SyntheticEvent) => void;
|
||||
|
||||
/**
|
||||
* Label for the button
|
||||
@@ -99,15 +107,162 @@ 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> {
|
||||
private dropdownElt: HTMLElement;
|
||||
private expandButtonElt: HTMLElement;
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
if (!this.dropdownElt || !this.expandButtonElt) {
|
||||
return;
|
||||
}
|
||||
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
|
||||
}
|
||||
|
||||
private onKeyPress(event: React.KeyboardEvent): boolean {
|
||||
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
|
||||
this.commandClickCallback && this.commandClickCallback(event);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
|
||||
if (event.keyCode === KeyCodes.DownArrow) {
|
||||
$(this.dropdownElt).hide();
|
||||
$(this.dropdownElt).show().focus();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === KeyCodes.UpArrow) {
|
||||
$(this.dropdownElt).hide();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private getCommandButtonId(): string {
|
||||
if (this.props.id) {
|
||||
return this.props.id;
|
||||
} else {
|
||||
return `commandButton-${StringUtils.stripSpacesFromString(this.props.commandButtonLabel)}`;
|
||||
}
|
||||
}
|
||||
|
||||
public static renderButton(options: CommandButtonComponentProps, key?: string): JSX.Element {
|
||||
return <CommandButtonComponent key={key} {...options} />;
|
||||
}
|
||||
|
||||
private commandClickCallback(e: React.SyntheticEvent): void {
|
||||
if (this.props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Query component's parent, not document
|
||||
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
|
||||
if (el) {
|
||||
el.style.display = "none";
|
||||
}
|
||||
this.props.onCommandClick(e);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
commandButtonClicked: this.props.commandButtonLabel,
|
||||
});
|
||||
}
|
||||
|
||||
private renderChildren(): JSX.Element {
|
||||
if (!this.props.children || this.props.children.length < 1) {
|
||||
return <React.Fragment />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="commandExpand"
|
||||
tabIndex={0}
|
||||
ref={(ref: HTMLElement) => {
|
||||
this.expandButtonElt = ref;
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
|
||||
>
|
||||
<div className="commandDropdownLauncher">
|
||||
<span className="partialSplitter" />
|
||||
<span className="expandDropdown">
|
||||
<img src={CollapseChevronDownIcon} />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="commandDropdownContainer"
|
||||
ref={(ref: HTMLElement) => {
|
||||
this.dropdownElt = ref;
|
||||
}}
|
||||
>
|
||||
<div className="commandDropdown">
|
||||
{this.props.children.map((c: CommandButtonComponentProps, index: number): JSX.Element => {
|
||||
return CommandButtonComponent.renderButton(c, `${index}`);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public static renderLabel(
|
||||
props: CommandButtonComponentProps,
|
||||
key?: string,
|
||||
refct?: (input: HTMLElement) => void,
|
||||
): JSX.Element {
|
||||
if (!props.commandButtonLabel) {
|
||||
return <React.Fragment />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="commandLabel" key={key} ref={refct}>
|
||||
{props.commandButtonLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
let mainClassName = "commandButtonComponent";
|
||||
if (this.props.disabled) {
|
||||
mainClassName += " commandDisabled";
|
||||
}
|
||||
if (this.props.isSelected) {
|
||||
mainClassName += " selectedButton";
|
||||
}
|
||||
|
||||
let contentClassName = "commandContent";
|
||||
if (this.props.children && this.props.children.length > 0) {
|
||||
contentClassName += " hasHiddenItems";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="commandButtonReact">
|
||||
<span
|
||||
className={mainClassName}
|
||||
role="menuitem"
|
||||
tabIndex={this.props.tabIndex}
|
||||
onKeyPress={(e: React.KeyboardEvent<HTMLSpanElement>) => this.onKeyPress(e)}
|
||||
title={this.props.tooltipText}
|
||||
id={this.getCommandButtonId()}
|
||||
aria-disabled={this.props.disabled}
|
||||
aria-haspopup={this.props.hasPopup}
|
||||
aria-label={this.props.ariaLabel}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
|
||||
>
|
||||
<div className={contentClassName}>
|
||||
<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />
|
||||
{CommandButtonComponent.renderLabel(this.props)}
|
||||
</div>
|
||||
</span>
|
||||
{this.props.children && this.renderChildren()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface DialogState {
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean,
|
||||
) => void;
|
||||
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
|
||||
showOkModalDialog: (title: string, subText: string) => void;
|
||||
}
|
||||
|
||||
export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
@@ -83,7 +83,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
textFieldProps,
|
||||
primaryButtonDisabled,
|
||||
}),
|
||||
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps): void =>
|
||||
showOkModalDialog: (title: string, subText: string): void =>
|
||||
get().openDialog({
|
||||
isModal: true,
|
||||
title,
|
||||
@@ -94,7 +94,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
get().closeDialog();
|
||||
},
|
||||
onSecondaryButtonClick: undefined,
|
||||
linkProps,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -3,37 +3,6 @@ import * as React from "react";
|
||||
import { loadMonaco, monaco } from "../../LazyMonaco";
|
||||
// import "./EditorReact.less";
|
||||
|
||||
// In development, add a function to window to allow us to get the editor instance for a given element
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const win = window as any;
|
||||
win._monaco_getEditorForElement =
|
||||
win._monaco_getEditorForElement ||
|
||||
((element: HTMLElement) => {
|
||||
const editorId = element.dataset["monacoEditorId"];
|
||||
if (!editorId || !win.__monaco_editors || typeof win.__monaco_editors !== "object") {
|
||||
return null;
|
||||
}
|
||||
return win.__monaco_editors[editorId];
|
||||
});
|
||||
|
||||
win._monaco_getEditorContentForElement =
|
||||
win._monaco_getEditorContentForElement ||
|
||||
((element: HTMLElement) => {
|
||||
const editor = win._monaco_getEditorForElement(element);
|
||||
return editor ? editor.getValue() : null;
|
||||
});
|
||||
|
||||
win._monaco_setEditorContentForElement =
|
||||
win._monaco_setEditorContentForElement ||
|
||||
((element: HTMLElement, text: string) => {
|
||||
const editor = win._monaco_getEditorForElement(element);
|
||||
if (editor) {
|
||||
editor.setValue(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface EditorReactStates {
|
||||
showEditor: boolean;
|
||||
}
|
||||
@@ -42,7 +11,7 @@ export interface EditorReactProps {
|
||||
content: string;
|
||||
isReadOnly: boolean;
|
||||
ariaLabel: string; // Sets what will be read to the user to define the control
|
||||
onContentSelected?: (selectedContent: string, selection: monaco.Selection) => void; // Called when text is selected
|
||||
onContentSelected?: (selectedContent: string) => void; // Called when text is selected
|
||||
onContentChanged?: (newContent: string) => void; // Called when text is changed
|
||||
theme?: string; // Monaco editor theme
|
||||
wordWrap?: monaco.editor.IEditorOptions["wordWrap"];
|
||||
@@ -51,38 +20,13 @@ 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;
|
||||
public editor: monaco.editor.IStandaloneCodeEditor;
|
||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||
private selectionListener: monaco.IDisposable;
|
||||
monacoApi: {
|
||||
default: typeof monaco;
|
||||
Emitter: typeof monaco.Emitter;
|
||||
MarkerTag: typeof monaco.MarkerTag;
|
||||
MarkerSeverity: typeof monaco.MarkerSeverity;
|
||||
CancellationTokenSource: typeof monaco.CancellationTokenSource;
|
||||
Uri: typeof monaco.Uri;
|
||||
KeyCode: typeof monaco.KeyCode;
|
||||
KeyMod: typeof monaco.KeyMod;
|
||||
Position: typeof monaco.Position;
|
||||
Range: typeof monaco.Range;
|
||||
Selection: typeof monaco.Selection;
|
||||
SelectionDirection: typeof monaco.SelectionDirection;
|
||||
Token: typeof monaco.Token;
|
||||
editor: typeof monaco.editor;
|
||||
languages: typeof monaco.languages;
|
||||
};
|
||||
|
||||
public constructor(props: EditorReactProps) {
|
||||
super(props);
|
||||
@@ -102,28 +46,10 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
public componentDidUpdate(previous: EditorReactProps) {
|
||||
if (this.props.content !== previous.content) {
|
||||
this.editor?.setValue(this.props.content);
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -133,12 +59,9 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!this.state.showEditor && (
|
||||
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
|
||||
)}
|
||||
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />}
|
||||
<div
|
||||
data-test="EditorReact/Host/Unloaded"
|
||||
className={this.props.className || "jsonEditor"}
|
||||
className="jsonEditor"
|
||||
style={this.props.monacoContainerStyles}
|
||||
ref={(elt: HTMLElement) => this.setRef(elt)}
|
||||
/>
|
||||
@@ -148,26 +71,9 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
|
||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||
this.editor = editor;
|
||||
this.rootNode.dataset["test"] = "EditorReact/Host/Loaded";
|
||||
|
||||
// In development, we want to be able to access the editor instance from the console
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
this.rootNode.dataset["monacoEditorId"] = this.editor.getId();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const win = window as any;
|
||||
|
||||
win["__monaco_editors"] = win["__monaco_editors"] || {};
|
||||
win["__monaco_editors"][this.editor.getId()] = this.editor;
|
||||
}
|
||||
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||
// Hooking the model's onDidChangeContent event because of some event ordering issues.
|
||||
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
|
||||
// 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(() => {
|
||||
queryEditorModel.onDidChangeContent(() => {
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
this.props.onContentChanged(queryEditorModel.getValue());
|
||||
});
|
||||
@@ -177,27 +83,10 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
this.selectionListener = this.editor.onDidChangeCursorSelection(
|
||||
(event: monaco.editor.ICursorSelectionChangedEvent) => {
|
||||
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection);
|
||||
this.props.onContentSelected(selectedContent, event.selection);
|
||||
this.props.onContentSelected(selectedContent);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.enableWordWrapContextMenuItem) {
|
||||
editor.addAction({
|
||||
// An unique identifier of the contributed action.
|
||||
id: "wordwrap",
|
||||
label: "Toggle Word Wrap",
|
||||
contextMenuGroupId: EditorReact.VIEWING_OPTIONS_GROUP_ID,
|
||||
contextMenuOrder: 1,
|
||||
// Method that will be executed when the action is triggered.
|
||||
// @param editor The editor instance is passed in as a convenience
|
||||
run: (ed) => {
|
||||
const newOption = ed.getOption(this.monacoApi.editor.EditorOption.wordWrap) === "on" ? "off" : "on";
|
||||
ed.updateOptions({ wordWrap: newOption });
|
||||
this.props.onWordWrapChanged(newOption);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +98,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
value: this.props.content,
|
||||
readOnly: this.props.isReadOnly,
|
||||
ariaLabel: this.props.ariaLabel,
|
||||
fontSize: this.props.fontSize || 12,
|
||||
fontSize: 12,
|
||||
automaticLayout: true,
|
||||
theme: this.props.theme,
|
||||
wordWrap: this.props.wordWrap || "off",
|
||||
@@ -218,19 +107,11 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
lineDecorationsWidth: this.props.lineDecorationsWidth,
|
||||
minimap: this.props.minimap,
|
||||
scrollBeyondLastLine: this.props.scrollBeyondLastLine,
|
||||
fixedOverflowWidgets: true,
|
||||
};
|
||||
|
||||
this.rootNode.innerHTML = "";
|
||||
this.monacoApi = await loadMonaco();
|
||||
|
||||
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;
|
||||
}
|
||||
const monaco = await loadMonaco();
|
||||
createCallback(monaco?.editor?.create(this.rootNode, options));
|
||||
|
||||
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={
|
||||
{
|
||||
"dropdown": {
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
@@ -89,20 +89,20 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
label="Platform"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": "Hosted",
|
||||
"text": "Hosted",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "Portal",
|
||||
"text": "Portal",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "Emulator",
|
||||
"text": "Emulator",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "",
|
||||
"text": "None",
|
||||
},
|
||||
@@ -110,8 +110,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
}
|
||||
selectedKey="Hosted"
|
||||
styles={
|
||||
{
|
||||
"dropdown": {
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
@@ -208,7 +208,7 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -222,8 +222,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder="https://notebookserver"
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -235,8 +235,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -248,8 +248,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -265,8 +265,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -279,8 +279,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder="https://localhost:1234/explorer.html"
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
@@ -292,8 +292,8 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { ProgressBar, makeStyles } from "@fluentui/react-components";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
indeterminateProgressBarRoot: {
|
||||
"@media screen and (prefers-reduced-motion: reduce)": {
|
||||
animationIterationCount: "infinite",
|
||||
animationDuration: "3s",
|
||||
animationName: {
|
||||
"0%": {
|
||||
opacity: ".2", // matches indeterminate bar width
|
||||
},
|
||||
"50%": {
|
||||
opacity: "1",
|
||||
},
|
||||
"100%": {
|
||||
opacity: ".2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
indeterminateProgressBarBar: {
|
||||
"@media screen and (prefers-reduced-motion: reduce)": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const IndeterminateProgressBar: React.FC = () => {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<ProgressBar
|
||||
bar={{ className: styles.indeterminateProgressBarBar }}
|
||||
className={styles.indeterminateProgressBarRoot}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Button, MessageBar, MessageBarActions, MessageBarBody } from "@fluentui/react-components";
|
||||
import { DismissRegular } from "@fluentui/react-icons";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export enum MessageBannerState {
|
||||
/** The banner should be visible if the triggering conditions are met. */
|
||||
Allowed = "allowed",
|
||||
|
||||
/** The banner has been dismissed by the user and will not be shown until the component is recreated, even if the visibility condition is true. */
|
||||
Dismissed = "dismissed",
|
||||
|
||||
/** The banner has been supressed by the user and will not be shown at all, even if the visibility condition is true. */
|
||||
Suppressed = "suppressed",
|
||||
}
|
||||
|
||||
export type MessageBannerProps = {
|
||||
/** A CSS class for the root MessageBar component */
|
||||
className: string;
|
||||
|
||||
/** A unique ID for the message that will be used to store it's dismiss/suppress state across sessions. */
|
||||
messageId: string;
|
||||
|
||||
/** The current visibility state for the banner IGNORING the user's dimiss/suppress preference
|
||||
*
|
||||
* If this value is true but the user has dismissed the banner, the banner will NOT be shown.
|
||||
*/
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
/** A component that shows a message banner which can be dismissed by the user.
|
||||
*
|
||||
* In the future, this can also support persisting the dismissed state in local storage without requiring changes to all the components that use it.
|
||||
*
|
||||
* A message banner can be in three "states":
|
||||
* - Allowed: The banner should be visible if the triggering conditions are met.
|
||||
* - Dismissed: The banner has been dismissed by the user and will not be shown until the component is recreated, even if the visibility condition is true.
|
||||
* - Suppressed: The banner has been supressed by the user and will not be shown at all, even if the visibility condition is true.
|
||||
*
|
||||
* The "Dismissed" state represents the user clicking the "x" in the banner to dismiss it.
|
||||
* The "Suppressed" state represents the user clicking "Don't show this again".
|
||||
*/
|
||||
export const MessageBanner: React.FC<MessageBannerProps> = ({ visible, className, children }) => {
|
||||
const [state, setState] = useState<MessageBannerState>(MessageBannerState.Allowed);
|
||||
|
||||
if (state !== MessageBannerState.Allowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageBar className={className}>
|
||||
<MessageBarBody>{children}</MessageBarBody>
|
||||
<MessageBarActions
|
||||
containerAction={
|
||||
<Button
|
||||
aria-label="dismiss"
|
||||
appearance="transparent"
|
||||
icon={<DismissRegular />}
|
||||
onClick={() => setState(MessageBannerState.Dismissed)}
|
||||
/>
|
||||
}
|
||||
></MessageBarActions>
|
||||
</MessageBar>
|
||||
);
|
||||
};
|
||||
@@ -4,8 +4,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardBase
|
||||
aria-label="name"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"display": "inline-block",
|
||||
"marginRight": 20,
|
||||
"width": 256,
|
||||
@@ -16,8 +16,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardActivityBase
|
||||
activity="Invalid Date"
|
||||
people={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"name": "author",
|
||||
"profileImageSrc": false,
|
||||
},
|
||||
@@ -26,8 +26,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<StyledDocumentCardPreviewBase
|
||||
previewImages={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"height": 144,
|
||||
"imageFit": 2,
|
||||
"previewImageSrc": "thumbnailUrl",
|
||||
@@ -40,8 +40,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Text
|
||||
nowrap={true}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"height": 18,
|
||||
"padding": "2px 16px",
|
||||
},
|
||||
@@ -69,15 +69,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"padding": "8px 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -88,8 +88,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="RedEye"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -100,8 +100,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -112,8 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="Download"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -124,8 +124,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "#605E5C",
|
||||
"paddingRight": 8,
|
||||
},
|
||||
@@ -136,8 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<Icon
|
||||
iconName="Heart"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
@@ -151,8 +151,8 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<StyledDocumentCardDetailsBase>
|
||||
<Separator
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"height": 1,
|
||||
"padding": 0,
|
||||
},
|
||||
@@ -161,22 +161,22 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"padding": "0px 16px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
{
|
||||
Object {
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Favorite"
|
||||
id="TooltipHost-IconButton-Heart"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"display": "inline-block",
|
||||
"float": "left",
|
||||
},
|
||||
@@ -186,7 +186,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Favorite"
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "Heart",
|
||||
}
|
||||
}
|
||||
@@ -196,15 +196,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</StyledTooltipHostBase>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
{
|
||||
Object {
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Download"
|
||||
id="TooltipHost-IconButton-Download"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"display": "inline-block",
|
||||
"float": "left",
|
||||
},
|
||||
@@ -214,7 +214,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Download"
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "Download",
|
||||
}
|
||||
}
|
||||
@@ -224,15 +224,15 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
</StyledTooltipHostBase>
|
||||
<StyledTooltipHostBase
|
||||
calloutProps={
|
||||
{
|
||||
Object {
|
||||
"gapSpace": 0,
|
||||
}
|
||||
}
|
||||
content="Remove"
|
||||
id="TooltipHost-IconButton-Delete"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"display": "inline-block",
|
||||
"float": "right",
|
||||
},
|
||||
@@ -242,7 +242,7 @@ exports[`GalleryCardComponent renders 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Remove"
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "Delete",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`CodeOfConduct renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports[`CodeOfConduct renders 1`] = `
|
||||
<StackItem>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
"fontWeight": 500,
|
||||
}
|
||||
@@ -41,12 +41,12 @@ exports[`CodeOfConduct renders 1`] = `
|
||||
label="I have read and accept the code of conduct."
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
Object {
|
||||
"label": Object {
|
||||
"margin": 0,
|
||||
"padding": "2 0 2 0",
|
||||
},
|
||||
"text": {
|
||||
"text": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`InfoComponent renders 1`] = `
|
||||
<StyledHoverCardBase
|
||||
instantOpenOnClick={true}
|
||||
plainCardProps={
|
||||
{
|
||||
Object {
|
||||
"onRenderPlainCard": [Function],
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ exports[`InfoComponent renders 1`] = `
|
||||
className="infoIconMain"
|
||||
iconName="Help"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
itemKey="OfficialSamples"
|
||||
key="OfficialSamples"
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
"padding": 10,
|
||||
}
|
||||
@@ -50,8 +50,8 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
</StackItem>
|
||||
<StackItem
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"minWidth": 200,
|
||||
},
|
||||
}
|
||||
@@ -60,20 +60,20 @@ exports[`GalleryViewerComponent renders 1`] = `
|
||||
<Dropdown
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": 0,
|
||||
"text": "Most viewed",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": 1,
|
||||
"text": "Most downloaded",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": 3,
|
||||
"text": "Most recent",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": 2,
|
||||
"text": "Most favorited",
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 30,
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "HeartFill",
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -96,8 +96,8 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
@@ -115,7 +115,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
||||
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 30,
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "Heart",
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -208,8 +208,8 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogSurface,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Field,
|
||||
ProgressBar,
|
||||
} from "@fluentui/react-components";
|
||||
import * as React from "react";
|
||||
|
||||
interface ProgressModalDialogProps {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
maxValue: number;
|
||||
value: number;
|
||||
dismissText: string;
|
||||
onDismiss: () => void;
|
||||
onCancel?: () => void;
|
||||
/* mode drives the state of the action buttons
|
||||
* inProgress: Show cancel button
|
||||
* completed: Show close button
|
||||
* aborting: Show cancel button, but disabled
|
||||
* aborted: Show close button
|
||||
*/
|
||||
mode?: "inProgress" | "completed" | "aborting" | "aborted";
|
||||
}
|
||||
|
||||
/**
|
||||
* React component that renders a modal dialog with a progress bar.
|
||||
*/
|
||||
export const ProgressModalDialog: React.FC<ProgressModalDialogProps> = ({
|
||||
isOpen,
|
||||
title,
|
||||
message,
|
||||
maxValue,
|
||||
value,
|
||||
dismissText,
|
||||
onCancel,
|
||||
onDismiss,
|
||||
children,
|
||||
mode = "completed",
|
||||
}) => (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(event, data) => {
|
||||
if (!data.open) {
|
||||
onDismiss();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Field validationMessage={message} validationState="none">
|
||||
<ProgressBar max={maxValue} value={value} />
|
||||
</Field>
|
||||
{children}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{mode === "inProgress" || mode === "aborting" ? (
|
||||
<Button appearance="secondary" onClick={onCancel} disabled={mode === "aborting"}>
|
||||
{dismissText}
|
||||
</Button>
|
||||
) : (
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<Button appearance="primary">Close</Button>
|
||||
</DialogTrigger>
|
||||
)}
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -25,14 +25,7 @@
|
||||
font-family: @DataExplorerFont;
|
||||
}
|
||||
|
||||
.settingsV2Editor {
|
||||
.settingsV2IndexingPolicyEditor {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
.settingsV2EditorSpinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ describe("SettingsComponent", () => {
|
||||
readSettings: undefined,
|
||||
onSettingsClick: undefined,
|
||||
loadOffer: undefined,
|
||||
getPendingThroughputSplitNotification: undefined,
|
||||
} as ViewModels.Database;
|
||||
newCollection.getDatabase = () => newDatabase;
|
||||
newCollection.offer = ko.observable(undefined);
|
||||
|
||||
@@ -3,13 +3,7 @@ import {
|
||||
ComputedPropertiesComponent,
|
||||
ComputedPropertiesComponentProps,
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
|
||||
import {
|
||||
ContainerVectorPolicyComponent,
|
||||
ContainerVectorPolicyComponentProps,
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/useCommandBar";
|
||||
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";
|
||||
@@ -33,6 +27,7 @@ import {
|
||||
PartitionKeyComponent,
|
||||
PartitionKeyComponentProps,
|
||||
} from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
|
||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
import "./SettingsComponent.less";
|
||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||
@@ -130,6 +125,7 @@ export interface SettingsComponentState {
|
||||
conflictResolutionPolicyProcedureBaseline: string;
|
||||
isConflictResolutionDirty: boolean;
|
||||
|
||||
initialNotification: DataModels.Notification;
|
||||
selectedTab: SettingsV2TabTypes;
|
||||
}
|
||||
|
||||
@@ -148,7 +144,6 @@ 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;
|
||||
|
||||
@@ -163,7 +158,6 @@ 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;
|
||||
|
||||
@@ -228,6 +222,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
conflictResolutionPolicyProcedureBaseline: undefined,
|
||||
isConflictResolutionDirty: false,
|
||||
|
||||
initialNotification: undefined,
|
||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||
};
|
||||
|
||||
@@ -1050,6 +1045,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
|
||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||
throughputError: this.state.throughputError,
|
||||
};
|
||||
|
||||
@@ -1101,7 +1097,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange,
|
||||
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
||||
};
|
||||
|
||||
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||
@@ -1148,10 +1143,6 @@ 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({
|
||||
@@ -1165,13 +1156,6 @@ 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="settingsV2Editor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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,7 +16,6 @@ export interface IndexingPolicyComponentProps {
|
||||
logIndexingPolicySuccessMessage: () => void;
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
isVectorSearchEnabled?: boolean;
|
||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -120,15 +119,10 @@ 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="settingsV2Editor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"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={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"paddingLeft": 10,
|
||||
"width": 210,
|
||||
},
|
||||
@@ -34,12 +34,12 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
ariaLabel="Index Type 1"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": "Single",
|
||||
"text": "Single Field",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "Wildcard",
|
||||
"text": "Wildcard",
|
||||
},
|
||||
@@ -48,8 +48,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
placeholder="Select an index type"
|
||||
selectedKey="Single"
|
||||
styles={
|
||||
{
|
||||
"dropdown": {
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"paddingleft": 10,
|
||||
"width": 202,
|
||||
},
|
||||
@@ -60,7 +60,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
ariaLabel="Undo Button 1"
|
||||
disabled={false}
|
||||
iconProps={
|
||||
{
|
||||
Object {
|
||||
"iconName": "Undo",
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<StyledMessageBar
|
||||
messageBarType={1}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"marginLeft": 10,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ exports[`MongoIndexingPolicyComponent error shown for collection with compound i
|
||||
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
</Text>
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "definition",
|
||||
"isResizable": true,
|
||||
"key": "definition",
|
||||
@@ -56,7 +56,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
"minWidth": 100,
|
||||
"name": "Definition",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"fieldName": "type",
|
||||
"isResizable": true,
|
||||
"key": "type",
|
||||
@@ -64,7 +64,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
"minWidth": 100,
|
||||
"name": "Type",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"fieldName": "actionButton",
|
||||
"isResizable": true,
|
||||
"key": "actionButton",
|
||||
@@ -75,15 +75,15 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={[]}
|
||||
items={Array []}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"selectors": {
|
||||
".ms-FocusZone": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
".ms-FocusZone": Object {
|
||||
"paddingTop": 0,
|
||||
},
|
||||
},
|
||||
@@ -93,14 +93,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -117,11 +117,11 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
</Stack>
|
||||
<Separator
|
||||
styles={
|
||||
{
|
||||
"root": [
|
||||
{
|
||||
"selectors": {
|
||||
"::before": {
|
||||
Object {
|
||||
"root": Array [
|
||||
Object {
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
@@ -132,8 +132,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -136,15 +136,15 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
|
||||
};
|
||||
|
||||
const getPercentageComplete = () => {
|
||||
const jobStatus = portalDataTransferJob?.properties?.status;
|
||||
const isCompleted = jobStatus === "Completed";
|
||||
if (isCompleted) {
|
||||
return 1;
|
||||
}
|
||||
const processedCount = portalDataTransferJob?.properties?.processedCount;
|
||||
const totalCount = portalDataTransferJob?.properties?.totalCount;
|
||||
const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob);
|
||||
return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0;
|
||||
const jobStatus = portalDataTransferJob?.properties?.status;
|
||||
const isCancelled = jobStatus === "Cancelled";
|
||||
const isCompleted = jobStatus === "Completed";
|
||||
if (totalCount <= 0 && !isCompleted) {
|
||||
return isCancelled ? 0 : null;
|
||||
}
|
||||
return isCompleted ? 1 : processedCount / totalCount;
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { shallow } from "enzyme";
|
||||
import ko from "knockout";
|
||||
import React from "react";
|
||||
import * as Constants from "../../../../Common/Constants";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import { updateUserContext } from "../../../../UserContext";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { throughputUnit } from "../SettingsRenderUtils";
|
||||
import { collection } from "../TestUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
|
||||
describe("ScaleComponent", () => {
|
||||
const targetThroughput = 6000;
|
||||
|
||||
const baseProps: ScaleComponentProps = {
|
||||
collection: collection,
|
||||
database: undefined,
|
||||
@@ -28,8 +36,39 @@ describe("ScaleComponent", () => {
|
||||
onScaleDiscardableChange: () => {
|
||||
return;
|
||||
},
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${targetThroughput} ${throughputUnit}`,
|
||||
} as DataModels.Notification,
|
||||
};
|
||||
|
||||
it("renders with correct initial notification", () => {
|
||||
let wrapper = shallow(<ScaleComponent {...baseProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
|
||||
|
||||
const newCollection = { ...collection };
|
||||
const maxThroughput = 5000;
|
||||
newCollection.offer = ko.observable({
|
||||
manualThroughput: undefined,
|
||||
autoscaleMaxThroughput: maxThroughput,
|
||||
minimumThroughput: 400,
|
||||
id: "offer",
|
||||
offerReplacePending: true,
|
||||
});
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
initialNotification: undefined as DataModels.Notification,
|
||||
collection: newCollection,
|
||||
};
|
||||
wrapper = shallow(<ScaleComponent {...newProps} />);
|
||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
|
||||
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput);
|
||||
});
|
||||
|
||||
it("autoScale disabled", () => {
|
||||
const scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.isAutoScaleEnabled()).toEqual(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
getThroughputApplyLongDelayMessage,
|
||||
getThroughputApplyShortDelayMessage,
|
||||
subComponentStackProps,
|
||||
throughputUnit,
|
||||
@@ -33,6 +34,7 @@ export interface ScaleComponentProps {
|
||||
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
initialNotification: DataModels.Notification;
|
||||
throughputError?: string;
|
||||
}
|
||||
|
||||
@@ -100,6 +102,10 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
};
|
||||
|
||||
public getInitialNotificationElement = (): JSX.Element => {
|
||||
if (this.props.initialNotification) {
|
||||
return this.getLongDelayMessage();
|
||||
}
|
||||
|
||||
if (this.offer?.offerReplacePending) {
|
||||
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
|
||||
return getThroughputApplyShortDelayMessage(
|
||||
@@ -114,6 +120,26 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
public getLongDelayMessage = (): JSX.Element => {
|
||||
const matches: string[] = this.props.initialNotification?.description.match(
|
||||
`Throughput update for (.*) ${throughputUnit}`,
|
||||
);
|
||||
|
||||
const throughput = this.props.throughputBaseline;
|
||||
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
|
||||
if (targetThroughput) {
|
||||
return getThroughputApplyLongDelayMessage(
|
||||
this.props.wasAutopilotOriginallySet,
|
||||
throughput,
|
||||
throughputUnit,
|
||||
this.databaseId,
|
||||
this.collectionId,
|
||||
targetThroughput,
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
private getThroughputInputComponent = (): JSX.Element => (
|
||||
<ThroughputInputAutoPilotV3Component
|
||||
databaseAccount={userContext?.databaseAccount}
|
||||
|
||||
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="settingsV2Editor"
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
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={
|
||||
{
|
||||
"flexContainer": [
|
||||
{
|
||||
Object {
|
||||
"flexContainer": Array [
|
||||
Object {
|
||||
"columnGap": "default",
|
||||
"display": "default",
|
||||
"selectors": {
|
||||
".ms-ChoiceField-field.is-checked::after": {
|
||||
"selectors": Object {
|
||||
".ms-ChoiceField-field.is-checked::after": Object {
|
||||
"borderColor": undefined,
|
||||
},
|
||||
".ms-ChoiceField-field.is-checked::before": {
|
||||
".ms-ChoiceField-field.is-checked::before": Object {
|
||||
"borderColor": undefined,
|
||||
},
|
||||
".ms-ChoiceField-wrapper label": {
|
||||
".ms-ChoiceField-wrapper label": Object {
|
||||
"fontFamily": undefined,
|
||||
"fontSize": 14,
|
||||
"padding": "2px 5px",
|
||||
@@ -55,12 +55,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
onChange={[Function]}
|
||||
onRenderLabel={[Function]}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"borderColor": "",
|
||||
"height": 25,
|
||||
"selectors": {
|
||||
":disabled": {
|
||||
"selectors": Object {
|
||||
":disabled": Object {
|
||||
"backgroundColor": undefined,
|
||||
"borderColor": undefined,
|
||||
},
|
||||
@@ -77,7 +77,7 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
|
||||
exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
@@ -86,12 +86,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
label="Mode"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": "LastWriterWins",
|
||||
"text": "Last Write Wins (default)",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "Custom",
|
||||
"text": "Merge Procedure (custom)",
|
||||
},
|
||||
@@ -99,19 +99,19 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
}
|
||||
selectedKey="Custom"
|
||||
styles={
|
||||
{
|
||||
"flexContainer": [
|
||||
{
|
||||
Object {
|
||||
"flexContainer": Array [
|
||||
Object {
|
||||
"columnGap": "default",
|
||||
"display": "default",
|
||||
"selectors": {
|
||||
".ms-ChoiceField-field.is-checked::after": {
|
||||
"selectors": Object {
|
||||
".ms-ChoiceField-field.is-checked::after": Object {
|
||||
"borderColor": "",
|
||||
},
|
||||
".ms-ChoiceField-field.is-checked::before": {
|
||||
".ms-ChoiceField-field.is-checked::before": Object {
|
||||
"borderColor": "",
|
||||
},
|
||||
".ms-ChoiceField-wrapper label": {
|
||||
".ms-ChoiceField-wrapper label": Object {
|
||||
"fontFamily": undefined,
|
||||
"fontSize": 14,
|
||||
"padding": "2px 5px",
|
||||
@@ -129,12 +129,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
|
||||
onChange={[Function]}
|
||||
onRenderLabel={[Function]}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"borderColor": "",
|
||||
"height": 25,
|
||||
"selectors": {
|
||||
":disabled": {
|
||||
"selectors": Object {
|
||||
":disabled": Object {
|
||||
"backgroundColor": undefined,
|
||||
"borderColor": undefined,
|
||||
},
|
||||
|
||||
@@ -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="settingsV2Editor"
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ScaleComponent renders with correct initial notification 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledMessageBar
|
||||
messageBarType={5}
|
||||
>
|
||||
<Text
|
||||
id="throughputApplyLongDelayMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
|
||||
<br />
|
||||
Database: test, Container: test
|
||||
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
||||
</Text>
|
||||
</StyledMessageBar>
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ThroughputInputAutoPilotV3Component
|
||||
canExceedMaximumValue={true}
|
||||
collectionName="test"
|
||||
databaseName="test"
|
||||
isAutoPilotSelected={false}
|
||||
isEmulator={false}
|
||||
isEnabled={true}
|
||||
isFixed={false}
|
||||
label="Throughput (6,000 - unlimited RU/s)"
|
||||
maxAutoPilotThroughput={4000}
|
||||
maxAutoPilotThroughputBaseline={4000}
|
||||
maximum={1000000}
|
||||
minimum={6000}
|
||||
onAutoPilotSelected={[Function]}
|
||||
onMaxAutoPilotThroughputChange={[Function]}
|
||||
onScaleDiscardableChange={[Function]}
|
||||
onScaleSaveableChange={[Function]}
|
||||
onThroughputChange={[Function]}
|
||||
spendAckChecked={false}
|
||||
throughput={1000}
|
||||
throughputBaseline={1000}
|
||||
usageSizeInKB={100}
|
||||
wasAutopilotOriginallySet={true}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
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={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"display": "inline-block",
|
||||
"float": "right",
|
||||
},
|
||||
@@ -45,8 +45,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
|
||||
ariaLabel="Info"
|
||||
iconName="Info"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"marginBottom": -3,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("SettingsUtils", () => {
|
||||
readSettings: undefined,
|
||||
onSettingsClick: undefined,
|
||||
loadOffer: undefined,
|
||||
getPendingThroughputSplitNotification: undefined,
|
||||
} as ViewModels.Database;
|
||||
};
|
||||
newCollection.offer(undefined);
|
||||
|
||||
@@ -47,7 +47,6 @@ export enum SettingsV2TabTypes {
|
||||
IndexingPolicyTab,
|
||||
PartitionKeyTab,
|
||||
ComputedPropertiesTab,
|
||||
ContainerVectorPolicyTab,
|
||||
}
|
||||
|
||||
export interface IsComponentDirtyResult {
|
||||
@@ -150,11 +149,9 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
case SettingsV2TabTypes.IndexingPolicyTab:
|
||||
return "Indexing Policy";
|
||||
case SettingsV2TabTypes.PartitionKeyTab:
|
||||
return "Partition Keys (preview)";
|
||||
return "Partition Keys";
|
||||
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||
return "Computed Properties";
|
||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||
return "Container Vector Policy (preview)";
|
||||
return "Computed Properties (preview)";
|
||||
default:
|
||||
throw new Error(`Unknown tab ${tab}`);
|
||||
}
|
||||
|
||||
@@ -16,27 +16,28 @@ 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": {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -60,16 +61,16 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": {
|
||||
"partitionKey": Object {
|
||||
"kind": "hash",
|
||||
"paths": [],
|
||||
"paths": Array [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": [
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": {},
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
@@ -90,7 +91,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="SubSettingsTab"
|
||||
key="SubSettingsTab"
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
@@ -100,20 +101,21 @@ 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": {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -137,16 +139,16 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": {
|
||||
"partitionKey": Object {
|
||||
"kind": "hash",
|
||||
"paths": [],
|
||||
"paths": Array [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": [
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": {},
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
@@ -174,29 +176,28 @@ exports[`SettingsComponent renders 1`] = `
|
||||
itemKey="IndexingPolicyTab"
|
||||
key="IndexingPolicyTab"
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndexingPolicyComponent
|
||||
indexingPolicyContent={
|
||||
{
|
||||
Object {
|
||||
"automatic": true,
|
||||
"excludedPaths": [],
|
||||
"includedPaths": [],
|
||||
"excludedPaths": Array [],
|
||||
"includedPaths": Array [],
|
||||
"indexingMode": "consistent",
|
||||
}
|
||||
}
|
||||
indexingPolicyContentBaseline={
|
||||
{
|
||||
Object {
|
||||
"automatic": true,
|
||||
"excludedPaths": [],
|
||||
"includedPaths": [],
|
||||
"excludedPaths": Array [],
|
||||
"includedPaths": Array [],
|
||||
"indexingMode": "consistent",
|
||||
}
|
||||
}
|
||||
isVectorSearchEnabled={false}
|
||||
logIndexingPolicySuccessMessage={[Function]}
|
||||
onIndexingPolicyContentChange={[Function]}
|
||||
onIndexingPolicyDirtyChange={[Function]}
|
||||
@@ -206,31 +207,32 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerText="Partition Keys (preview)"
|
||||
headerText="Partition Keys"
|
||||
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": {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -254,29 +256,30 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"id": [Function],
|
||||
"indexingPolicy": [Function],
|
||||
"offer": [Function],
|
||||
"partitionKey": {
|
||||
"partitionKey": Object {
|
||||
"kind": "hash",
|
||||
"paths": [],
|
||||
"paths": Array [],
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperties": [
|
||||
"partitionKeyProperties": Array [
|
||||
"partitionKey",
|
||||
],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": {},
|
||||
"uniqueKeyPolicy": Object {},
|
||||
"usageSizeInKB": [Function],
|
||||
}
|
||||
}
|
||||
explorer={
|
||||
Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
@@ -297,27 +300,27 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerText="Computed Properties"
|
||||
headerText="Computed Properties (preview)"
|
||||
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={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -81,8 +81,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -102,8 +102,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="updateThroughputDelayedApplyWarningMessage"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -114,8 +114,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -134,8 +134,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="throughputApplyShortDelayMessage"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -150,8 +150,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Text
|
||||
id="throughputApplyLongDelayMessage"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -165,8 +165,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -179,8 +179,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -191,8 +191,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -211,8 +211,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -247,15 +247,15 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -270,8 +270,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Stack>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
@@ -287,8 +287,8 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"color": "windowtext",
|
||||
"fontSize": 14,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TextField,
|
||||
Toggle,
|
||||
} from "@fluentui/react";
|
||||
import { TFunction } from "i18next";
|
||||
import * as React from "react";
|
||||
import {
|
||||
ChoiceItem,
|
||||
@@ -99,7 +100,7 @@ export interface SmartUiComponentProps {
|
||||
onInputChange: (input: AnyDisplay, newValue: InputType) => void;
|
||||
onError: (hasError: boolean) => void;
|
||||
disabled: boolean;
|
||||
getTranslation: (messageKey: string, namespace?: string) => string;
|
||||
getTranslation: TFunction;
|
||||
}
|
||||
|
||||
interface SmartUiComponentState {
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
aria-labelledby="description-label"
|
||||
id="description-text-display"
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"whiteSpace": "pre-line",
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -70,14 +70,14 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
</StyledLabelBase>
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 2,
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -133,11 +133,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
onChange={[Function]}
|
||||
step={10}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
"valueLabel": {
|
||||
"valueLabel": Object {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
@@ -157,7 +157,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -199,8 +199,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
id="containerId-textField-input"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -219,7 +219,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -243,8 +243,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
onChange={[Function]}
|
||||
onText="Enabled"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -261,7 +261,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -282,16 +282,16 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
id="database-dropdown-input"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": "db1",
|
||||
"text": "Database 1",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "db2",
|
||||
"text": "Database 2",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "db3",
|
||||
"text": "Database 3",
|
||||
},
|
||||
@@ -299,13 +299,13 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||
}
|
||||
selectedKey="db2"
|
||||
styles={
|
||||
{
|
||||
"dropdown": {
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
},
|
||||
"root": {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -323,7 +323,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
aria-labelledby="description-label"
|
||||
id="description-text-display"
|
||||
style={
|
||||
{
|
||||
Object {
|
||||
"whiteSpace": "pre-line",
|
||||
}
|
||||
}
|
||||
@@ -372,7 +372,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -389,14 +389,14 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
</StyledLabelBase>
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 2,
|
||||
}
|
||||
}
|
||||
@@ -425,7 +425,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -450,11 +450,11 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
onChange={[Function]}
|
||||
step={10}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
"valueLabel": {
|
||||
"valueLabel": Object {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
@@ -474,7 +474,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -495,7 +495,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -515,8 +515,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
id="containerId-textField-input"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -535,7 +535,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -558,8 +558,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
onChange={[Function]}
|
||||
onText="Enabled"
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
@@ -576,7 +576,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
<Stack
|
||||
className="widgetRendererContainer"
|
||||
tokens={
|
||||
{
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
@@ -596,16 +596,16 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
id="database-dropdown-input"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
Array [
|
||||
Object {
|
||||
"key": "db1",
|
||||
"text": "Database 1",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "db2",
|
||||
"text": "Database 2",
|
||||
},
|
||||
{
|
||||
Object {
|
||||
"key": "db3",
|
||||
"text": "Database 3",
|
||||
},
|
||||
@@ -613,13 +613,13 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||
}
|
||||
selectedKey="db2"
|
||||
styles={
|
||||
{
|
||||
"dropdown": {
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"color": "#393939",
|
||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||
"fontSize": 12,
|
||||
},
|
||||
"root": {
|
||||
"root": Object {
|
||||
"width": 400,
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user