mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-07 19:46:53 +00:00
Compare commits
11 Commits
user/bchou
...
hotfix/110
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40db06a7a4 | ||
|
|
ff1eb6a78e | ||
|
|
31ec3c08bc | ||
|
|
abf4b3bd0f | ||
|
|
d0d615a85a | ||
|
|
2996120235 | ||
|
|
3cd6d5a65d | ||
|
|
d924824536 | ||
|
|
cd27814fad | ||
|
|
909957a9a1 | ||
|
|
569e5ed1fc |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -198,6 +198,18 @@ jobs:
|
|||||||
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
|
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
|
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
|
||||||
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
|
echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
|
||||||
|
echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
|
echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
|
||||||
|
echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
|
echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
|
||||||
|
echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
|
echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
|
||||||
|
echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
||||||
- name: Upload blob report to GitHub Actions Artifacts
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
|
|||||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
@@ -1,6 +1,22 @@
|
|||||||
// Place your settings in this file to overwrite default and user settings.
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
{
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
".vs": true,
|
||||||
|
".vscode/**": true,
|
||||||
|
"*.trx": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"built/**": true,
|
||||||
|
"coverage/**": true,
|
||||||
|
"libs/**": true,
|
||||||
|
"node_modules/**": true,
|
||||||
|
"package-lock.json": true,
|
||||||
|
"quickstart/**": true,
|
||||||
|
"test/out/**": true,
|
||||||
|
"workers/libs/**": true
|
||||||
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
@@ -8,8 +24,5 @@
|
|||||||
"source.organizeImports": "explicit"
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
1
images/AzureOpenAi.svg
Normal file
1
images/AzureOpenAi.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="uuid-adbdae8e-5a41-46d1-8c18-aa73cdbfee32" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><path d="m0,2.7v12.6c0,1.491,1.209,2.7,2.7,2.7h12.6c1.491,0,2.7-1.209,2.7-2.7V2.7c0-1.491-1.209-2.7-2.7-2.7H2.7C1.209,0,0,1.209,0,2.7ZM10.8,0v3.6c0,3.976,3.224,7.2,7.2,7.2h-3.6c-3.976,0-7.199,3.222-7.2,7.198v-3.598c0-3.976-3.224-7.2-7.2-7.2h3.6c3.976,0,7.2-3.224,7.2-7.2Z" fill="#000000" stroke-width="0" /></svg>
|
||||||
|
After Width: | Height: | Size: 443 B |
3
images/github-black-and-white.svg
Normal file
3
images/github-black-and-white.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8 0C8.73438 0 9.44271 0.0960961 10.125 0.288288C10.8073 0.48048 11.4427 0.758091 12.0312 1.12112C12.6198 1.48415 13.1589 1.91124 13.6484 2.4024C14.138 2.89356 14.5573 3.44611 14.9062 4.06006C15.2552 4.67401 15.5234 5.32799 15.7109 6.02202C15.8984 6.71605 15.9948 7.44211 16 8.2002C16 9.08108 15.8698 9.92993 15.6094 10.7467C15.349 11.5636 14.9766 12.3136 14.4922 12.997C14.0078 13.6803 13.4323 14.2783 12.7656 14.7908C12.099 15.3033 11.3542 15.701 10.5312 15.984C10.5156 15.9893 10.4922 15.992 10.4609 15.992C10.4297 15.992 10.4062 15.9947 10.3906 16C10.2656 16 10.1667 15.9626 10.0938 15.8879C10.0208 15.8131 9.98438 15.7144 9.98438 15.5916V14.4705C9.98438 14.1021 9.98698 13.7257 9.99219 13.3413C9.99219 13.0691 9.95312 12.7941 9.875 12.5165C9.79688 12.2389 9.65625 12.0067 9.45312 11.8198C10.0573 11.7504 10.5859 11.625 11.0391 11.4434C11.4922 11.2619 11.8724 11.0057 12.1797 10.6747C12.487 10.3437 12.7161 9.94328 12.8672 9.47347C13.0182 9.00367 13.0964 8.43777 13.1016 7.77578C13.1016 7.35936 13.0339 6.96697 12.8984 6.5986C12.763 6.23023 12.5573 5.88856 12.2812 5.57357C12.3385 5.42409 12.3802 5.26927 12.4062 5.10911C12.4323 4.94895 12.4453 4.78879 12.4453 4.62863C12.4453 4.42042 12.4245 4.21488 12.3828 4.01201C12.3411 3.80914 12.2812 3.60627 12.2031 3.4034C12.1771 3.39273 12.1484 3.38739 12.1172 3.38739C12.0859 3.38739 12.0573 3.38739 12.0312 3.38739C11.8646 3.38739 11.6901 3.41408 11.5078 3.46747C11.3255 3.52085 11.1458 3.59026 10.9688 3.67568C10.7917 3.76109 10.6172 3.85452 10.4453 3.95596C10.2734 4.05739 10.125 4.15349 10 4.24424C9.34896 4.05739 8.68229 3.96396 8 3.96396C7.31771 3.96396 6.65104 4.05739 6 4.24424C5.86979 4.15349 5.72135 4.05739 5.55469 3.95596C5.38802 3.85452 5.21615 3.76376 5.03906 3.68368C4.86198 3.6036 4.67969 3.5342 4.49219 3.47548C4.30469 3.41675 4.13021 3.38739 3.96875 3.38739H3.88281C3.85156 3.38739 3.82292 3.39273 3.79688 3.4034C3.72396 3.60093 3.66667 3.80113 3.625 4.004C3.58333 4.20687 3.5599 4.41508 3.55469 4.62863C3.55469 4.78879 3.56771 4.94895 3.59375 5.10911C3.61979 5.26927 3.66146 5.42409 3.71875 5.57357C3.44271 5.88322 3.23698 6.22222 3.10156 6.59059C2.96615 6.95896 2.89844 7.35402 2.89844 7.77578C2.89844 8.42709 2.97396 8.99032 3.125 9.46547C3.27604 9.94061 3.50521 10.341 3.8125 10.6667C4.11979 10.9923 4.5 11.2513 4.95312 11.4434C5.40625 11.6356 5.9349 11.7638 6.53906 11.8278C6.38802 11.9666 6.27344 12.1321 6.19531 12.3243C6.11719 12.5165 6.0625 12.7167 6.03125 12.9249C5.89062 12.9943 5.74219 13.0477 5.58594 13.0851C5.42969 13.1225 5.27344 13.1411 5.11719 13.1411C4.78385 13.1411 4.50781 13.0611 4.28906 12.9009C4.07031 12.7407 3.875 12.5219 3.70312 12.2442C3.64062 12.1428 3.5651 12.0414 3.47656 11.9399C3.38802 11.8385 3.29167 11.7477 3.1875 11.6677C3.08333 11.5876 2.97135 11.5235 2.85156 11.4755C2.73177 11.4274 2.60677 11.4007 2.47656 11.3954H2.38281C2.34115 11.3954 2.30208 11.4034 2.26562 11.4194C2.22917 11.4354 2.19271 11.4515 2.15625 11.4675C2.11979 11.4835 2.10417 11.5102 2.10938 11.5475C2.10938 11.6116 2.14583 11.673 2.21875 11.7317C2.29167 11.7905 2.35156 11.8385 2.39844 11.8759L2.42188 11.8919C2.53646 11.9826 2.63542 12.0681 2.71875 12.1481C2.80208 12.2282 2.88021 12.3163 2.95312 12.4124C3.02604 12.5085 3.08594 12.6099 3.13281 12.7167C3.17969 12.8235 3.23958 12.9489 3.3125 13.0931C3.48958 13.5095 3.73698 13.8111 4.05469 13.998C4.3724 14.1849 4.75521 14.2809 5.20312 14.2863C5.33854 14.2863 5.47396 14.2783 5.60938 14.2623C5.74479 14.2462 5.88021 14.2222 6.01562 14.1902V15.5836C6.01562 15.7117 5.97917 15.8131 5.90625 15.8879C5.83333 15.9626 5.73177 16 5.60156 16H5.53906C5.51302 16 5.48958 15.9947 5.46875 15.984C4.65104 15.7117 3.90625 15.3193 3.23438 14.8068C2.5625 14.2943 1.98698 13.6937 1.50781 13.005C1.02865 12.3163 0.658854 11.5636 0.398438 10.7467C0.138021 9.92993 0.00520833 9.08108 0 8.2002C0 7.44745 0.09375 6.72139 0.28125 6.02202C0.46875 5.32266 0.739583 4.67134 1.09375 4.06807C1.44792 3.4648 1.86458 2.91225 2.34375 2.41041C2.82292 1.90858 3.36198 1.47881 3.96094 1.12112C4.5599 0.76343 5.19792 0.488488 5.875 0.296296C6.55208 0.104104 7.26042 0.00533867 8 0Z" fill="#000000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
37676
package-lock.json
generated
37676
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
346
package.json
346
package.json
@@ -4,270 +4,200 @@
|
|||||||
"description": "Cosmos Explorer",
|
"description": "Cosmos Explorer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "16.3.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.5.0",
|
"@azure/cosmos": "4.5.0",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "4.10.1",
|
"@azure/identity": "4.5.0",
|
||||||
"@azure/msal-browser": "4.24.0",
|
"@azure/msal-browser": "2.14.2",
|
||||||
"@babel/plugin-transform-class-properties": "^7.24.7",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.28.0",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@fluentui/react": "8.123.6",
|
"@fluentui/react": "8.119.0",
|
||||||
"@fluentui/react-components": "9.70.0",
|
"@fluentui/react-components": "9.54.2",
|
||||||
"@jupyterlab/services": "7.4.9",
|
"@jupyterlab/services": "6.0.2",
|
||||||
"@jupyterlab/terminal": "4.4.9",
|
"@jupyterlab/terminal": "3.0.3",
|
||||||
"@microsoft/applicationinsights-web": "3.3.10",
|
"@microsoft/applicationinsights-web": "2.6.1",
|
||||||
"@nteract/commutable": "7.5.1",
|
"@nteract/commutable": "7.5.1",
|
||||||
"@nteract/connected-components": "6.9.0",
|
"@nteract/connected-components": "6.8.2",
|
||||||
"@nteract/core": "15.1.9",
|
"@nteract/core": "15.1.9",
|
||||||
"@nteract/data-explorer": "8.2.12",
|
"@nteract/data-explorer": "8.0.3",
|
||||||
"@nteract/directory-listing": "2.1.0",
|
"@nteract/directory-listing": "2.0.6",
|
||||||
"@nteract/dropdown-menu": "1.1.9",
|
"@nteract/dropdown-menu": "1.0.1",
|
||||||
"@nteract/editor": "10.1.12",
|
"@nteract/editor": "10.1.12",
|
||||||
"@nteract/fixtures": "2.3.19",
|
"@nteract/fixtures": "2.3.0",
|
||||||
"@nteract/iron-icons": "1.0.0",
|
"@nteract/iron-icons": "1.0.0",
|
||||||
"@nteract/jupyter-widgets": "4.1.19",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.6.2",
|
"@nteract/markdown": "4.6.0",
|
||||||
"@nteract/monaco-editor": "3.2.2",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "5.1.14",
|
"@nteract/outputs": "3.0.9",
|
||||||
"@nteract/presentational-components": "3.4.12",
|
"@nteract/presentational-components": "3.0.7",
|
||||||
"@nteract/stateful-components": "1.7.15",
|
"@nteract/stateful-components": "1.7.0",
|
||||||
"@nteract/styles": "2.2.11",
|
"@nteract/styles": "2.0.2",
|
||||||
"@nteract/transform-geojson": "5.1.13",
|
"@nteract/transform-geojson": "5.1.8",
|
||||||
"@nteract/transform-model-debug": "5.0.1",
|
"@nteract/transform-model-debug": "5.0.1",
|
||||||
"@nteract/transform-plotly": "7.0.1",
|
"@nteract/transform-plotly": "6.1.6",
|
||||||
"@nteract/transform-vdom": "4.0.15",
|
"@nteract/transform-vdom": "4.0.11",
|
||||||
"@nteract/transform-vega": "7.0.6",
|
"@nteract/transform-vega": "7.0.6",
|
||||||
"@octokit/rest": "21.1.1",
|
"@octokit/rest": "17.9.2",
|
||||||
"@phosphor/widgets": "1.9.3",
|
"@phosphor/widgets": "1.9.3",
|
||||||
"@testing-library/jest-dom": "6.8.0",
|
"@testing-library/jest-dom": "6.4.6",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.14.171",
|
||||||
"@types/mkdirp": "1.0.2",
|
"@types/mkdirp": "1.0.1",
|
||||||
"@types/node-fetch": "2.6.13",
|
"@types/node-fetch": "2.5.7",
|
||||||
"@xmldom/xmldom": "0.9.8",
|
"@xmldom/xmldom": "0.7.13",
|
||||||
"@xterm/xterm": "5.5.0",
|
"@xterm/xterm": "5.5.0",
|
||||||
"@xterm/addon-fit": "0.10.0",
|
"@xterm/addon-fit": "0.10.0",
|
||||||
"allotment": "1.20.4",
|
"allotment": "1.20.2",
|
||||||
"applicationinsights": "3.12.0",
|
"applicationinsights": "1.8.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "3.2.0",
|
"canvas": "2.11.2",
|
||||||
"clean-webpack-plugin": "4.0.0",
|
"clean-webpack-plugin": "4.0.0",
|
||||||
"clipboard-copy": "4.0.1",
|
"clipboard-copy": "4.0.1",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
"css-element-queries": "1.2.3",
|
"css-element-queries": "1.1.1",
|
||||||
"d3": "7.9.0",
|
"d3": "7.8.5",
|
||||||
"datatables.net-colreorder-dt": "2.1.1",
|
"datatables.net-colreorder-dt": "1.7.0",
|
||||||
"datatables.net-dt": "2.3.4",
|
"datatables.net-dt": "1.13.8",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "1.29.0",
|
||||||
"dayjs": "1.11.18",
|
"dayjs": "1.8.19",
|
||||||
"dom-to-image": "2.6.0",
|
"dom-to-image": "2.6.0",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "8.2.0",
|
||||||
"eslint-plugin-jest": "28.14.0",
|
"eslint-plugin-jest": "27.4.2",
|
||||||
"eslint-plugin-react": "7.37.5",
|
"eslint-plugin-react": "7.33.2",
|
||||||
"hasher": "1.2.0",
|
"hasher": "1.2.0",
|
||||||
"html2canvas": "1.4.1",
|
"html2canvas": "1.0.0-rc.5",
|
||||||
"i18next": "25.5.2",
|
"i18next": "23.11.5",
|
||||||
"i18next-browser-languagedetector": "8.2.0",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"i18next-http-backend": "3.0.2",
|
"i18next-http-backend": "1.0.23",
|
||||||
"iframe-resizer-react": "5.1.5",
|
"iframe-resizer-react": "1.1.0",
|
||||||
"immer": "10.1.3",
|
"immer": "9.0.6",
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "4.1.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-typeahead": "2.11.1",
|
"jquery-typeahead": "2.11.1",
|
||||||
"jquery-ui-dist": "1.13.3",
|
"jquery-ui-dist": "1.13.2",
|
||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"loader-utils": "3.3.1",
|
"loader-utils": "2.0.3",
|
||||||
"mkdirp": "3.0.1",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.53.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "6.2.1",
|
"p-retry": "6.2.1",
|
||||||
"patch-package": "8.0.1",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "3.1.1",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
"q": "2.0.3",
|
"q": "1.5.1",
|
||||||
"react": "18.2.0",
|
"react": "16.14.0",
|
||||||
"react-animate-height": "3.2.3",
|
"react-animate-height": "2.0.8",
|
||||||
"react-dnd": "16.0.1",
|
"react-dnd": "14.0.2",
|
||||||
"react-dnd-html5-backend": "16.0.1",
|
"react-dnd-html5-backend": "14.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "16.14.0",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-i18next": "16.0.0",
|
"react-i18next": "14.1.2",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.1.3",
|
||||||
"react-splitter-layout": "4.0.0",
|
"react-splitter-layout": "4.0.0",
|
||||||
"react-string-format": "1.2.0",
|
"react-string-format": "1.0.1",
|
||||||
"react-window": "1.8.10",
|
"react-window": "1.8.10",
|
||||||
"react-youtube": "10.1.0",
|
"react-youtube": "9.0.1",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.21",
|
"rx-jupyter": "5.5.12",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.3.3",
|
||||||
"shell-quote": "1.8.3",
|
"shell-quote": "1.7.3",
|
||||||
"styled-components": "6.1.19",
|
"styled-components": "5.0.1",
|
||||||
"swr": "2.3.6",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
"tinykeys": "3.0.0",
|
"tinykeys": "2.1.0",
|
||||||
"underscore": "1.13.7",
|
"underscore": "1.12.1",
|
||||||
"utility-types": "3.11.0",
|
"utility-types": "3.10.0",
|
||||||
"zustand": "5.0.8"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.4",
|
"@babel/core": "7.24.7",
|
||||||
"@babel/preset-env": "7.28.3",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-react": "7.27.1",
|
"@babel/preset-react": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.27.1",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@playwright/test": "1.55.1",
|
"@playwright/test": "1.49.1",
|
||||||
"@testing-library/react": "16.3.0",
|
"@testing-library/react": "11.2.3",
|
||||||
"@types/applicationinsights-js": "1.0.9",
|
"@types/applicationinsights-js": "1.0.7",
|
||||||
"@types/codemirror": "5.60.16",
|
"@types/codemirror": "0.0.56",
|
||||||
"@types/crossroads": "0.0.33",
|
"@types/crossroads": "0.0.30",
|
||||||
"@types/d3": "7.4.3",
|
"@types/d3": "5.9.2",
|
||||||
"@types/datatables.net": "1.10.28",
|
"@types/datatables.net": "1.10.28",
|
||||||
"@types/datatables.net-colreorder": "1.4.5",
|
"@types/datatables.net-colreorder": "1.4.5",
|
||||||
"@types/dom-to-image": "2.6.7",
|
"@types/dom-to-image": "2.6.2",
|
||||||
"@types/enzyme": "3.10.19",
|
"@types/enzyme": "3.10.12",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.9",
|
"@types/enzyme-adapter-react-16": "1.0.9",
|
||||||
"@types/hasher": "0.0.35",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "30.0.0",
|
"@types/jest": "29.5.12",
|
||||||
"@types/jquery": "3.5.33",
|
"@types/jquery": "3.5.29",
|
||||||
"@types/node": "24.6.0",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.6",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/q": "1.5.8",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "18.3.7",
|
"@types/react": "17.0.44",
|
||||||
"@types/react-dom": "18.3.7",
|
"@types/react-dom": "17.0.15",
|
||||||
"@types/react-notification-system": "0.2.46",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.34",
|
"@types/react-redux": "7.1.7",
|
||||||
"@types/react-splitter-layout": "4.0.0",
|
"@types/react-splitter-layout": "3.0.1",
|
||||||
"@types/react-window": "1.8.8",
|
"@types/react-window": "1.8.8",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "1.27.2",
|
||||||
"@types/sinon": "17.0.4",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.34",
|
"@types/styled-components": "5.1.1",
|
||||||
"@types/underscore": "1.13.0",
|
"@types/underscore": "1.7.36",
|
||||||
"@types/youtube-player": "5.5.11",
|
"@types/youtube-player": "5.5.6",
|
||||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||||
"@typescript-eslint/parser": "8.45.0",
|
"@typescript-eslint/parser": "6.7.4",
|
||||||
"@webpack-cli/serve": "3.0.1",
|
"@webpack-cli/serve": "2.0.5",
|
||||||
"babel-jest": "30.2.0",
|
"babel-jest": "29.7.0",
|
||||||
"babel-loader": "10.0.0",
|
"babel-loader": "8.1.0",
|
||||||
"buffer": "6.0.3",
|
"buffer": "5.1.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||||
"create-file-webpack": "1.0.2",
|
"create-file-webpack": "1.0.2",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "6.8.1",
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "3.11.0",
|
||||||
"enzyme-adapter-react-16": "1.15.8",
|
"enzyme-adapter-react-16": "1.15.8",
|
||||||
"enzyme-to-json": "3.6.2",
|
"enzyme-to-json": "3.6.2",
|
||||||
"eslint": "9.36.0",
|
"eslint": "8.50.0",
|
||||||
"eslint-cli": "1.1.1",
|
"eslint-cli": "1.1.1",
|
||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||||
"eslint-plugin-react-hooks": "5.2.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.2.5",
|
||||||
"fs-extra": "11.3.2",
|
"fs-extra": "7.0.0",
|
||||||
"html-inline-css-webpack-plugin": "1.11.2",
|
"html-inline-css-webpack-plugin": "1.11.2",
|
||||||
"html-loader": "5.1.0",
|
"html-loader": "5.0.0",
|
||||||
"html-webpack-plugin": "5.6.4",
|
"html-webpack-plugin": "5.5.3",
|
||||||
"jest": "30.2.0",
|
"jest": "29.7.0",
|
||||||
"jest-canvas-mock": "2.5.2",
|
"jest-canvas-mock": "2.5.2",
|
||||||
"jest-circus": "30.2.0",
|
"jest-circus": "29.7.0",
|
||||||
"jest-environment-jsdom": "30.2.0",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"jest-html-loader": "1.0.0",
|
"jest-html-loader": "1.0.0",
|
||||||
"jest-react-hooks-shallow": "1.5.1",
|
"jest-react-hooks-shallow": "1.5.1",
|
||||||
"jest-trx-results-processor": "3.0.2",
|
"jest-trx-results-processor": "3.0.2",
|
||||||
"less": "4.4.1",
|
"less": "3.8.1",
|
||||||
"less-loader": "12.3.0",
|
"less-loader": "11.1.3",
|
||||||
"less-vars-loader": "1.1.0",
|
"less-vars-loader": "1.1.0",
|
||||||
"mini-css-extract-plugin": "2.9.4",
|
"mini-css-extract-plugin": "2.1.0",
|
||||||
"monaco-editor-webpack-plugin": "7.1.0",
|
"monaco-editor-webpack-plugin": "7.1.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "2.6.7",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.0.3",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"raw-loader": "4.0.2",
|
"raw-loader": "0.5.1",
|
||||||
"react-dev-utils": "12.0.1",
|
"react-dev-utils": "12.0.1",
|
||||||
"rimraf": "5.0.10",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "21.0.0",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "4.0.0",
|
"style-loader": "0.23.0",
|
||||||
"ts-loader": "9.5.4",
|
"ts-loader": "9.2.4",
|
||||||
"typedoc": "0.28.13",
|
"typedoc": "0.26.2",
|
||||||
"typescript": "5.9.2",
|
"typescript": "4.9.5",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"wait-on": "8.0.5",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "5.102.0",
|
"webpack": "5.88.2",
|
||||||
"webpack-bundle-analyzer": "4.10.2",
|
"webpack-bundle-analyzer": "4.9.1",
|
||||||
"webpack-cli": "6.0.1",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.2.2"
|
"webpack-dev-server": "4.15.2"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@nteract/connected-components": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/core": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/data-explorer": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/dropdown-menu": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/editor": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/iron-icons": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/jupyter-widgets": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/logos": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/markdown": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/monaco-editor": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/octicons": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/outputs": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/presentational-components": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/stateful-components": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-geojson": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-model-debug": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-plotly": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-vdom": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
},
|
|
||||||
"@nteract/transform-vega": {
|
|
||||||
"react": "18.2.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ index e5dc283..1930c2b 100644
|
|||||||
|
|
||||||
/// <reference types="jquery" />
|
/// <reference types="jquery" />
|
||||||
|
|
||||||
-import DataTables, {Api, ColumnSelector} from 'datatables.net';
|
-import DataTables, {Api} from 'datatables.net';
|
||||||
+import DataTables, { Api, ColumnSelector } from 'datatables.net';
|
+import DataTables, { Api } from 'datatables.net';
|
||||||
|
|
||||||
export default DataTables;
|
export default DataTables;
|
||||||
|
|
||||||
@@ -40,6 +40,8 @@ declare module 'datatables.net' {
|
@@ -40,6 +40,8 @@ declare module 'datatables.net' {
|
||||||
@@ -17,6 +17,6 @@ index e5dc283..1930c2b 100644
|
|||||||
*/
|
*/
|
||||||
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
|
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
|
||||||
+ // @ts-ignore
|
+ // @ts-ignore
|
||||||
new (dt: Api<any>, settings: boolean | ConfigColReorder): DataTablesStatic['ColReorder'];
|
new (dt: Api<any>, settings: boolean | ConfigColReorder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
11246
sampleData/fabricSampleData.json
Normal file
11246
sampleData/fabricSampleData.json
Normal file
File diff suppressed because it is too large
Load Diff
288086
sampleData/fabricSampleDataVectors.json
Normal file
288086
sampleData/fabricSampleDataVectors.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,7 @@ export class CapabilityNames {
|
|||||||
public static readonly EnableMongo: string = "EnableMongo";
|
public static readonly EnableMongo: string = "EnableMongo";
|
||||||
public static readonly EnableServerless: string = "EnableServerless";
|
public static readonly EnableServerless: string = "EnableServerless";
|
||||||
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
|
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
|
||||||
public static readonly EnableNoSQLFullTextSearch: string = "EnableNoSQLFullTextSearch";
|
public static readonly EnableNoSQLFullTextSearchPreviewFeatures: string = "EnableNoSQLFullTextSearchPreviewFeatures";
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CapacityMode {
|
export enum CapacityMode {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
@@ -43,6 +45,14 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||||
|
|
||||||
|
if (isFabricNative()) {
|
||||||
|
sendMessage({
|
||||||
|
type: FabricMessageTypes.ContainerUpdated,
|
||||||
|
params: { updateType: "created" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -19,6 +21,11 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
|||||||
await client().database(databaseId).container(collectionId).delete();
|
await client().database(databaseId).container(collectionId).delete();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: FabricMessageTypes.ContainerUpdated,
|
||||||
|
params: { updateType: "deleted" },
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Item, RequestOptions } from "@azure/cosmos";
|
import { Item, RequestOptions } from "@azure/cosmos";
|
||||||
import { HttpHeaders } from "Common/Constants";
|
import { HttpHeaders } from "Common/Constants";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
@@ -23,10 +24,17 @@ export const updateDocument = async (
|
|||||||
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
|
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
// If user has chosen to ignore partition key on update, pass null instead of actual partition key value
|
||||||
|
const ignorePartitionKeyOnDocumentUpdateFlag = LocalStorageUtility.getEntryBoolean(
|
||||||
|
StorageKey.IgnorePartitionKeyOnDocumentUpdate,
|
||||||
|
);
|
||||||
|
const partitionKey = ignorePartitionKeyOnDocumentUpdateFlag ? undefined : getPartitionKeyValue(documentId);
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(collection.databaseId)
|
.database(collection.databaseId)
|
||||||
.container(collection.id())
|
.container(collection.id())
|
||||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
.item(documentId.id(), partitionKey)
|
||||||
.replace(newDocument, options);
|
.replace(newDocument, options);
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
|
|||||||
@@ -110,11 +110,30 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.AAD_ENDPOINT, configContext.allowedAadEndpoints || defaultAllowedAadEndpoints)) {
|
if (newContext.allowedAadEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedAadEndpoints: newContext.allowedAadEndpoints });
|
||||||
|
}
|
||||||
|
if (newContext.allowedArmEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedArmEndpoints: newContext.allowedArmEndpoints });
|
||||||
|
}
|
||||||
|
if (newContext.allowedGraphEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedGraphEndpoints: newContext.allowedGraphEndpoints });
|
||||||
|
}
|
||||||
|
if (newContext.allowedBackendEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedBackendEndpoints: newContext.allowedBackendEndpoints });
|
||||||
|
}
|
||||||
|
if (newContext.allowedMongoProxyEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedMongoProxyEndpoints: newContext.allowedMongoProxyEndpoints });
|
||||||
|
}
|
||||||
|
if (newContext.allowedCassandraProxyEndpoints) {
|
||||||
|
Object.assign(configContext, { allowedCassandraProxyEndpoints: newContext.allowedCassandraProxyEndpoints });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateEndpoint(newContext.AAD_ENDPOINT, configContext.allowedAadEndpoints)) {
|
||||||
delete newContext.AAD_ENDPOINT;
|
delete newContext.AAD_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints || defaultAllowedArmEndpoints)) {
|
if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints)) {
|
||||||
delete newContext.ARM_ENDPOINT;
|
delete newContext.ARM_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,9 +141,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
|||||||
delete newContext.EMULATOR_ENDPOINT;
|
delete newContext.EMULATOR_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!validateEndpoint(newContext.GRAPH_ENDPOINT, configContext.allowedGraphEndpoints)) {
|
||||||
!validateEndpoint(newContext.GRAPH_ENDPOINT, configContext.allowedGraphEndpoints || defaultAllowedGraphEndpoints)
|
|
||||||
) {
|
|
||||||
delete newContext.GRAPH_ENDPOINT;
|
delete newContext.GRAPH_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,30 +149,15 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
|||||||
delete newContext.ARCADIA_ENDPOINT;
|
delete newContext.ARCADIA_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!validateEndpoint(newContext.PORTAL_BACKEND_ENDPOINT, configContext.allowedBackendEndpoints)) {
|
||||||
!validateEndpoint(
|
|
||||||
newContext.PORTAL_BACKEND_ENDPOINT,
|
|
||||||
configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
delete newContext.PORTAL_BACKEND_ENDPOINT;
|
delete newContext.PORTAL_BACKEND_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!validateEndpoint(newContext.MONGO_PROXY_ENDPOINT, configContext.allowedMongoProxyEndpoints)) {
|
||||||
!validateEndpoint(
|
|
||||||
newContext.MONGO_PROXY_ENDPOINT,
|
|
||||||
configContext.allowedMongoProxyEndpoints || defaultAllowedMongoProxyEndpoints,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
delete newContext.MONGO_PROXY_ENDPOINT;
|
delete newContext.MONGO_PROXY_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!validateEndpoint(newContext.CASSANDRA_PROXY_ENDPOINT, configContext.allowedCassandraProxyEndpoints)) {
|
||||||
!validateEndpoint(
|
|
||||||
newContext.CASSANDRA_PROXY_ENDPOINT,
|
|
||||||
configContext.allowedCassandraProxyEndpoints || defaultAllowedCassandraProxyEndpoints,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
delete newContext.CASSANDRA_PROXY_ENDPOINT;
|
delete newContext.CASSANDRA_PROXY_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FabricMessageTypes } from "./FabricMessageTypes";
|
import { FabricMessageTypes } from "./FabricMessageTypes";
|
||||||
|
import { MessageTypes } from "./MessageTypes";
|
||||||
|
|
||||||
// This is the current version of these messages
|
// This is the current version of these messages
|
||||||
export const DATA_EXPLORER_RPC_VERSION = "3";
|
export const DATA_EXPLORER_RPC_VERSION = "3";
|
||||||
@@ -19,9 +20,32 @@ export type DataExploreMessageV3 =
|
|||||||
type: FabricMessageTypes.GetAllResourceTokens;
|
type: FabricMessageTypes.GetAllResourceTokens;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: FabricMessageTypes.GetAccessToken;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageTypes.TelemetryInfo;
|
||||||
|
data: {
|
||||||
|
action: string;
|
||||||
|
actionModifier: string;
|
||||||
|
data: unknown;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: FabricMessageTypes.OpenSettings;
|
type: FabricMessageTypes.OpenSettings;
|
||||||
settingsId: string;
|
params: [{ settingsId?: "About" | "Connection" }];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: FabricMessageTypes.RestoreContainer;
|
||||||
|
params: [];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: FabricMessageTypes.ContainerUpdated;
|
||||||
|
params: {
|
||||||
|
updateType: "created" | "deleted" | "settings";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export interface GetCosmosTokenMessageOptions {
|
export interface GetCosmosTokenMessageOptions {
|
||||||
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export enum FabricMessageTypes {
|
|||||||
GetAccessToken = "GetAccessToken",
|
GetAccessToken = "GetAccessToken",
|
||||||
Ready = "Ready",
|
Ready = "Ready",
|
||||||
OpenSettings = "OpenSettings",
|
OpenSettings = "OpenSettings",
|
||||||
|
RestoreContainer = "RestoreContainer",
|
||||||
|
ContainerUpdated = "ContainerUpdated",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthorizationToken {
|
export interface AuthorizationToken {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { FC, useEffect } from "react";
|
import React, { FC, useEffect } from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface DialogState {
|
export interface DialogState {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -38,7 +38,7 @@ export interface DialogState {
|
|||||||
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
|
showOkModalDialog: (title: string, subText: string, linkProps?: LinkProps) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDialog = create<DialogState>((set, get) => ({
|
export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||||
visible: false,
|
visible: false,
|
||||||
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
|
||||||
closeDialog: () =>
|
closeDialog: () =>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { FullTextIndex, FullTextPath, FullTextPolicy } from "Contracts/DataModels";
|
import { FullTextIndex, FullTextPath, FullTextPolicy } from "Contracts/DataModels";
|
||||||
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { isFullTextSearchPreviewFeaturesEnabled } from "Utils/CapabilityUtils";
|
||||||
|
|
||||||
export interface FullTextPoliciesComponentProps {
|
export interface FullTextPoliciesComponentProps {
|
||||||
fullTextPolicy: FullTextPolicy;
|
fullTextPolicy: FullTextPolicy;
|
||||||
@@ -22,6 +23,7 @@ export interface FullTextPoliciesComponentProps {
|
|||||||
) => void;
|
) => void;
|
||||||
discardChanges?: boolean;
|
discardChanges?: boolean;
|
||||||
onChangesDiscarded?: () => void;
|
onChangesDiscarded?: () => void;
|
||||||
|
englishOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FullTextPolicyData {
|
export interface FullTextPolicyData {
|
||||||
@@ -66,6 +68,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
onFullTextPathChange,
|
onFullTextPathChange,
|
||||||
discardChanges,
|
discardChanges,
|
||||||
onChangesDiscarded,
|
onChangesDiscarded,
|
||||||
|
englishOnly,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const getFullTextPathError = (path: string, index?: number): string => {
|
const getFullTextPathError = (path: string, index?: number): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
@@ -87,6 +90,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
if (!fullTextPolicy) {
|
if (!fullTextPolicy) {
|
||||||
fullTextPolicy = { defaultLanguage: getFullTextLanguageOptions()[0].key as never, fullTextPaths: [] };
|
fullTextPolicy = { defaultLanguage: getFullTextLanguageOptions()[0].key as never, fullTextPaths: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullTextPolicy.fullTextPaths.map((fullTextPath: FullTextPath) => ({
|
return fullTextPolicy.fullTextPaths.map((fullTextPath: FullTextPath) => ({
|
||||||
...fullTextPath,
|
...fullTextPath,
|
||||||
pathError: getFullTextPathError(fullTextPath.path),
|
pathError: getFullTextPathError(fullTextPath.path),
|
||||||
@@ -166,7 +170,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
required={true}
|
required={true}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
options={getFullTextLanguageOptions()}
|
options={getFullTextLanguageOptions(englishOnly)}
|
||||||
selectedKey={defaultLanguage}
|
selectedKey={defaultLanguage}
|
||||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
setDefaultLanguage(option.key as never)
|
setDefaultLanguage(option.key as never)
|
||||||
@@ -211,7 +215,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
required={true}
|
required={true}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
options={getFullTextLanguageOptions()}
|
options={getFullTextLanguageOptions(englishOnly)}
|
||||||
selectedKey={fullTextPolicy.language}
|
selectedKey={fullTextPolicy.language}
|
||||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
onFullTextPathPolicyChange(index, option)
|
onFullTextPathPolicyChange(index, option)
|
||||||
@@ -229,11 +233,30 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFullTextLanguageOptions = (): IDropdownOption[] => {
|
export const getFullTextLanguageOptions = (englishOnly?: boolean): IDropdownOption[] => {
|
||||||
return [
|
const multiLanguageSupportEnabled: boolean = isFullTextSearchPreviewFeaturesEnabled() && !englishOnly;
|
||||||
|
const fullTextLanguageOptions: IDropdownOption[] = [
|
||||||
{
|
{
|
||||||
key: "en-US",
|
key: "en-US",
|
||||||
text: "English (US)",
|
text: "English (US)",
|
||||||
},
|
},
|
||||||
|
...(multiLanguageSupportEnabled
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "fr-FR",
|
||||||
|
text: "French",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "de-DE",
|
||||||
|
text: "German",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "es-ES",
|
||||||
|
text: "Spanish",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return fullTextLanguageOptions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { IndexingPolicy } from "@azure/cosmos";
|
||||||
|
import { act } from "@testing-library/react";
|
||||||
import { AuthType } from "AuthType";
|
import { AuthType } from "AuthType";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
@@ -287,3 +290,47 @@ describe("SettingsComponent", () => {
|
|||||||
expect(wrapper.state("isThroughputBucketsSaveable")).toBe(false);
|
expect(wrapper.state("isThroughputBucketsSaveable")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("SettingsComponent - indexing policy subscription", () => {
|
||||||
|
const baseProps: SettingsComponentProps = {
|
||||||
|
settingsTab: new CollectionSettingsTabV2({
|
||||||
|
collection: collection,
|
||||||
|
tabKind: ViewModels.CollectionTabKind.CollectionSettingsV2,
|
||||||
|
title: "Scale & Settings",
|
||||||
|
tabPath: "",
|
||||||
|
node: undefined,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
it("subscribes to the correct container's indexing policy and updates state on change", async () => {
|
||||||
|
const containerId = collection.id();
|
||||||
|
const mockIndexingPolicy: IndexingPolicy = {
|
||||||
|
automatic: false,
|
||||||
|
indexingMode: "lazy",
|
||||||
|
includedPaths: [{ path: "/foo/*" }],
|
||||||
|
excludedPaths: [{ path: "/bar/*" }],
|
||||||
|
compositeIndexes: [],
|
||||||
|
spatialIndexes: [],
|
||||||
|
vectorIndexes: [],
|
||||||
|
fullTextIndexes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
|
const instance = wrapper.instance() as SettingsComponent;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
useIndexingPolicyStore.setState({
|
||||||
|
indexingPolicies: {
|
||||||
|
[containerId]: mockIndexingPolicy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(wrapper.state("indexingPolicyContent")).toEqual(mockIndexingPolicy);
|
||||||
|
expect(wrapper.state("indexingPolicyContentBaseline")).toEqual(mockIndexingPolicy);
|
||||||
|
// @ts-expect-error: rawDataModel is intentionally accessed for test validation
|
||||||
|
expect(instance.collection.rawDataModel.indexingPolicy).toEqual(mockIndexingPolicy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import {
|
import {
|
||||||
ComputedPropertiesComponent,
|
ComputedPropertiesComponent,
|
||||||
ComputedPropertiesComponentProps,
|
ComputedPropertiesComponentProps,
|
||||||
@@ -11,6 +13,7 @@ import {
|
|||||||
ThroughputBucketsComponent,
|
ThroughputBucketsComponent,
|
||||||
ThroughputBucketsComponentProps,
|
ThroughputBucketsComponentProps,
|
||||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
||||||
|
import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
@@ -70,7 +73,6 @@ import {
|
|||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure,
|
parseConflictResolutionProcedure,
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
tab: SettingsV2TabTypes;
|
tab: SettingsV2TabTypes;
|
||||||
content: JSX.Element;
|
content: JSX.Element;
|
||||||
@@ -173,7 +175,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private totalThroughputUsed: number;
|
private totalThroughputUsed: number;
|
||||||
private throughputBucketsEnabled: boolean;
|
private throughputBucketsEnabled: boolean;
|
||||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
private unsubscribe: () => void;
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -303,8 +305,19 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
this.unsubscribe = useIndexingPolicyStore.subscribe(
|
||||||
|
() => {
|
||||||
|
this.refreshCollectionData();
|
||||||
|
},
|
||||||
|
(state) => state.indexingPolicies[this.collection.id()],
|
||||||
|
);
|
||||||
|
this.refreshCollectionData();
|
||||||
|
}
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
@@ -431,6 +444,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.props.settingsTab.isExecuting(false);
|
this.props.settingsTab.isExecuting(false);
|
||||||
|
|
||||||
|
// Send message to Fabric no matter success or failure.
|
||||||
|
// In case of failure, saveCollectionSettings might have partially succeeded and Fabric needs to refresh
|
||||||
|
if (isFabricNative() && this.isCollectionSettingsTab) {
|
||||||
|
sendMessage({
|
||||||
|
type: FabricMessageTypes.ContainerUpdated,
|
||||||
|
params: { updateType: "settings" },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -777,7 +799,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
{ name: "name_of_property", query: "query_to_compute_property" },
|
{ name: "name_of_property", query: "query_to_compute_property" },
|
||||||
] as DataModels.ComputedProperties;
|
] as DataModels.ComputedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const throughputBuckets = this.offer?.throughputBuckets;
|
const throughputBuckets = this.offer?.throughputBuckets;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -929,10 +950,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
startKey,
|
startKey,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
private refreshCollectionData = async (): Promise<void> => {
|
||||||
|
const containerId = this.collection.id();
|
||||||
|
const latestIndexingPolicy = useIndexingPolicyStore.getState().indexingPolicies[containerId];
|
||||||
|
const rawPolicy = latestIndexingPolicy ?? this.collection.indexingPolicy();
|
||||||
|
|
||||||
|
const latestCollection: DataModels.IndexingPolicy = {
|
||||||
|
automatic: rawPolicy?.automatic ?? true,
|
||||||
|
indexingMode: rawPolicy?.indexingMode ?? "consistent",
|
||||||
|
includedPaths: rawPolicy?.includedPaths ?? [],
|
||||||
|
excludedPaths: rawPolicy?.excludedPaths ?? [],
|
||||||
|
compositeIndexes: rawPolicy?.compositeIndexes ?? [],
|
||||||
|
spatialIndexes: rawPolicy?.spatialIndexes ?? [],
|
||||||
|
vectorIndexes: rawPolicy?.vectorIndexes ?? [],
|
||||||
|
fullTextIndexes: rawPolicy?.fullTextIndexes ?? [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.collection.rawDataModel.indexingPolicy = latestCollection;
|
||||||
|
this.setState({
|
||||||
|
indexingPolicyContent: latestCollection,
|
||||||
|
indexingPolicyContentBaseline: latestCollection,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private saveCollectionSettings = async (startKey: number): Promise<void> => {
|
private saveCollectionSettings = async (startKey: number): Promise<void> => {
|
||||||
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
this.state.isContainerPolicyDirty ||
|
this.state.isContainerPolicyDirty ||
|
||||||
@@ -1161,7 +1203,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||||
throughputError: this.state.throughputError,
|
throughputError: this.state.throughputError,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.isCollectionSettingsTab) {
|
if (!this.isCollectionSettingsTab) {
|
||||||
return (
|
return (
|
||||||
<div className="settingsV2MainContainer">
|
<div className="settingsV2MainContainer">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { MessageBar, MessageBarType } from "@fluentui/react";
|
import { MessageBar, MessageBarType } from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||||
import {
|
import {
|
||||||
mongoIndexTransformationRefreshingMessage,
|
mongoIndexTransformationRefreshingMessage,
|
||||||
renderMongoIndexTransformationRefreshMessage,
|
renderMongoIndexTransformationRefreshMessage,
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
|
||||||
import { isIndexTransforming } from "../../SettingsUtils";
|
import { isIndexTransforming } from "../../SettingsUtils";
|
||||||
|
|
||||||
export interface IndexingPolicyRefreshComponentProps {
|
export interface IndexingPolicyRefreshComponentProps {
|
||||||
|
|||||||
@@ -72,6 +72,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
"rawDataModel": {
|
||||||
|
"indexingPolicy": {
|
||||||
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
|
"includedPaths": [],
|
||||||
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
|
},
|
||||||
"uniqueKeyPolicy": {
|
"uniqueKeyPolicy": {
|
||||||
"uniqueKeys": [
|
"uniqueKeys": [
|
||||||
{
|
{
|
||||||
@@ -164,6 +174,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
"rawDataModel": {
|
||||||
|
"indexingPolicy": {
|
||||||
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
|
"includedPaths": [],
|
||||||
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
|
},
|
||||||
"uniqueKeyPolicy": {
|
"uniqueKeyPolicy": {
|
||||||
"uniqueKeys": [
|
"uniqueKeys": [
|
||||||
{
|
{
|
||||||
@@ -238,17 +258,25 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
indexingPolicyContent={
|
indexingPolicyContent={
|
||||||
{
|
{
|
||||||
"automatic": true,
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
"excludedPaths": [],
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
"includedPaths": [],
|
"includedPaths": [],
|
||||||
"indexingMode": "consistent",
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
indexingPolicyContentBaseline={
|
indexingPolicyContentBaseline={
|
||||||
{
|
{
|
||||||
"automatic": true,
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
"excludedPaths": [],
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
"includedPaths": [],
|
"includedPaths": [],
|
||||||
"indexingMode": "consistent",
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isVectorSearchEnabled={false}
|
isVectorSearchEnabled={false}
|
||||||
@@ -321,6 +349,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
"rawDataModel": {
|
||||||
|
"indexingPolicy": {
|
||||||
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
|
"includedPaths": [],
|
||||||
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
|
},
|
||||||
"uniqueKeyPolicy": {
|
"uniqueKeyPolicy": {
|
||||||
"uniqueKeys": [
|
"uniqueKeys": [
|
||||||
{
|
{
|
||||||
@@ -461,6 +499,16 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
"rawDataModel": {
|
||||||
|
"indexingPolicy": {
|
||||||
|
"automatic": true,
|
||||||
|
"compositeIndexes": [],
|
||||||
|
"excludedPaths": [],
|
||||||
|
"fullTextIndexes": [],
|
||||||
|
"includedPaths": [],
|
||||||
|
"indexingMode": "consistent",
|
||||||
|
"spatialIndexes": [],
|
||||||
|
"vectorIndexes": [],
|
||||||
|
},
|
||||||
"uniqueKeyPolicy": {
|
"uniqueKeyPolicy": {
|
||||||
"uniqueKeys": [
|
"uniqueKeys": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { shallow } from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
@@ -112,8 +112,8 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.isNotebooksEnabledForAccount,
|
|
||||||
() => this.refreshCommandBarButtons(),
|
() => this.refreshCommandBarButtons(),
|
||||||
|
(state) => state.isNotebooksEnabledForAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
@@ -136,13 +136,13 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useTabs.subscribe(
|
useTabs.subscribe(
|
||||||
(state) => state.openedTabs,
|
|
||||||
(openedTabs: TabsBase[]) => {
|
(openedTabs: TabsBase[]) => {
|
||||||
if (openedTabs.length === 0) {
|
if (openedTabs.length === 0) {
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
(state) => state.openedTabs,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
@@ -170,9 +170,9 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
|
||||||
async () => this.initiateAndRefreshNotebookList(),
|
async () => this.initiateAndRefreshNotebookList(),
|
||||||
{ equalityFn: shallow },
|
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
||||||
|
shallow,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resourceTree = new ResourceTreeAdapter(this);
|
this.resourceTree = new ResourceTreeAdapter(this);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
|||||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
||||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
@@ -30,7 +30,7 @@ export interface CommandBarStore {
|
|||||||
setIsHidden: (isHidden: boolean) => void;
|
setIsHidden: (isHidden: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommandBar = create<CommandBarStore>((set) => ({
|
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
||||||
contextButtons: [] as CommandButtonComponentProps[],
|
contextButtons: [] as CommandButtonComponentProps[],
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -59,7 +59,7 @@ export class StatusBar extends React.Component<Props> {
|
|||||||
<Bar data-test="notebookStatusBar">
|
<Bar data-test="notebookStatusBar">
|
||||||
<RightStatus>
|
<RightStatus>
|
||||||
{this.props.lastSaved ? (
|
{this.props.lastSaved ? (
|
||||||
<p data-test="saveStatus"> Last saved {formatDistanceToNow(this.props.lastSaved)} ago </p>
|
<p data-test="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
||||||
) : (
|
) : (
|
||||||
<p> Not saved yet </p>
|
<p> Not saved yet </p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
|
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
|
||||||
@@ -67,274 +66,270 @@ interface NotebookState {
|
|||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook = create<NotebookState>()(
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
isNotebookEnabled: false,
|
||||||
(set, get) => ({
|
isNotebooksEnabledForAccount: false,
|
||||||
isNotebookEnabled: false,
|
notebookServerInfo: {
|
||||||
isNotebooksEnabledForAccount: false,
|
notebookServerEndpoint: undefined,
|
||||||
notebookServerInfo: {
|
authToken: undefined,
|
||||||
notebookServerEndpoint: "",
|
forwardingId: undefined,
|
||||||
authToken: "",
|
},
|
||||||
forwardingId: "",
|
sparkClusterConnectionInfo: {
|
||||||
},
|
userName: undefined,
|
||||||
sparkClusterConnectionInfo: {
|
password: undefined,
|
||||||
userName: "",
|
endpoints: [],
|
||||||
password: "",
|
},
|
||||||
endpoints: [] as DataModels.SparkClusterEndpoint[],
|
isSynapseLinkUpdating: false,
|
||||||
},
|
memoryUsageInfo: undefined,
|
||||||
isSynapseLinkUpdating: false,
|
isShellEnabled: false,
|
||||||
memoryUsageInfo: undefined as DataModels.MemoryUsageInfo,
|
notebookBasePath: Constants.Notebook.defaultBasePath,
|
||||||
isShellEnabled: false,
|
isInitializingNotebooks: false,
|
||||||
notebookBasePath: Constants.Notebook.defaultBasePath,
|
myNotebooksContentRoot: undefined,
|
||||||
isInitializingNotebooks: false,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
myNotebooksContentRoot: undefined as NotebookContentItem,
|
galleryContentRoot: undefined,
|
||||||
gitHubNotebooksContentRoot: undefined as NotebookContentItem,
|
connectionInfo: {
|
||||||
galleryContentRoot: undefined as NotebookContentItem,
|
status: ConnectionStatusType.Connect,
|
||||||
connectionInfo: {
|
},
|
||||||
status: ConnectionStatusType.Connect,
|
notebookFolderName: undefined,
|
||||||
},
|
isAllocating: false,
|
||||||
notebookFolderName: "",
|
isRefreshed: false,
|
||||||
isAllocating: false,
|
containerStatus: {
|
||||||
isRefreshed: false,
|
status: undefined,
|
||||||
containerStatus: {
|
durationLeftInMinutes: undefined,
|
||||||
status: undefined,
|
phoenixServerInfo: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
},
|
||||||
phoenixServerInfo: undefined,
|
isPhoenixNotebooks: undefined,
|
||||||
} as ContainerInfo,
|
isPhoenixFeatures: undefined,
|
||||||
isPhoenixNotebooks: undefined as boolean,
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
isPhoenixFeatures: undefined as boolean,
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
set({ notebookServerInfo }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
||||||
set({ notebookServerInfo }),
|
set({ sparkClusterConnectionInfo }),
|
||||||
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
||||||
set({ sparkClusterConnectionInfo }),
|
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||||
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
await get().getPhoenixStatus();
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
const { databaseAccount, authType } = userContext;
|
||||||
await get().getPhoenixStatus();
|
if (
|
||||||
const { databaseAccount, authType } = userContext;
|
authType === AuthType.EncryptedToken ||
|
||||||
if (
|
authType === AuthType.ResourceToken ||
|
||||||
authType === AuthType.EncryptedToken ||
|
authType === AuthType.MasterKey
|
||||||
authType === AuthType.ResourceToken ||
|
) {
|
||||||
authType === AuthType.MasterKey
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
) {
|
return;
|
||||||
set({ isNotebooksEnabledForAccount: false });
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstWriteLocation =
|
const firstWriteLocation =
|
||||||
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
||||||
? databaseAccount?.location
|
? databaseAccount?.location
|
||||||
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
||||||
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
|
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(disallowedLocationsUri, {
|
const response = await fetch(disallowedLocationsUri, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
[Constants.HttpHeaders.contentType]: "application/json",
|
[Constants.HttpHeaders.contentType]: "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch disallowed locations");
|
throw new Error("Failed to fetch disallowed locations");
|
||||||
}
|
}
|
||||||
|
|
||||||
const disallowedLocations: string[] = await response.json();
|
const disallowedLocations: string[] = await response.json();
|
||||||
if (!disallowedLocations) {
|
if (!disallowedLocations) {
|
||||||
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
||||||
set({ isNotebooksEnabledForAccount: true });
|
set({ isNotebooksEnabledForAccount: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// firstWriteLocation should not be disallowed
|
// firstWriteLocation should not be disallowed
|
||||||
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
||||||
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
||||||
set({ isNotebooksEnabledForAccount: false });
|
set({ isNotebooksEnabledForAccount: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
|
findItem: (root: NotebookContentItem, item: NotebookContentItem): NotebookContentItem => {
|
||||||
const currentItem = root || get().myNotebooksContentRoot;
|
const currentItem = root || get().myNotebooksContentRoot;
|
||||||
|
|
||||||
if (currentItem) {
|
if (currentItem) {
|
||||||
if (currentItem.path === item.path && currentItem.name === item.name) {
|
if (currentItem.path === item.path && currentItem.name === item.name) {
|
||||||
return currentItem;
|
return currentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentItem.children) {
|
if (currentItem.children) {
|
||||||
for (const childItem of currentItem.children) {
|
for (const childItem of currentItem.children) {
|
||||||
const result = get().findItem(childItem, item);
|
const result = get().findItem(childItem, item);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
|
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, parent);
|
const parentItem = get().findItem(root, parent);
|
||||||
item.parent = parentItem;
|
item.parent = parentItem;
|
||||||
if (parentItem.children) {
|
if (parentItem.children) {
|
||||||
parentItem.children.push(item);
|
parentItem.children.push(item);
|
||||||
} else {
|
} else {
|
||||||
parentItem.children = [item];
|
parentItem.children = [item];
|
||||||
}
|
}
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, item.parent);
|
const parentItem = get().findItem(root, item.parent);
|
||||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
parentItem.children.push(item);
|
parentItem.children.push(item);
|
||||||
item.parent = parentItem;
|
item.parent = parentItem;
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
|
||||||
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
|
||||||
const parentItem = get().findItem(root, item.parent);
|
const parentItem = get().findItem(root, item.parent);
|
||||||
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
|
||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
||||||
set({ notebookFolderName });
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().notebookFolderName,
|
||||||
path: get().notebookBasePath,
|
path: get().notebookBasePath,
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
const galleryContentRoot = {
|
||||||
|
name: "Gallery",
|
||||||
|
path: "Gallery",
|
||||||
|
type: NotebookContentItemType.File,
|
||||||
|
};
|
||||||
|
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||||
|
? {
|
||||||
|
name: "GitHub repos",
|
||||||
|
path: "PsuedoDir",
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
set({
|
||||||
|
myNotebooksContentRoot,
|
||||||
|
galleryContentRoot,
|
||||||
|
gitHubNotebooksContentRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (get().notebookServerInfo?.notebookServerEndpoint) {
|
||||||
|
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
|
||||||
|
set({ myNotebooksContentRoot: updatedRoot });
|
||||||
|
|
||||||
|
if (updatedRoot?.children) {
|
||||||
|
// Count 1st generation children (tree is lazy-loaded)
|
||||||
|
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
||||||
|
updatedRoot.children.forEach((notebookItem) => {
|
||||||
|
switch (notebookItem.type) {
|
||||||
|
case NotebookContentItemType.File:
|
||||||
|
nodeCounts.files++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Directory:
|
||||||
|
nodeCounts.directories++;
|
||||||
|
break;
|
||||||
|
case NotebookContentItemType.Notebook:
|
||||||
|
nodeCounts.notebooks++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
|
||||||
|
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
|
||||||
|
if (gitHubNotebooksContentRoot) {
|
||||||
|
gitHubNotebooksContentRoot.children = [];
|
||||||
|
pinnedRepos?.forEach((pinnedRepo) => {
|
||||||
|
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||||
|
const repoTreeItem: NotebookContentItem = {
|
||||||
|
name: repoFullName,
|
||||||
|
path: "PsuedoDir",
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
children: [],
|
||||||
|
parent: gitHubNotebooksContentRoot,
|
||||||
};
|
};
|
||||||
const galleryContentRoot = {
|
|
||||||
name: "Gallery",
|
pinnedRepo.branches.forEach((branch) => {
|
||||||
path: "Gallery",
|
repoTreeItem.children.push({
|
||||||
type: NotebookContentItemType.File,
|
name: branch.name,
|
||||||
};
|
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
||||||
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
|
||||||
? {
|
|
||||||
name: "GitHub repos",
|
|
||||||
path: "PsuedoDir",
|
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
}
|
parent: repoTreeItem,
|
||||||
: undefined;
|
|
||||||
|
|
||||||
set({
|
|
||||||
myNotebooksContentRoot,
|
|
||||||
galleryContentRoot,
|
|
||||||
gitHubNotebooksContentRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (get().notebookServerInfo?.notebookServerEndpoint) {
|
|
||||||
const updatedRoot = await notebookManager?.notebookContentClient?.updateItemChildren(myNotebooksContentRoot);
|
|
||||||
set({ myNotebooksContentRoot: updatedRoot });
|
|
||||||
|
|
||||||
if (updatedRoot?.children) {
|
|
||||||
// Count 1st generation children (tree is lazy-loaded)
|
|
||||||
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
|
||||||
updatedRoot.children.forEach((notebookItem) => {
|
|
||||||
switch (notebookItem.type) {
|
|
||||||
case NotebookContentItemType.File:
|
|
||||||
nodeCounts.files++;
|
|
||||||
break;
|
|
||||||
case NotebookContentItemType.Directory:
|
|
||||||
nodeCounts.directories++;
|
|
||||||
break;
|
|
||||||
case NotebookContentItemType.Notebook:
|
|
||||||
nodeCounts.notebooks++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]): void => {
|
|
||||||
const gitHubNotebooksContentRoot = cloneDeep(get().gitHubNotebooksContentRoot);
|
|
||||||
if (gitHubNotebooksContentRoot) {
|
|
||||||
gitHubNotebooksContentRoot.children = [];
|
|
||||||
pinnedRepos?.forEach((pinnedRepo) => {
|
|
||||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
|
||||||
const repoTreeItem: NotebookContentItem = {
|
|
||||||
name: repoFullName,
|
|
||||||
path: "PsuedoDir",
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
children: [],
|
|
||||||
parent: gitHubNotebooksContentRoot,
|
|
||||||
};
|
|
||||||
|
|
||||||
pinnedRepo.branches.forEach((branch) => {
|
|
||||||
repoTreeItem.children.push({
|
|
||||||
name: branch.name,
|
|
||||||
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
parent: repoTreeItem,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
set({ gitHubNotebooksContentRoot });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
|
||||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
|
||||||
useNotebook.getState().setIsAllocating(false);
|
|
||||||
useNotebook.getState().setContainerStatus({
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
getPhoenixStatus: async () => {
|
|
||||||
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
|
||||||
let isPhoenixNotebooks = false;
|
|
||||||
let isPhoenixFeatures = false;
|
|
||||||
|
|
||||||
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
gitHubNotebooksContentRoot.children.push(repoTreeItem);
|
||||||
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
});
|
||||||
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
|
||||||
|
|
||||||
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
set({ gitHubNotebooksContentRoot });
|
||||||
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
}
|
||||||
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
},
|
||||||
isPhoenixFeatures =
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
isPublicInternetAllowed &&
|
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||||
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
|
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||||
(userContext.features.phoenixFeatures === true ||
|
useTabs.getState().closeAllNotebookTabs(true);
|
||||||
userContext.apiType === "Postgres" ||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
userContext.apiType === "VCoreMongo");
|
useNotebook.getState().setNotebookServerInfo(undefined);
|
||||||
} else {
|
useNotebook.getState().setIsAllocating(false);
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
useNotebook.getState().setContainerStatus({
|
||||||
}
|
status: undefined,
|
||||||
} else {
|
durationLeftInMinutes: undefined,
|
||||||
isPhoenixNotebooks = isPhoenixFeatures = false;
|
phoenixServerInfo: undefined,
|
||||||
}
|
});
|
||||||
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
},
|
||||||
set({ isPhoenixFeatures: isPhoenixFeatures });
|
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||||
|
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||||
|
getPhoenixStatus: async () => {
|
||||||
|
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
||||||
|
let isPhoenixNotebooks = false;
|
||||||
|
let isPhoenixFeatures = false;
|
||||||
|
|
||||||
|
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
||||||
|
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||||
|
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
||||||
|
|
||||||
|
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
||||||
|
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
||||||
|
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
||||||
|
isPhoenixFeatures =
|
||||||
|
isPublicInternetAllowed &&
|
||||||
|
// phoenix needs to be enabled for Postgres and VCoreMongo accounts since the PSQL and mongo shell requires phoenix containers
|
||||||
|
(userContext.features.phoenixFeatures === true ||
|
||||||
|
userContext.apiType === "Postgres" ||
|
||||||
|
userContext.apiType === "VCoreMongo");
|
||||||
|
} else {
|
||||||
|
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
isPhoenixNotebooks = isPhoenixFeatures = false;
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
}
|
||||||
})
|
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
||||||
)
|
set({ isPhoenixFeatures: isPhoenixFeatures });
|
||||||
);
|
}
|
||||||
|
},
|
||||||
|
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
||||||
|
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
||||||
|
}));
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CollectionCreation } from "Shared/Constants";
|
import { CollectionCreation } from "Shared/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -52,7 +52,6 @@ import { getCollectionName } from "Utils/APITypeUtils";
|
|||||||
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
import { getUpsellMessage } from "Utils/PricingUtils";
|
||||||
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator";
|
||||||
@@ -894,6 +893,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
) => {
|
) => {
|
||||||
this.setState({ fullTextPolicy, fullTextIndexes, fullTextPolicyValidated });
|
this.setState({ fullTextPolicy, fullTextIndexes, fullTextPolicyValidated });
|
||||||
}}
|
}}
|
||||||
|
// Remove when multi language support on container create issue is fixed
|
||||||
|
englishOnly={true}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1355,8 +1356,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
// Throughput
|
// Throughput
|
||||||
if (isFabricNative()) {
|
if (isFabricNative()) {
|
||||||
// Fabric Native accounts are always autoscale and have a fixed throughput of 5K
|
autoPilotMaxThroughput = DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT;
|
||||||
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput5K;
|
|
||||||
offerThroughput = undefined;
|
offerThroughput = undefined;
|
||||||
} else if (databaseLevelThroughput) {
|
} else if (databaseLevelThroughput) {
|
||||||
if (this.state.createNewDatabase) {
|
if (this.state.createNewDatabase) {
|
||||||
|
|||||||
@@ -516,6 +516,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FullTextPoliciesComponent
|
<FullTextPoliciesComponent
|
||||||
|
englishOnly={true}
|
||||||
fullTextPolicy={
|
fullTextPolicy={
|
||||||
{
|
{
|
||||||
"defaultLanguage": "en-US",
|
"defaultLanguage": "en-US",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
IToggleStyles,
|
IToggleStyles,
|
||||||
Position,
|
Position,
|
||||||
SpinButton,
|
SpinButton,
|
||||||
|
Stack,
|
||||||
Toggle,
|
Toggle,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, makeStyles } from "@fluentui/react-components";
|
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, makeStyles } from "@fluentui/react-components";
|
||||||
@@ -51,7 +52,7 @@ import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
|||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
@@ -65,6 +66,8 @@ export interface DataPlaneRbacState {
|
|||||||
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
|
setAadDataPlaneUpdated: (aadTokenUpdated: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DataPlaneRbacStore = UseStore<Partial<DataPlaneRbacState>>;
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
bulletList: {
|
bulletList: {
|
||||||
listStyleType: "disc",
|
listStyleType: "disc",
|
||||||
@@ -98,7 +101,7 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useDataPlaneRbac = create<Partial<DataPlaneRbacState>>(() => ({
|
export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({
|
||||||
dataPlaneRbacEnabled: false,
|
dataPlaneRbacEnabled: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -202,6 +205,9 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
? (LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation) as Constants.MongoGuidRepresentation)
|
? (LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation) as Constants.MongoGuidRepresentation)
|
||||||
: Constants.MongoGuidRepresentation.CSharpLegacy,
|
: Constants.MongoGuidRepresentation.CSharpLegacy,
|
||||||
);
|
);
|
||||||
|
const [ignorePartitionKeyOnDocumentUpdate, setIgnorePartitionKeyOnDocumentUpdate] = useState<boolean>(
|
||||||
|
LocalStorageUtility.getEntryBoolean(StorageKey.IgnorePartitionKeyOnDocumentUpdate),
|
||||||
|
);
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
@@ -422,6 +428,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
LocalStorageUtility.setEntryString(StorageKey.MongoGuidRepresentation, mongoGuidRepresentation);
|
LocalStorageUtility.setEntryString(StorageKey.MongoGuidRepresentation, mongoGuidRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advanced settings
|
||||||
|
LocalStorageUtility.setEntryBoolean(
|
||||||
|
StorageKey.IgnorePartitionKeyOnDocumentUpdate,
|
||||||
|
ignorePartitionKeyOnDocumentUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||||
@@ -451,6 +463,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logConsoleInfo(
|
||||||
|
`${ignorePartitionKeyOnDocumentUpdate ? "Enabled" : "Disabled"} ignoring partition key on document update`,
|
||||||
|
);
|
||||||
|
|
||||||
refreshExplorer && (await explorer.refreshExplorer());
|
refreshExplorer && (await explorer.refreshExplorer());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
@@ -591,6 +607,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setMongoGuidRepresentation(option.key as Constants.MongoGuidRepresentation);
|
setMongoGuidRepresentation(option.key as Constants.MongoGuidRepresentation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnIgnorePartitionKeyOnDocumentUpdateChange = (
|
||||||
|
ev: React.MouseEvent<HTMLElement>,
|
||||||
|
checked?: boolean,
|
||||||
|
): void => {
|
||||||
|
setIgnorePartitionKeyOnDocumentUpdate(!!checked);
|
||||||
|
};
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
@@ -1135,6 +1158,29 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
|
<AccordionItem value="15">
|
||||||
|
<AccordionHeader>
|
||||||
|
<div className={styles.header}>Advanced Settings</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<div className={styles.settingsSectionContainer}>
|
||||||
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
|
||||||
|
<Checkbox
|
||||||
|
styles={{ label: { padding: 0 } }}
|
||||||
|
className="padding"
|
||||||
|
ariaLabel="Ignore partition key on document update"
|
||||||
|
checked={ignorePartitionKeyOnDocumentUpdate}
|
||||||
|
onChange={handleOnIgnorePartitionKeyOnDocumentUpdateChange}
|
||||||
|
label="Ignore partition key on document update"
|
||||||
|
/>
|
||||||
|
<InfoTooltip className={styles.headerIcon}>
|
||||||
|
If checked, the partition key value will not be used to locate the document during update
|
||||||
|
operations. Only use this if document updates are failing due to an abnormal partition key.
|
||||||
|
</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -575,6 +575,52 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
<AccordionItem
|
||||||
|
value="15"
|
||||||
|
>
|
||||||
|
<AccordionHeader>
|
||||||
|
<div
|
||||||
|
className="___15c001r_0000000 fq02s40"
|
||||||
|
>
|
||||||
|
Advanced Settings
|
||||||
|
</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<div
|
||||||
|
className="___1dfa554_0000000 fo7qwa0"
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
{
|
||||||
|
"childrenGap": 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verticalAlign="center"
|
||||||
|
>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
ariaLabel="Ignore partition key on document update"
|
||||||
|
checked={false}
|
||||||
|
className="padding"
|
||||||
|
label="Ignore partition key on document update"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
{
|
||||||
|
"label": {
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InfoTooltip
|
||||||
|
className="___vtc5hy0_0000000 f10ra9hq f1k6fduh"
|
||||||
|
>
|
||||||
|
If checked, the partition key value will not be used to locate the document during update operations. Only use this if document updates are failing due to an abnormal partition key.
|
||||||
|
</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
@@ -838,6 +884,52 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
<AccordionItem
|
||||||
|
value="15"
|
||||||
|
>
|
||||||
|
<AccordionHeader>
|
||||||
|
<div
|
||||||
|
className="___15c001r_0000000 fq02s40"
|
||||||
|
>
|
||||||
|
Advanced Settings
|
||||||
|
</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<div
|
||||||
|
className="___1dfa554_0000000 fo7qwa0"
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
{
|
||||||
|
"childrenGap": 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verticalAlign="center"
|
||||||
|
>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
ariaLabel="Ignore partition key on document update"
|
||||||
|
checked={false}
|
||||||
|
className="padding"
|
||||||
|
label="Ignore partition key on document update"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
{
|
||||||
|
"label": {
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InfoTooltip
|
||||||
|
className="___vtc5hy0_0000000 f10ra9hq f1k6fduh"
|
||||||
|
>
|
||||||
|
If checked, the partition key value will not be used to locate the document during update operations. Only use this if document updates are failing due to an abnormal partition key.
|
||||||
|
</InfoTooltip>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<div
|
<div
|
||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { Upload } from "Common/Upload/Upload";
|
import { Upload } from "Common/Upload/Upload";
|
||||||
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, useReducer, useState } from "react";
|
||||||
import { getErrorMessage } from "../../Tables/Utilities";
|
import { getErrorMessage } from "../../Tables/Utilities";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
@@ -57,6 +57,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
|||||||
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
|
const [reducer, setReducer] = useReducer((x) => x + 1, 1);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
setFormError("");
|
setFormError("");
|
||||||
@@ -75,6 +76,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
|||||||
(uploadDetails) => {
|
(uploadDetails) => {
|
||||||
setUploadFileData(uploadDetails.data);
|
setUploadFileData(uploadDetails.data);
|
||||||
setFiles(undefined);
|
setFiles(undefined);
|
||||||
|
setReducer(); // Trigger a re-render to update the UI with new upload details
|
||||||
// Emit the upload details to the parent component
|
// Emit the upload details to the parent component
|
||||||
onUpload && onUpload(uploadDetails.data);
|
onUpload && onUpload(uploadDetails.data);
|
||||||
},
|
},
|
||||||
@@ -95,6 +97,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
|||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError,
|
formError,
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
|
isSubmitButtonDisabled: !files || files.length === 0,
|
||||||
submitButtonText: "Upload",
|
submitButtonText: "Upload",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
@@ -192,6 +195,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
|
|||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload
|
<Upload
|
||||||
|
key={reducer} // Force re-render on state change
|
||||||
label="Select JSON Files"
|
label="Select JSON Files"
|
||||||
onUpload={updateSelectedFiles}
|
onUpload={updateSelectedFiles}
|
||||||
accept="application/json"
|
accept="application/json"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
exports[`Upload Items Pane should render Default properly 1`] = `
|
exports[`Upload Items Pane should render Default properly 1`] = `
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
|
isSubmitButtonDisabled={true}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Upload"
|
submitButtonText="Upload"
|
||||||
>
|
>
|
||||||
@@ -11,6 +12,7 @@ exports[`Upload Items Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
accept="application/json"
|
accept="application/json"
|
||||||
|
key="1"
|
||||||
label="Select JSON Files"
|
label="Select JSON Files"
|
||||||
multiple={true}
|
multiple={true}
|
||||||
onUpload={[Function]}
|
onUpload={[Function]}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||||
import QueryError from "Common/QueryError";
|
import QueryError from "Common/QueryError";
|
||||||
import * as DataModels from "Contracts/DataModels";
|
|
||||||
import { QueryResults } from "Contracts/ViewModels";
|
import { QueryResults } from "Contracts/ViewModels";
|
||||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
import { guid } from "Explorer/Tables/Utilities";
|
import { guid } from "Explorer/Tables/Utilities";
|
||||||
import { QueryCopilotState } from "hooks/useQueryCopilot";
|
import { QueryCopilotState } from "hooks/useQueryCopilot";
|
||||||
|
|
||||||
import React, { createContext, useContext, useState } from "react";
|
import React, { createContext, useContext, useState } from "react";
|
||||||
import { create } from "zustand";
|
import create from "zustand";
|
||||||
|
|
||||||
const context = createContext(null);
|
const context = createContext(null);
|
||||||
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
|
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
|
||||||
|
|
||||||
@@ -26,12 +24,12 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
isGeneratingQuery: false,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as boolean,
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults,
|
queryResults: undefined,
|
||||||
errors: [] as QueryError[],
|
errors: [],
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showPromptTeachingBubble: true,
|
showPromptTeachingBubble: true,
|
||||||
showDeletePopup: false,
|
showDeletePopup: false,
|
||||||
@@ -43,7 +41,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showWelcomeSidebar: true,
|
showWelcomeSidebar: true,
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
@@ -88,7 +86,7 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetQueryCopilotStates: () => {
|
resetQueryCopilotStates: () => {
|
||||||
set((state: QueryCopilotState) => ({
|
set((state) => ({
|
||||||
...state,
|
...state,
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
@@ -101,11 +99,11 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
isGeneratingQuery: false,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as boolean,
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults,
|
queryResults: undefined,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showPromptTeachingBubble: true,
|
showPromptTeachingBubble: true,
|
||||||
@@ -117,19 +115,19 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
generatedQueryComments: "",
|
generatedQueryComments: "",
|
||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
forwardingId: undefined,
|
||||||
} as DataModels.NotebookWorkspaceConnectionInfo,
|
},
|
||||||
containerStatus: {
|
containerStatus: {
|
||||||
status: undefined,
|
status: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
durationLeftInMinutes: undefined,
|
||||||
phoenixServerInfo: undefined,
|
phoenixServerInfo: undefined,
|
||||||
} as DataModels.ContainerInfo,
|
},
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
@@ -139,4 +137,3 @@ const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Eleme
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { CopilotProvider, useCopilotStore };
|
export { CopilotProvider, useCopilotStore };
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
*/
|
*/
|
||||||
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
import { DocumentAddRegular, LinkMultipleRegular, OpenRegular } from "@fluentui/react-icons";
|
import { DocumentAddRegular, LinkMultipleRegular, OpenRegular } from "@fluentui/react-icons";
|
||||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
import { SampleDataConfiguration, SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||||
|
import { SampleDataFile } from "Explorer/SplashScreen/SampleUtil";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import AzureOpenAiIcon from "../../../images/AzureOpenAi.svg";
|
||||||
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
||||||
|
import GithubIcon from "../../../images/github-black-and-white.svg";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
export interface SplashScreenProps {
|
export interface SplashScreenProps {
|
||||||
@@ -26,11 +29,11 @@ const useStyles = makeStyles({
|
|||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
buttonsContainer: {
|
buttonsContainer: {
|
||||||
width: "584px",
|
width: "760px",
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
display: "grid",
|
display: "grid",
|
||||||
padding: "16px",
|
padding: "16px",
|
||||||
gridTemplateColumns: "repeat(3, 1fr)",
|
gridTemplateColumns: "repeat(4, 1fr)",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
gridAutoRows: "minmax(184px, auto)",
|
gridAutoRows: "minmax(184px, auto)",
|
||||||
},
|
},
|
||||||
@@ -53,6 +56,15 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
three: {
|
three: {
|
||||||
|
gridColumn: "4",
|
||||||
|
gridRow: "1",
|
||||||
|
"& img": {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
four: {
|
||||||
gridColumn: "3",
|
gridColumn: "3",
|
||||||
gridRow: "2",
|
gridRow: "2",
|
||||||
"& svg": {
|
"& svg": {
|
||||||
@@ -61,6 +73,15 @@ const useStyles = makeStyles({
|
|||||||
margin: "auto",
|
margin: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
five: {
|
||||||
|
gridColumn: "4",
|
||||||
|
gridRow: "2",
|
||||||
|
"& img": {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
single: {
|
single: {
|
||||||
gridColumn: "1 / 4",
|
gridColumn: "1 / 4",
|
||||||
gridRow: "1 / 3",
|
gridRow: "1 / 3",
|
||||||
@@ -132,6 +153,8 @@ const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className
|
|||||||
export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScreenProps) => {
|
export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScreenProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const [openSampleDataImportDialog, setOpenSampleDataImportDialog] = React.useState(false);
|
const [openSampleDataImportDialog, setOpenSampleDataImportDialog] = React.useState(false);
|
||||||
|
const [selectedSampleDataConfiguration, setSelectedSampleDataConfiguration] =
|
||||||
|
React.useState<SampleDataConfiguration>(undefined);
|
||||||
|
|
||||||
const getSplashScreenButtons = (): JSX.Element => {
|
const getSplashScreenButtons = (): JSX.Element => {
|
||||||
const buttons: FabricHomeScreenButtonProps[] = [
|
const buttons: FabricHomeScreenButtonProps[] = [
|
||||||
@@ -145,10 +168,30 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Sample data",
|
title: "Sample Data",
|
||||||
description: "Automatically load sample data in your database",
|
description: "Load sample data in your database",
|
||||||
icon: <img src={CosmosDbBlackIcon} alt={"Azure Cosmos DB icon"} aria-hidden="true" />,
|
icon: <img src={CosmosDbBlackIcon} alt={"Azure Cosmos DB icon"} aria-hidden="true" />,
|
||||||
onClick: () => setOpenSampleDataImportDialog(true),
|
onClick: () => {
|
||||||
|
setSelectedSampleDataConfiguration({
|
||||||
|
databaseName: userContext.fabricContext?.databaseName,
|
||||||
|
newContainerName: "SampleData",
|
||||||
|
sampleDataFile: SampleDataFile.FABRIC_SAMPLE_DATA,
|
||||||
|
});
|
||||||
|
setOpenSampleDataImportDialog(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sample Vector Data",
|
||||||
|
description: "Load sample vector data with text-embedding-ada-002",
|
||||||
|
icon: <img src={AzureOpenAiIcon} alt={"Azure Open AI icon"} aria-hidden="true" />,
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedSampleDataConfiguration({
|
||||||
|
databaseName: userContext.fabricContext?.databaseName,
|
||||||
|
newContainerName: "SampleVectorData",
|
||||||
|
sampleDataFile: SampleDataFile.FABRIC_SAMPLE_VECTOR_DATA,
|
||||||
|
});
|
||||||
|
setOpenSampleDataImportDialog(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "App development",
|
title: "App development",
|
||||||
@@ -156,17 +199,25 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
icon: <LinkMultipleRegular />,
|
icon: <LinkMultipleRegular />,
|
||||||
onClick: () => window.open("https://aka.ms/cosmosdbfabricsdk", "_blank"),
|
onClick: () => window.open("https://aka.ms/cosmosdbfabricsdk", "_blank"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Sample Gallery",
|
||||||
|
description: "Get real-world end-to-end samples",
|
||||||
|
icon: <img src={GithubIcon} alt={"GitHub icon"} aria-hidden="true" />,
|
||||||
|
onClick: () => window.open("https://aka.ms/CosmosFabricSamplesGallery", "_blank"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return isFabricNativeReadOnly() ? (
|
return isFabricNativeReadOnly() ? (
|
||||||
<div className={styles.buttonsContainer}>
|
<div className={styles.buttonsContainer}>
|
||||||
<FabricHomeScreenButton className={styles.single} {...buttons[2]} />
|
<FabricHomeScreenButton className={styles.single} {...buttons[3]} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.buttonsContainer}>
|
<div className={styles.buttonsContainer}>
|
||||||
<FabricHomeScreenButton className={styles.one} {...buttons[0]} />
|
<FabricHomeScreenButton className={styles.one} {...buttons[0]} />
|
||||||
<FabricHomeScreenButton className={styles.two} {...buttons[1]} />
|
<FabricHomeScreenButton className={styles.two} {...buttons[1]} />
|
||||||
<FabricHomeScreenButton className={styles.three} {...buttons[2]} />
|
<FabricHomeScreenButton className={styles.three} {...buttons[2]} />
|
||||||
|
<FabricHomeScreenButton className={styles.four} {...buttons[3]} />
|
||||||
|
<FabricHomeScreenButton className={styles.five} {...buttons[4]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -179,7 +230,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
open={openSampleDataImportDialog}
|
open={openSampleDataImportDialog}
|
||||||
setOpen={setOpenSampleDataImportDialog}
|
setOpen={setOpenSampleDataImportDialog}
|
||||||
explorer={props.explorer}
|
explorer={props.explorer}
|
||||||
databaseName={userContext.fabricContext?.databaseName}
|
sampleDataConfiguration={selectedSampleDataConfiguration}
|
||||||
/>
|
/>
|
||||||
<div className={styles.title} role="heading" aria-label={title} aria-level={1}>
|
<div className={styles.title} role="heading" aria-label={title} aria-level={1}>
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -11,12 +11,10 @@ import {
|
|||||||
tokens,
|
tokens,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { checkContainerExists, createContainer, importData } from "Explorer/SplashScreen/SampleUtil";
|
import { checkContainerExists, createContainer, importData, SampleDataFile } from "Explorer/SplashScreen/SampleUtil";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
|
||||||
const SAMPLE_DATA_CONTAINER_NAME = "SampleData";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -24,6 +22,12 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface SampleDataConfiguration {
|
||||||
|
databaseName: string;
|
||||||
|
newContainerName: string;
|
||||||
|
sampleDataFile: SampleDataFile;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This dialog:
|
* This dialog:
|
||||||
* - creates a container
|
* - creates a container
|
||||||
@@ -35,11 +39,11 @@ export const SampleDataImportDialog: React.FC<{
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
databaseName: string;
|
sampleDataConfiguration: SampleDataConfiguration | undefined;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [status, setStatus] = useState<"idle" | "creating" | "importing" | "completed" | "error">("idle");
|
const [status, setStatus] = useState<"idle" | "creating" | "importing" | "completed" | "error">("idle");
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
const containerName = SAMPLE_DATA_CONTAINER_NAME;
|
const containerName = props.sampleDataConfiguration?.newContainerName;
|
||||||
const [collection, setCollection] = useState<ViewModels.Collection>(undefined);
|
const [collection, setCollection] = useState<ViewModels.Collection>(undefined);
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
@@ -53,7 +57,7 @@ export const SampleDataImportDialog: React.FC<{
|
|||||||
|
|
||||||
const handleStartImport = async (): Promise<void> => {
|
const handleStartImport = async (): Promise<void> => {
|
||||||
setStatus("creating");
|
setStatus("creating");
|
||||||
const databaseName = props.databaseName;
|
const databaseName = props.sampleDataConfiguration.databaseName;
|
||||||
if (checkContainerExists(databaseName, containerName)) {
|
if (checkContainerExists(databaseName, containerName)) {
|
||||||
const msg = `The container "${containerName}" in database "${databaseName}" already exists. Please delete it and retry.`;
|
const msg = `The container "${containerName}" in database "${databaseName}" already exists. Please delete it and retry.`;
|
||||||
setStatus("error");
|
setStatus("error");
|
||||||
@@ -63,7 +67,12 @@ export const SampleDataImportDialog: React.FC<{
|
|||||||
|
|
||||||
let collection;
|
let collection;
|
||||||
try {
|
try {
|
||||||
collection = await createContainer(databaseName, containerName, props.explorer);
|
collection = await createContainer(
|
||||||
|
databaseName,
|
||||||
|
containerName,
|
||||||
|
props.explorer,
|
||||||
|
props.sampleDataConfiguration.sampleDataFile,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setStatus("error");
|
setStatus("error");
|
||||||
setErrorMessage(`Failed to create container: ${error instanceof Error ? error.message : String(error)}`);
|
setErrorMessage(`Failed to create container: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
@@ -72,7 +81,7 @@ export const SampleDataImportDialog: React.FC<{
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setStatus("importing");
|
setStatus("importing");
|
||||||
await importData(collection);
|
await importData(props.sampleDataConfiguration.sampleDataFile, collection);
|
||||||
setCollection(collection);
|
setCollection(collection);
|
||||||
setStatus("completed");
|
setStatus("completed");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import { JSONObject } from "@azure/cosmos";
|
||||||
import { BackendDefaults } from "Common/Constants";
|
import { BackendDefaults } from "Common/Constants";
|
||||||
import { createCollection } from "Common/dataAccess/createCollection";
|
import { createCollection } from "Common/dataAccess/createCollection";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import { DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
|
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public for unit tests
|
* Public for unit tests
|
||||||
@@ -26,21 +30,81 @@ const hasContainer = (
|
|||||||
export const checkContainerExists = (databaseName: string, containerName: string) =>
|
export const checkContainerExists = (databaseName: string, containerName: string) =>
|
||||||
hasContainer(databaseName, containerName, useDatabases.getState().databases);
|
hasContainer(databaseName, containerName, useDatabases.getState().databases);
|
||||||
|
|
||||||
|
export enum SampleDataFile {
|
||||||
|
COPILOT = "Copilot",
|
||||||
|
FABRIC_SAMPLE_DATA = "FabricSampleData",
|
||||||
|
FABRIC_SAMPLE_VECTOR_DATA = "FabricSampleVectorData",
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerSettings: {
|
||||||
|
[key in SampleDataFile]: {
|
||||||
|
partitionKeyString: string;
|
||||||
|
vectorEmbeddingPolicy?: DataModels.VectorEmbeddingPolicy;
|
||||||
|
indexingPolicy?: DataModels.IndexingPolicy;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
[SampleDataFile.COPILOT]: {
|
||||||
|
partitionKeyString: "category",
|
||||||
|
},
|
||||||
|
[SampleDataFile.FABRIC_SAMPLE_DATA]: {
|
||||||
|
partitionKeyString: "categoryName",
|
||||||
|
},
|
||||||
|
[SampleDataFile.FABRIC_SAMPLE_VECTOR_DATA]: {
|
||||||
|
partitionKeyString: "categoryName",
|
||||||
|
vectorEmbeddingPolicy: {
|
||||||
|
vectorEmbeddings: [
|
||||||
|
{
|
||||||
|
path: "/vectors",
|
||||||
|
dataType: "float32",
|
||||||
|
distanceFunction: "cosine",
|
||||||
|
dimensions: 1536,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
indexingPolicy: {
|
||||||
|
automatic: true,
|
||||||
|
indexingMode: "consistent",
|
||||||
|
includedPaths: [
|
||||||
|
{
|
||||||
|
path: "/*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
excludedPaths: [
|
||||||
|
{
|
||||||
|
path: '/"_etag"/?',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fullTextIndexes: [],
|
||||||
|
vectorIndexes: [
|
||||||
|
{
|
||||||
|
path: "/vectors",
|
||||||
|
type: "quantizedFlat",
|
||||||
|
quantizationByteSize: 64,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const createContainer = async (
|
export const createContainer = async (
|
||||||
databaseName: string,
|
databaseName: string,
|
||||||
containerName: string,
|
containerName: string,
|
||||||
explorer: Explorer,
|
explorer: Explorer,
|
||||||
|
sampleDataFile: SampleDataFile,
|
||||||
): Promise<ViewModels.Collection> => {
|
): Promise<ViewModels.Collection> => {
|
||||||
const createRequest: DataModels.CreateCollectionParams = {
|
const createRequest: DataModels.CreateCollectionParams = {
|
||||||
|
autoPilotMaxThroughput: isFabricNative() ? DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT : undefined,
|
||||||
createNewDatabase: false,
|
createNewDatabase: false,
|
||||||
collectionId: containerName,
|
collectionId: containerName,
|
||||||
databaseId: databaseName,
|
databaseId: databaseName,
|
||||||
databaseLevelThroughput: false,
|
databaseLevelThroughput: false,
|
||||||
partitionKey: {
|
partitionKey: {
|
||||||
paths: [`/${SAMPLE_DATA_PARTITION_KEY}`],
|
paths: [`/${containerSettings[sampleDataFile].partitionKeyString}`],
|
||||||
kind: "Hash",
|
kind: "Hash",
|
||||||
version: BackendDefaults.partitionKeyVersion,
|
version: BackendDefaults.partitionKeyVersion,
|
||||||
},
|
},
|
||||||
|
vectorEmbeddingPolicy: containerSettings[sampleDataFile].vectorEmbeddingPolicy,
|
||||||
|
indexingPolicy: containerSettings[sampleDataFile].indexingPolicy,
|
||||||
};
|
};
|
||||||
await createCollection(createRequest);
|
await createCollection(createRequest);
|
||||||
await explorer.refreshAllDatabases();
|
await explorer.refreshAllDatabases();
|
||||||
@@ -53,12 +117,39 @@ export const createContainer = async (
|
|||||||
return newCollection;
|
return newCollection;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SAMPLE_DATA_PARTITION_KEY = "category"; // This pkey is specifically set for queryCopilotSampleData.json below
|
export const importData = async (sampleDataFile: SampleDataFile, collection: ViewModels.Collection): Promise<void> => {
|
||||||
|
let documents: JSONObject[] = undefined;
|
||||||
|
switch (sampleDataFile) {
|
||||||
|
case SampleDataFile.COPILOT:
|
||||||
|
documents = (
|
||||||
|
await import(/* webpackChunkName: "queryCopilotSampleData" */ "../../../sampleData/queryCopilotSampleData.json")
|
||||||
|
).data;
|
||||||
|
break;
|
||||||
|
case SampleDataFile.FABRIC_SAMPLE_DATA:
|
||||||
|
documents = (await import(/* webpackChunkName: "fabricSampleData" */ "../../../sampleData/fabricSampleData.json"))
|
||||||
|
.default;
|
||||||
|
break;
|
||||||
|
case SampleDataFile.FABRIC_SAMPLE_VECTOR_DATA:
|
||||||
|
documents = (
|
||||||
|
await import(
|
||||||
|
/* webpackChunkName: "fabricSampleDataVectors" */ "../../../sampleData/fabricSampleDataVectors.json"
|
||||||
|
)
|
||||||
|
).default;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown sample data file: ${sampleDataFile}`);
|
||||||
|
}
|
||||||
|
if (!documents) {
|
||||||
|
throw new Error(`Failed to load sample data file: ${sampleDataFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
export const importData = async (collection: ViewModels.Collection): Promise<void> => {
|
// Time it
|
||||||
// TODO: keep same chunk as ContainerSampleGenerator
|
const start = performance.now();
|
||||||
const dataFileContent = await import(
|
await collection.bulkInsertDocuments(documents);
|
||||||
/* webpackChunkName: "queryCopilotSampleData" */ "../../../sampleData/queryCopilotSampleData.json"
|
const end = performance.now();
|
||||||
);
|
TelemetryProcessor.trace(Action.ImportSampleData, ActionModifiers.Success, {
|
||||||
await collection.bulkInsertDocuments(dataFileContent.data);
|
documentsCount: documents.length,
|
||||||
|
durationMs: end - start,
|
||||||
|
sampleDataFile,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,39 +77,39 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
{
|
{
|
||||||
dispose: useNotebook.subscribe(
|
dispose: useNotebook.subscribe(
|
||||||
(state) => state.isNotebookEnabled,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.isNotebookEnabled,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
||||||
{
|
{
|
||||||
dispose: useCarousel.subscribe(
|
dispose: useCarousel.subscribe(
|
||||||
|
() => this.setState({}),
|
||||||
(state) => state.showCoachMark,
|
(state) => state.showCoachMark,
|
||||||
() => this.setState({}),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: usePostgres.subscribe(
|
dispose: usePostgres.subscribe(
|
||||||
|
() => this.setState({}),
|
||||||
(state) => state.showPostgreTeachingBubble,
|
(state) => state.showPostgreTeachingBubble,
|
||||||
() => this.setState({}),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: usePostgres.subscribe(
|
dispose: usePostgres.subscribe(
|
||||||
(state) => state.showResetPasswordBubble,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.showResetPasswordBubble,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: useDatabases.subscribe(
|
dispose: useDatabases.subscribe(
|
||||||
(state) => state.sampleDataResourceTokenCollection,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.sampleDataResourceTokenCollection,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispose: useQueryCopilot.subscribe(
|
dispose: useQueryCopilot.subscribe(
|
||||||
(state) => state.copilotEnabled,
|
|
||||||
() => this.setState({}),
|
() => this.setState({}),
|
||||||
|
(state) => state.copilotEnabled,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import "xterm/css/xterm.css";
|
||||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
import { TerminalKind } from "../../../Contracts/ViewModels";
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
import { startCloudShellTerminal } from "./CloudShellTerminalCore";
|
import { startCloudShellTerminal } from "./CloudShellTerminalCore";
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.notebookServerInfo,
|
|
||||||
() => logConsoleInfo("New notebook server info received."),
|
() => logConsoleInfo("New notebook server info received."),
|
||||||
|
(state) => state.notebookServerInfo,
|
||||||
);
|
);
|
||||||
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
||||||
contentItem: options.notebookContentItem,
|
contentItem: options.notebookContentItem,
|
||||||
@@ -165,7 +165,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: kernelLabel,
|
iconAlt: kernelLabel,
|
||||||
onCommandClick: () => { },
|
onCommandClick: () => {},
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: availableKernels.length < 1,
|
disabled: availableKernels.length < 1,
|
||||||
@@ -276,7 +276,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: null,
|
iconAlt: null,
|
||||||
onCommandClick: () => { },
|
onCommandClick: () => {},
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
ariaLabel: cellTypeLabel,
|
ariaLabel: cellTypeLabel,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
|
|||||||
107
src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx
Normal file
107
src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { CircleFilled } from "@fluentui/react-icons";
|
||||||
|
import type { IIndexMetric } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||||
|
import { useIndexAdvisorStyles } from "Explorer/Tabs/QueryTab/StylesAdvisor";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
// SDK response format
|
||||||
|
export interface IndexMetricsResponse {
|
||||||
|
UtilizedIndexes?: {
|
||||||
|
SingleIndexes?: Array<{ IndexSpec: string; IndexImpactScore?: string }>;
|
||||||
|
CompositeIndexes?: Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>;
|
||||||
|
};
|
||||||
|
PotentialIndexes?: {
|
||||||
|
SingleIndexes?: Array<{ IndexSpec: string; IndexImpactScore?: string }>;
|
||||||
|
CompositeIndexes?: Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseIndexMetrics(indexMetrics: IndexMetricsResponse): {
|
||||||
|
included: IIndexMetric[];
|
||||||
|
notIncluded: IIndexMetric[];
|
||||||
|
} {
|
||||||
|
const included: IIndexMetric[] = [];
|
||||||
|
const notIncluded: IIndexMetric[] = [];
|
||||||
|
|
||||||
|
// Process UtilizedIndexes (Included in Current Policy)
|
||||||
|
if (indexMetrics.UtilizedIndexes) {
|
||||||
|
// Single indexes
|
||||||
|
indexMetrics.UtilizedIndexes.SingleIndexes?.forEach((index) => {
|
||||||
|
included.push({
|
||||||
|
index: index.IndexSpec,
|
||||||
|
impact: index.IndexImpactScore || "Utilized",
|
||||||
|
section: "Included",
|
||||||
|
path: index.IndexSpec,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Composite indexes
|
||||||
|
indexMetrics.UtilizedIndexes.CompositeIndexes?.forEach((index) => {
|
||||||
|
const compositeSpec = index.IndexSpecs.join(", ");
|
||||||
|
included.push({
|
||||||
|
index: compositeSpec,
|
||||||
|
impact: index.IndexImpactScore || "Utilized",
|
||||||
|
section: "Included",
|
||||||
|
composite: index.IndexSpecs.map((spec) => {
|
||||||
|
const [path, order] = spec.trim().split(/\s+/);
|
||||||
|
return {
|
||||||
|
path: path.trim(),
|
||||||
|
order: order?.toLowerCase() === "desc" ? "descending" : "ascending",
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process PotentialIndexes (Not Included in Current Policy)
|
||||||
|
if (indexMetrics.PotentialIndexes) {
|
||||||
|
// Single indexes
|
||||||
|
indexMetrics.PotentialIndexes.SingleIndexes?.forEach((index) => {
|
||||||
|
notIncluded.push({
|
||||||
|
index: index.IndexSpec,
|
||||||
|
impact: index.IndexImpactScore || "Unknown",
|
||||||
|
section: "Not Included",
|
||||||
|
path: index.IndexSpec,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Composite indexes
|
||||||
|
indexMetrics.PotentialIndexes.CompositeIndexes?.forEach((index) => {
|
||||||
|
const compositeSpec = index.IndexSpecs.join(", ");
|
||||||
|
notIncluded.push({
|
||||||
|
index: compositeSpec,
|
||||||
|
impact: index.IndexImpactScore || "Unknown",
|
||||||
|
section: "Not Included",
|
||||||
|
composite: index.IndexSpecs.map((spec) => {
|
||||||
|
const [path, order] = spec.trim().split(/\s+/);
|
||||||
|
return {
|
||||||
|
path: path.trim(),
|
||||||
|
order: order?.toLowerCase() === "desc" ? "descending" : "ascending",
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { included, notIncluded };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderImpactDots = (impact: string): JSX.Element => {
|
||||||
|
const style = useIndexAdvisorStyles();
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
if (impact === "High") {
|
||||||
|
count = 3;
|
||||||
|
} else if (impact === "Medium") {
|
||||||
|
count = 2;
|
||||||
|
} else if (impact === "Low") {
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.indexAdvisorImpactDots}>
|
||||||
|
{Array.from({ length: count }).map((_, i) => (
|
||||||
|
<CircleFilled key={i} className={style.indexAdvisorImpactDot} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,18 +3,21 @@ import QueryError from "Common/QueryError";
|
|||||||
import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar";
|
import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar";
|
||||||
import { MessageBanner } from "Explorer/Controls/MessageBanner";
|
import { MessageBanner } from "Explorer/Controls/MessageBanner";
|
||||||
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||||
|
import useZoomLevel from "hooks/useZoomLevel";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { conditionalClass } from "Utils/StyleUtils";
|
||||||
import RunQuery from "../../../../images/RunQuery.png";
|
import RunQuery from "../../../../images/RunQuery.png";
|
||||||
import { QueryResults } from "../../../Contracts/ViewModels";
|
import { QueryResults } from "../../../Contracts/ViewModels";
|
||||||
import { ErrorList } from "./ErrorList";
|
import { ErrorList } from "./ErrorList";
|
||||||
import { ResultsView } from "./ResultsView";
|
import { ResultsView } from "./ResultsView";
|
||||||
import useZoomLevel from "hooks/useZoomLevel";
|
|
||||||
import { conditionalClass } from "Utils/StyleUtils";
|
|
||||||
|
|
||||||
export interface ResultsViewProps {
|
export interface ResultsViewProps {
|
||||||
isMongoDB: boolean;
|
isMongoDB: boolean;
|
||||||
queryResults: QueryResults;
|
queryResults: QueryResults;
|
||||||
executeQueryDocumentsPage: (firstItemIndex: number) => Promise<void>;
|
executeQueryDocumentsPage: (firstItemIndex: number) => Promise<void>;
|
||||||
|
queryEditorContent?: string;
|
||||||
|
databaseId?: string;
|
||||||
|
containerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryResultProps extends ResultsViewProps {
|
interface QueryResultProps extends ResultsViewProps {
|
||||||
@@ -49,6 +52,8 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
|||||||
queryResults,
|
queryResults,
|
||||||
executeQueryDocumentsPage,
|
executeQueryDocumentsPage,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
}: QueryResultProps): JSX.Element => {
|
}: QueryResultProps): JSX.Element => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent);
|
const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent);
|
||||||
@@ -91,6 +96,9 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
|||||||
queryResults={queryResults}
|
queryResults={queryResults}
|
||||||
executeQueryDocumentsPage={executeQueryDocumentsPage}
|
executeQueryDocumentsPage={executeQueryDocumentsPage}
|
||||||
isMongoDB={isMongoDB}
|
isMongoDB={isMongoDB}
|
||||||
|
queryEditorContent={queryEditorContent}
|
||||||
|
databaseId={databaseId}
|
||||||
|
containerId={containerId}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ExecuteQueryCallToAction />
|
<ExecuteQueryCallToAction />
|
||||||
|
|||||||
@@ -52,8 +52,9 @@ describe("QueryTabComponent", () => {
|
|||||||
copilotVersion: "v3.0",
|
copilotVersion: "v3.0",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const propsMock: Readonly<IQueryTabComponentProps> = {
|
const propsMock: Readonly<IQueryTabComponentProps> = {
|
||||||
collection: { databaseId: "CopilotSampleDB" },
|
collection: { databaseId: "CopilotSampleDB", id: () => "CopilotContainer" },
|
||||||
onTabAccessor: () => jest.fn(),
|
onTabAccessor: () => jest.fn(),
|
||||||
isExecutionError: false,
|
isExecutionError: false,
|
||||||
tabId: "mockTabId",
|
tabId: "mockTabId",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { TabsState, useTabs } from "hooks/useTabs";
|
|||||||
import React, { Fragment, createRef } from "react";
|
import React, { Fragment, createRef } from "react";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
import { format } from "react-string-format";
|
import { format } from "react-string-format";
|
||||||
|
import create from "zustand";
|
||||||
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
||||||
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||||
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
@@ -56,6 +57,20 @@ import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
|
|||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import "./QueryTabComponent.less";
|
import "./QueryTabComponent.less";
|
||||||
|
|
||||||
|
export interface QueryMetadataStore {
|
||||||
|
userQuery: string;
|
||||||
|
databaseId: string;
|
||||||
|
containerId: string;
|
||||||
|
setMetadata: (query1: string, db: string, container: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useQueryMetadataStore = create<QueryMetadataStore>((set) => ({
|
||||||
|
userQuery: "",
|
||||||
|
databaseId: "",
|
||||||
|
containerId: "",
|
||||||
|
setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }),
|
||||||
|
}));
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
Result,
|
Result,
|
||||||
QueryMetrics,
|
QueryMetrics,
|
||||||
@@ -260,6 +275,10 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteQueryClick = async (): Promise<void> => {
|
public onExecuteQueryClick = async (): Promise<void> => {
|
||||||
|
const query1 = this.state.sqlQueryEditorContent;
|
||||||
|
const db = this.props.collection.databaseId;
|
||||||
|
const container = this.props.collection.id();
|
||||||
|
useQueryMetadataStore.getState().setMetadata(query1, db, container);
|
||||||
this._iterator = undefined;
|
this._iterator = undefined;
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -775,6 +794,8 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
errors={this.props.copilotStore?.errors}
|
errors={this.props.copilotStore?.errors}
|
||||||
isExecuting={this.props.copilotStore?.isExecuting}
|
isExecuting={this.props.copilotStore?.isExecuting}
|
||||||
queryResults={this.props.copilotStore?.queryResults}
|
queryResults={this.props.copilotStore?.queryResults}
|
||||||
|
databaseId={this.props.collection.databaseId}
|
||||||
|
containerId={this.props.collection.id()}
|
||||||
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
||||||
QueryDocumentsPerPage(
|
QueryDocumentsPerPage(
|
||||||
firstItemIndex,
|
firstItemIndex,
|
||||||
@@ -790,6 +811,8 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
isExecuting={this.state.isExecuting}
|
isExecuting={this.state.isExecuting}
|
||||||
queryResults={this.state.queryResults}
|
queryResults={this.state.queryResults}
|
||||||
|
databaseId={this.props.collection.databaseId}
|
||||||
|
containerId={this.props.collection.id()}
|
||||||
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
||||||
this._executeQueryDocumentsPage(firstItemIndex)
|
this._executeQueryDocumentsPage(firstItemIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
170
src/Explorer/Tabs/QueryTab/ResultsView.test.tsx
Normal file
170
src/Explorer/Tabs/QueryTab/ResultsView.test.tsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { render, screen, waitFor } from "@testing-library/react";
|
||||||
|
import { IndexAdvisorTab } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const mockReplace = jest.fn();
|
||||||
|
const mockFetchAll = jest.fn();
|
||||||
|
const mockRead = jest.fn();
|
||||||
|
const mockLogConsoleProgress = jest.fn();
|
||||||
|
const mockHandleError = jest.fn();
|
||||||
|
|
||||||
|
const indexMetricsResponse = {
|
||||||
|
UtilizedIndexes: {
|
||||||
|
SingleIndexes: [{ IndexSpec: "/foo/?", IndexImpactScore: "High" }],
|
||||||
|
CompositeIndexes: [{ IndexSpecs: ["/baz/? DESC", "/qux/? ASC"], IndexImpactScore: "Low" }],
|
||||||
|
},
|
||||||
|
PotentialIndexes: {
|
||||||
|
SingleIndexes: [{ IndexSpec: "/bar/?", IndexImpactScore: "Medium" }],
|
||||||
|
CompositeIndexes: [] as Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockQueryResults = {
|
||||||
|
documents: [] as unknown[],
|
||||||
|
hasMoreResults: false,
|
||||||
|
itemCount: 0,
|
||||||
|
firstItemIndex: 0,
|
||||||
|
lastItemIndex: 0,
|
||||||
|
requestCharge: 0,
|
||||||
|
activityId: "test-activity-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
mockRead.mockResolvedValue({
|
||||||
|
resource: {
|
||||||
|
indexingPolicy: {
|
||||||
|
automatic: true,
|
||||||
|
indexingMode: "consistent",
|
||||||
|
includedPaths: [{ path: "/*" }, { path: "/foo/?" }],
|
||||||
|
excludedPaths: [],
|
||||||
|
},
|
||||||
|
partitionKey: "pk",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mockReplace.mockResolvedValue({
|
||||||
|
resource: {
|
||||||
|
indexingPolicy: {
|
||||||
|
automatic: true,
|
||||||
|
indexingMode: "consistent",
|
||||||
|
includedPaths: [{ path: "/*" }],
|
||||||
|
excludedPaths: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock("Common/CosmosClient", () => ({
|
||||||
|
client: () => ({
|
||||||
|
database: () => ({
|
||||||
|
container: () => ({
|
||||||
|
items: {
|
||||||
|
query: () => ({
|
||||||
|
fetchAll: mockFetchAll,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
read: mockRead,
|
||||||
|
replace: mockReplace,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("./StylesAdvisor", () => ({
|
||||||
|
useIndexAdvisorStyles: () => ({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../Utils/NotificationConsoleUtils", () => ({
|
||||||
|
logConsoleProgress: (...args: unknown[]) => {
|
||||||
|
mockLogConsoleProgress(...args);
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../Common/ErrorHandlingUtils", () => ({
|
||||||
|
handleError: (...args: unknown[]) => mockHandleError(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockFetchAll.mockResolvedValue({ indexMetrics: indexMetricsResponse });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("IndexAdvisorTab Basic Tests", () => {
|
||||||
|
test("component renders without crashing", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<IndexAdvisorTab queryEditorContent="SELECT * FROM c" databaseId="db1" containerId="col1" />,
|
||||||
|
);
|
||||||
|
expect(container).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders component and handles missing parameters", () => {
|
||||||
|
const { container } = render(<IndexAdvisorTab />);
|
||||||
|
expect(container).toBeTruthy();
|
||||||
|
// Should not crash when parameters are missing
|
||||||
|
});
|
||||||
|
|
||||||
|
test("fetches index metrics with query results", async () => {
|
||||||
|
render(
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={mockQueryResults}
|
||||||
|
queryEditorContent="SELECT * FROM c"
|
||||||
|
databaseId="db1"
|
||||||
|
containerId="col1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(mockFetchAll).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays content after loading", async () => {
|
||||||
|
render(
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={mockQueryResults}
|
||||||
|
queryEditorContent="SELECT * FROM c"
|
||||||
|
databaseId="db1"
|
||||||
|
containerId="col1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
// Wait for the component to finish loading
|
||||||
|
await waitFor(() => expect(mockFetchAll).toHaveBeenCalled());
|
||||||
|
// Component should have rendered some content
|
||||||
|
expect(screen.getByText(/Index Advisor/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls log console progress when fetching metrics", async () => {
|
||||||
|
render(
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={mockQueryResults}
|
||||||
|
queryEditorContent="SELECT * FROM c"
|
||||||
|
databaseId="db1"
|
||||||
|
containerId="col1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(mockLogConsoleProgress).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles error when fetch fails", async () => {
|
||||||
|
mockFetchAll.mockRejectedValueOnce(new Error("fetch failed"));
|
||||||
|
render(
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={mockQueryResults}
|
||||||
|
queryEditorContent="SELECT * FROM c"
|
||||||
|
databaseId="db1"
|
||||||
|
containerId="col1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(mockHandleError).toHaveBeenCalled(), { timeout: 3000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders with all required props", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={mockQueryResults}
|
||||||
|
queryEditorContent="SELECT * FROM c"
|
||||||
|
databaseId="testDb"
|
||||||
|
containerId="testContainer"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(container).toBeTruthy();
|
||||||
|
expect(container.firstChild).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import type { CompositePath, IndexingPolicy } from "@azure/cosmos";
|
||||||
|
import { FontIcon } from "@fluentui/react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
DataGrid,
|
DataGrid,
|
||||||
DataGridBody,
|
DataGridBody,
|
||||||
DataGridCell,
|
DataGridCell,
|
||||||
@@ -8,28 +11,45 @@ import {
|
|||||||
DataGridRow,
|
DataGridRow,
|
||||||
SelectTabData,
|
SelectTabData,
|
||||||
SelectTabEvent,
|
SelectTabEvent,
|
||||||
|
Spinner,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
TableColumnDefinition,
|
TableColumnDefinition,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
createTableColumn,
|
createTableColumn,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { ArrowDownloadRegular, CopyRegular } from "@fluentui/react-icons";
|
import { ArrowDownloadRegular, ChevronDown20Regular, ChevronRight20Regular, CopyRegular } from "@fluentui/react-icons";
|
||||||
|
import copy from "clipboard-copy";
|
||||||
import { HttpHeaders } from "Common/Constants";
|
import { HttpHeaders } from "Common/Constants";
|
||||||
import MongoUtility from "Common/MongoUtility";
|
import MongoUtility from "Common/MongoUtility";
|
||||||
import { QueryMetrics } from "Contracts/DataModels";
|
import { QueryMetrics } from "Contracts/DataModels";
|
||||||
|
import { QueryResults } from "Contracts/ViewModels";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
|
import {
|
||||||
|
parseIndexMetrics,
|
||||||
|
renderImpactDots,
|
||||||
|
type IndexMetricsResponse,
|
||||||
|
} from "Explorer/Tabs/QueryTab/IndexAdvisorUtils";
|
||||||
import { IDocument } from "Explorer/Tabs/QueryTab/QueryTabComponent";
|
import { IDocument } from "Explorer/Tabs/QueryTab/QueryTabComponent";
|
||||||
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import copy from "clipboard-copy";
|
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||||
import React, { useCallback, useState } from "react";
|
import create from "zustand";
|
||||||
|
import { client } from "../../../Common/CosmosClient";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { sampleDataClient } from "../../../Common/SampleDataClient";
|
||||||
import { ResultsViewProps } from "./QueryResultSection";
|
import { ResultsViewProps } from "./QueryResultSection";
|
||||||
|
import { useIndexAdvisorStyles } from "./StylesAdvisor";
|
||||||
enum ResultsTabs {
|
enum ResultsTabs {
|
||||||
Results = "results",
|
Results = "results",
|
||||||
QueryStats = "queryStats",
|
QueryStats = "queryStats",
|
||||||
|
IndexAdvisor = "indexadv",
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => {
|
const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
@@ -523,14 +543,331 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResults, executeQueryDocumentsPage }) => {
|
export interface IIndexMetric {
|
||||||
|
index: string;
|
||||||
|
impact: string;
|
||||||
|
section: "Included" | "Not Included" | "Header";
|
||||||
|
path?: string;
|
||||||
|
composite?: { path: string; order: string }[];
|
||||||
|
}
|
||||||
|
export const IndexAdvisorTab: React.FC<{
|
||||||
|
queryResults?: QueryResults;
|
||||||
|
queryEditorContent?: string;
|
||||||
|
databaseId?: string;
|
||||||
|
containerId?: string;
|
||||||
|
}> = ({ queryResults, queryEditorContent, databaseId, containerId }) => {
|
||||||
|
const style = useIndexAdvisorStyles();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [indexMetrics, setIndexMetrics] = useState<IndexMetricsResponse | null>(null);
|
||||||
|
const [showIncluded, setShowIncluded] = useState(true);
|
||||||
|
const [showNotIncluded, setShowNotIncluded] = useState(true);
|
||||||
|
const [selectedIndexes, setSelectedIndexes] = useState<IIndexMetric[]>([]);
|
||||||
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
const [updateMessageShown, setUpdateMessageShown] = useState(false);
|
||||||
|
const [included, setIncludedIndexes] = useState<IIndexMetric[]>([]);
|
||||||
|
const [notIncluded, setNotIncludedIndexes] = useState<IIndexMetric[]>([]);
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
const [justUpdatedPolicy, setJustUpdatedPolicy] = useState(false);
|
||||||
|
const indexingMetricsDocLink = "https://learn.microsoft.com/azure/cosmos-db/nosql/index-metrics";
|
||||||
|
|
||||||
|
const fetchIndexMetrics = async () => {
|
||||||
|
if (!queryEditorContent || !databaseId || !containerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const clearMessage = logConsoleProgress(`Querying items with IndexMetrics in container ${containerId}`);
|
||||||
|
try {
|
||||||
|
const querySpec = {
|
||||||
|
query: queryEditorContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use sampleDataClient for CopilotSampleDB, regular client for other databases
|
||||||
|
const cosmosClient = databaseId === "CopilotSampleDB" ? sampleDataClient() : client();
|
||||||
|
|
||||||
|
const sdkResponse = await cosmosClient
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.items.query(querySpec, {
|
||||||
|
populateIndexMetrics: true,
|
||||||
|
})
|
||||||
|
.fetchAll();
|
||||||
|
|
||||||
|
const parsedMetrics =
|
||||||
|
typeof sdkResponse.indexMetrics === "string" ? JSON.parse(sdkResponse.indexMetrics) : sdkResponse.indexMetrics;
|
||||||
|
|
||||||
|
setIndexMetrics(parsedMetrics);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "queryItemsWithIndexMetrics", `Error querying items from ${containerId}`);
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch index metrics when query results change (i.e., when Execute Query is clicked)
|
||||||
|
useEffect(() => {
|
||||||
|
if (queryEditorContent && databaseId && containerId && queryResults) {
|
||||||
|
fetchIndexMetrics();
|
||||||
|
}
|
||||||
|
}, [queryResults]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!indexMetrics) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { included, notIncluded } = parseIndexMetrics(indexMetrics);
|
||||||
|
setIncludedIndexes(included);
|
||||||
|
setNotIncludedIndexes(notIncluded);
|
||||||
|
if (justUpdatedPolicy) {
|
||||||
|
setJustUpdatedPolicy(false);
|
||||||
|
} else {
|
||||||
|
setUpdateMessageShown(false);
|
||||||
|
}
|
||||||
|
}, [indexMetrics]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const allSelected =
|
||||||
|
notIncluded.length > 0 && notIncluded.every((item) => selectedIndexes.some((s) => s.index === item.index));
|
||||||
|
setSelectAll(allSelected);
|
||||||
|
}, [selectedIndexes, notIncluded]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (indexObj: IIndexMetric, checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setSelectedIndexes((prev) => [...prev, indexObj]);
|
||||||
|
} else {
|
||||||
|
setSelectedIndexes((prev) => prev.filter((item) => item.index !== indexObj.index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = (checked: boolean) => {
|
||||||
|
setSelectAll(checked);
|
||||||
|
setSelectedIndexes(checked ? notIncluded : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdatePolicy = async () => {
|
||||||
|
setIsUpdating(true);
|
||||||
|
try {
|
||||||
|
const containerRef = client().database(databaseId).container(containerId);
|
||||||
|
const { resource: containerDef } = await containerRef.read();
|
||||||
|
|
||||||
|
const newIncludedPaths = selectedIndexes
|
||||||
|
.filter((index) => !index.composite)
|
||||||
|
.map((index) => {
|
||||||
|
return {
|
||||||
|
path: index.path,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const newCompositeIndexes: CompositePath[][] = selectedIndexes
|
||||||
|
.filter((index) => Array.isArray(index.composite))
|
||||||
|
.map(
|
||||||
|
(index) =>
|
||||||
|
(index.composite as { path: string; order: string }[]).map((comp) => ({
|
||||||
|
path: comp.path,
|
||||||
|
order: comp.order === "descending" ? "descending" : "ascending",
|
||||||
|
})) as CompositePath[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedPolicy: IndexingPolicy = {
|
||||||
|
...containerDef.indexingPolicy,
|
||||||
|
includedPaths: [...(containerDef.indexingPolicy?.includedPaths || []), ...newIncludedPaths],
|
||||||
|
compositeIndexes: [...(containerDef.indexingPolicy?.compositeIndexes || []), ...newCompositeIndexes],
|
||||||
|
automatic: containerDef.indexingPolicy?.automatic ?? true,
|
||||||
|
indexingMode: containerDef.indexingPolicy?.indexingMode ?? "consistent",
|
||||||
|
excludedPaths: containerDef.indexingPolicy?.excludedPaths ?? [],
|
||||||
|
};
|
||||||
|
await containerRef.replace({
|
||||||
|
id: containerId,
|
||||||
|
partitionKey: containerDef.partitionKey,
|
||||||
|
indexingPolicy: updatedPolicy,
|
||||||
|
});
|
||||||
|
useIndexingPolicyStore.getState().setIndexingPolicyFor(containerId, updatedPolicy);
|
||||||
|
const selectedIndexSet = new Set(selectedIndexes.map((s) => s.index));
|
||||||
|
const updatedNotIncluded: typeof notIncluded = [];
|
||||||
|
const newlyIncluded: typeof included = [];
|
||||||
|
for (const item of notIncluded) {
|
||||||
|
if (selectedIndexSet.has(item.index)) {
|
||||||
|
newlyIncluded.push(item);
|
||||||
|
} else {
|
||||||
|
updatedNotIncluded.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newIncluded = [...included, ...newlyIncluded];
|
||||||
|
const newNotIncluded = updatedNotIncluded;
|
||||||
|
setIncludedIndexes(newIncluded);
|
||||||
|
setNotIncludedIndexes(newNotIncluded);
|
||||||
|
setSelectedIndexes([]);
|
||||||
|
setSelectAll(false);
|
||||||
|
setUpdateMessageShown(true);
|
||||||
|
setJustUpdatedPolicy(true);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to update indexing policy:", err);
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRow = (item: IIndexMetric, index: number) => {
|
||||||
|
const isHeader = item.section === "Header";
|
||||||
|
const isNotIncluded = item.section === "Not Included";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell colSpan={2}>
|
||||||
|
<div className={style.indexAdvisorGrid}>
|
||||||
|
{isNotIncluded ? (
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedIndexes.some((selected) => selected.index === item.index)}
|
||||||
|
onChange={(_, data) => handleCheckboxChange(item, data.checked === true)}
|
||||||
|
/>
|
||||||
|
) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? (
|
||||||
|
<Checkbox checked={selectAll} onChange={(_, data) => handleSelectAll(data.checked === true)} />
|
||||||
|
) : (
|
||||||
|
<div className={style.indexAdvisorCheckboxSpacer}></div>
|
||||||
|
)}
|
||||||
|
{isHeader ? (
|
||||||
|
<span
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.index === "Included in Current Policy") {
|
||||||
|
setShowIncluded(!showIncluded);
|
||||||
|
} else if (item.index === "Not Included in Current Policy") {
|
||||||
|
setShowNotIncluded(!showNotIncluded);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.index === "Included in Current Policy" ? (
|
||||||
|
showIncluded ? (
|
||||||
|
<ChevronDown20Regular />
|
||||||
|
) : (
|
||||||
|
<ChevronRight20Regular />
|
||||||
|
)
|
||||||
|
) : showNotIncluded ? (
|
||||||
|
<ChevronDown20Regular />
|
||||||
|
) : (
|
||||||
|
<ChevronRight20Regular />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<div className={style.indexAdvisorChevronSpacer}></div>
|
||||||
|
)}
|
||||||
|
<div className={isHeader ? style.indexAdvisorRowBold : style.indexAdvisorRowNormal}>{item.index}</div>
|
||||||
|
<div className={isHeader ? style.indexAdvisorRowImpactHeader : style.indexAdvisorRowImpact}>
|
||||||
|
{!isHeader && item.impact}
|
||||||
|
</div>
|
||||||
|
<div>{!isHeader && renderImpactDots(item.impact)}</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const indexMetricItems = React.useMemo(() => {
|
||||||
|
const items: IIndexMetric[] = [];
|
||||||
|
items.push({ index: "Not Included in Current Policy", impact: "", section: "Header" });
|
||||||
|
if (showNotIncluded) {
|
||||||
|
notIncluded.forEach((item) => items.push({ ...item, section: "Not Included" }));
|
||||||
|
}
|
||||||
|
items.push({ index: "Included in Current Policy", impact: "", section: "Header" });
|
||||||
|
if (showIncluded) {
|
||||||
|
included.forEach((item) => items.push({ ...item, section: "Included" }));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}, [included, notIncluded, showIncluded, showNotIncluded]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spinner
|
||||||
|
size="small"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--spinner-size": "16px",
|
||||||
|
"--spinner-thickness": "2px",
|
||||||
|
"--spinner-color": "#0078D4",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={style.indexAdvisorMessage}>
|
||||||
|
{updateMessageShown ? (
|
||||||
|
<>
|
||||||
|
<span className={style.indexAdvisorSuccessIcon}>
|
||||||
|
<FontIcon iconName="CheckMark" style={{ color: "white", fontSize: 12 }} />
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Your indexing policy has been updated with the new included paths. You may review the changes in Scale &
|
||||||
|
Settings.
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
Index Advisor uses Indexing Metrics to suggest query paths that, when included in your indexing policy,
|
||||||
|
can improve the performance of this query by reducing RU costs and lowering latency.{" "}
|
||||||
|
<a href={indexingMetricsDocLink} target="_blank" rel="noopener noreferrer">
|
||||||
|
Learn more about Indexing Metrics
|
||||||
|
</a>
|
||||||
|
.{" "}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={style.indexAdvisorTitle}>Indexes analysis</div>
|
||||||
|
<Table className={style.indexAdvisorTable}>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={2}>
|
||||||
|
<div className={style.indexAdvisorGrid}>
|
||||||
|
<div className={style.indexAdvisorCheckboxSpacer}></div>
|
||||||
|
<div className={style.indexAdvisorChevronSpacer}></div>
|
||||||
|
<div>Index</div>
|
||||||
|
<div>
|
||||||
|
<span style={{ whiteSpace: "nowrap" }}>Estimated Impact</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>{indexMetricItems.map(renderRow)}</TableBody>
|
||||||
|
</Table>
|
||||||
|
{selectedIndexes.length > 0 && (
|
||||||
|
<div className={style.indexAdvisorButtonBar}>
|
||||||
|
{isUpdating ? (
|
||||||
|
<div className={style.indexAdvisorButtonSpinner}>
|
||||||
|
<Spinner size="tiny" />{" "}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button onClick={handleUpdatePolicy} className={style.indexAdvisorButton}>
|
||||||
|
Update Indexing Policy with selected index(es)
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const ResultsView: React.FC<ResultsViewProps> = ({
|
||||||
|
isMongoDB,
|
||||||
|
queryResults,
|
||||||
|
executeQueryDocumentsPage,
|
||||||
|
queryEditorContent,
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
|
}) => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
const [activeTab, setActiveTab] = useState<ResultsTabs>(ResultsTabs.Results);
|
const [activeTab, setActiveTab] = useState<ResultsTabs>(ResultsTabs.Results);
|
||||||
|
|
||||||
const onTabSelect = useCallback((event: SelectTabEvent, data: SelectTabData) => {
|
const onTabSelect = useCallback((event: SelectTabEvent, data: SelectTabData) => {
|
||||||
setActiveTab(data.value as ResultsTabs);
|
setActiveTab(data.value as ResultsTabs);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
<div data-test="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
||||||
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
|
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
|
||||||
@@ -548,6 +885,13 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
|
|||||||
>
|
>
|
||||||
Query Stats
|
Query Stats
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
data-test="QueryTab/ResultsPane/ResultsView/IndexAdvisorTab"
|
||||||
|
id={ResultsTabs.IndexAdvisor}
|
||||||
|
value={ResultsTabs.IndexAdvisor}
|
||||||
|
>
|
||||||
|
Index Advisor
|
||||||
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<div className={styles.queryResultsTabContentContainer}>
|
<div className={styles.queryResultsTabContentContainer}>
|
||||||
{activeTab === ResultsTabs.Results && (
|
{activeTab === ResultsTabs.Results && (
|
||||||
@@ -558,7 +902,30 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === ResultsTabs.QueryStats && <QueryStatsTab queryResults={queryResults} />}
|
{activeTab === ResultsTabs.QueryStats && <QueryStatsTab queryResults={queryResults} />}
|
||||||
|
{activeTab === ResultsTabs.IndexAdvisor && (
|
||||||
|
<IndexAdvisorTab
|
||||||
|
queryResults={queryResults}
|
||||||
|
queryEditorContent={queryEditorContent}
|
||||||
|
databaseId={databaseId}
|
||||||
|
containerId={containerId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export interface IndexingPolicyStore {
|
||||||
|
indexingPolicies: { [containerId: string]: IndexingPolicy };
|
||||||
|
setIndexingPolicyFor: (containerId: string, indexingPolicy: IndexingPolicy) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIndexingPolicyStore = create<IndexingPolicyStore>((set) => ({
|
||||||
|
indexingPolicies: {},
|
||||||
|
setIndexingPolicyFor: (containerId, indexingPolicy) =>
|
||||||
|
set((state) => ({
|
||||||
|
indexingPolicies: {
|
||||||
|
...state.indexingPolicies,
|
||||||
|
[containerId]: { ...indexingPolicy },
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|||||||
95
src/Explorer/Tabs/QueryTab/StylesAdvisor.ts
Normal file
95
src/Explorer/Tabs/QueryTab/StylesAdvisor.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { makeStyles } from "@fluentui/react-components";
|
||||||
|
export type IndexAdvisorStyles = ReturnType<typeof useIndexAdvisorStyles>;
|
||||||
|
export const useIndexAdvisorStyles = makeStyles({
|
||||||
|
indexAdvisorMessage: {
|
||||||
|
padding: "1rem",
|
||||||
|
fontSize: "1.2rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
indexAdvisorSuccessIcon: {
|
||||||
|
width: "18px",
|
||||||
|
height: "18px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#107C10",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
indexAdvisorTitle: {
|
||||||
|
padding: "1rem",
|
||||||
|
fontSize: "1.3rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
indexAdvisorTable: {
|
||||||
|
display: "block",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "7rem",
|
||||||
|
},
|
||||||
|
indexAdvisorGrid: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "30px 30px 1fr 50px 120px",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "15px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
indexAdvisorCheckboxSpacer: {
|
||||||
|
width: "18px",
|
||||||
|
height: "18px",
|
||||||
|
},
|
||||||
|
indexAdvisorChevronSpacer: {
|
||||||
|
width: "24px",
|
||||||
|
},
|
||||||
|
indexAdvisorRowBold: {
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
indexAdvisorRowNormal: {
|
||||||
|
fontWeight: "normal",
|
||||||
|
},
|
||||||
|
indexAdvisorRowImpactHeader: {
|
||||||
|
fontSize: 0,
|
||||||
|
},
|
||||||
|
indexAdvisorRowImpact: {
|
||||||
|
fontWeight: "normal",
|
||||||
|
},
|
||||||
|
indexAdvisorImpactDot: {
|
||||||
|
color: "#0078D4",
|
||||||
|
fontSize: "12px",
|
||||||
|
display: "inline-flex",
|
||||||
|
},
|
||||||
|
indexAdvisorImpactDots: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
},
|
||||||
|
indexAdvisorButtonBar: {
|
||||||
|
padding: "1rem",
|
||||||
|
marginTop: "-7rem",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
indexAdvisorButtonSpinner: {
|
||||||
|
marginTop: "1rem",
|
||||||
|
minWidth: "320px",
|
||||||
|
minHeight: "40px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "left",
|
||||||
|
justifyContent: "left",
|
||||||
|
marginLeft: "10rem",
|
||||||
|
},
|
||||||
|
indexAdvisorButton: {
|
||||||
|
backgroundColor: "#0078D4",
|
||||||
|
color: "white",
|
||||||
|
padding: "8px 16px",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
marginTop: "1rem",
|
||||||
|
fontSize: "1rem",
|
||||||
|
fontWeight: 500,
|
||||||
|
transition: "background 0.2s",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "#005a9e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
15
src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts
Normal file
15
src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import create from "zustand";
|
||||||
|
|
||||||
|
interface QueryMetadataStore {
|
||||||
|
userQuery: string;
|
||||||
|
databaseId: string;
|
||||||
|
containerId: string;
|
||||||
|
setMetadata: (query1: string, db: string, container: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useQueryMetadataStore = create<QueryMetadataStore>((set) => ({
|
||||||
|
userQuery: "",
|
||||||
|
databaseId: "",
|
||||||
|
containerId: "",
|
||||||
|
setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }),
|
||||||
|
}));
|
||||||
@@ -16,6 +16,7 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import shallow from "zustand/shallow";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
|
|
||||||
@@ -37,8 +38,13 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]);
|
const [openItems, setOpenItems] = React.useState<TreeItemValue[]>([]);
|
||||||
const treeStyles = useTreeStyles();
|
const treeStyles = useTreeStyles();
|
||||||
|
|
||||||
const isNotebookEnabled = useNotebook((state) => state.isNotebookEnabled)
|
const { isNotebookEnabled } = useNotebook(
|
||||||
|
(state) => ({
|
||||||
|
isNotebookEnabled: state.isNotebookEnabled,
|
||||||
|
}),
|
||||||
|
shallow,
|
||||||
|
);
|
||||||
|
|
||||||
// We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes.
|
// We intentionally avoid using a state selector here because we want to re-render the tree if the active tab changes.
|
||||||
const { refreshActiveTab } = useTabs();
|
const { refreshActiveTab } = useTabs();
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import PublishIcon from "../../../images/notebook/publish_content.svg";
|
|||||||
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
|
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
@@ -57,12 +58,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
|
|
||||||
useSelectedNode.subscribe(() => this.triggerRender());
|
useSelectedNode.subscribe(() => this.triggerRender());
|
||||||
useTabs.subscribe(
|
useTabs.subscribe(
|
||||||
(state) => state.activeTab,
|
|
||||||
() => this.triggerRender(),
|
() => this.triggerRender(),
|
||||||
|
(state) => state.activeTab,
|
||||||
);
|
);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
(state) => state.isNotebookEnabled,
|
|
||||||
() => this.triggerRender(),
|
() => this.triggerRender(),
|
||||||
|
(state) => state.isNotebookEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
useDatabases.subscribe(() => this.triggerRender());
|
useDatabases.subscribe(() => this.triggerRender());
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -27,147 +26,143 @@ interface DatabasesState {
|
|||||||
validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>;
|
validateCollectionId: (databaseId: string, collectionId: string) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDatabases = create<DatabasesState>()(
|
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
databases: [],
|
||||||
(set, get) => ({
|
resourceTokenCollection: undefined,
|
||||||
databases: [] as ViewModels.Database[],
|
sampleDataResourceTokenCollection: undefined,
|
||||||
resourceTokenCollection: undefined as ViewModels.CollectionBase,
|
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
||||||
sampleDataResourceTokenCollection: undefined as ViewModels.CollectionBase,
|
set((state) => {
|
||||||
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
||||||
set((state) => {
|
if (database?.id() === updatedDatabase?.id()) {
|
||||||
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
return updatedDatabase;
|
||||||
if (database?.id() === updatedDatabase?.id()) {
|
|
||||||
return updatedDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
return database;
|
|
||||||
});
|
|
||||||
return { databases: updatedDatabases };
|
|
||||||
}),
|
|
||||||
addDatabases: (databases: ViewModels.Database[]) =>
|
|
||||||
set((state) => ({
|
|
||||||
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
|
|
||||||
})),
|
|
||||||
deleteDatabase: (database: ViewModels.Database) =>
|
|
||||||
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
|
|
||||||
clearDatabases: () => set(() => ({ databases: [] })),
|
|
||||||
isSaveQueryEnabled: () => {
|
|
||||||
const savedQueriesDatabase: ViewModels.Database = _.find(
|
|
||||||
get().databases,
|
|
||||||
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
|
|
||||||
);
|
|
||||||
if (!savedQueriesDatabase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const savedQueriesCollection: ViewModels.Collection =
|
|
||||||
savedQueriesDatabase &&
|
|
||||||
_.find(
|
|
||||||
savedQueriesDatabase.collections(),
|
|
||||||
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
|
|
||||||
);
|
|
||||||
if (!savedQueriesCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
|
|
||||||
return isSampleDatabase === undefined
|
|
||||||
? get().databases.find((db) => databaseId === db.id())
|
|
||||||
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
|
|
||||||
},
|
|
||||||
isLastNonEmptyDatabase: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
|
|
||||||
},
|
|
||||||
findCollection: (databaseId: string, collectionId: string) => {
|
|
||||||
const database = get().findDatabaseWithId(databaseId);
|
|
||||||
return database?.collections()?.find((collection) => collection.id() === collectionId);
|
|
||||||
},
|
|
||||||
isLastCollection: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
if (databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let collectionCount = 0;
|
return database;
|
||||||
for (let i = 0; i < databases.length; i++) {
|
});
|
||||||
const database = databases[i];
|
return { databases: updatedDatabases };
|
||||||
collectionCount += database.collections().length;
|
}),
|
||||||
if (collectionCount > 1) {
|
addDatabases: (databases: ViewModels.Database[]) =>
|
||||||
return false;
|
set((state) => ({
|
||||||
}
|
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
|
||||||
}
|
})),
|
||||||
|
deleteDatabase: (database: ViewModels.Database) =>
|
||||||
|
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
|
||||||
|
clearDatabases: () => set(() => ({ databases: [] })),
|
||||||
|
isSaveQueryEnabled: () => {
|
||||||
|
const savedQueriesDatabase: ViewModels.Database = _.find(
|
||||||
|
get().databases,
|
||||||
|
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName,
|
||||||
|
);
|
||||||
|
if (!savedQueriesDatabase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const savedQueriesCollection: ViewModels.Collection =
|
||||||
|
savedQueriesDatabase &&
|
||||||
|
_.find(
|
||||||
|
savedQueriesDatabase.collections(),
|
||||||
|
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName,
|
||||||
|
);
|
||||||
|
if (!savedQueriesCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
findDatabaseWithId: (databaseId: string, isSampleDatabase?: boolean) => {
|
||||||
|
return isSampleDatabase === undefined
|
||||||
|
? get().databases.find((db) => databaseId === db.id())
|
||||||
|
: get().databases.find((db) => databaseId === db.id() && db.isSampleDB === isSampleDatabase);
|
||||||
|
},
|
||||||
|
isLastNonEmptyDatabase: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
|
||||||
|
},
|
||||||
|
findCollection: (databaseId: string, collectionId: string) => {
|
||||||
|
const database = get().findDatabaseWithId(databaseId);
|
||||||
|
return database?.collections()?.find((collection) => collection.id() === collectionId);
|
||||||
|
},
|
||||||
|
isLastCollection: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
if (databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
let collectionCount = 0;
|
||||||
},
|
for (let i = 0; i < databases.length; i++) {
|
||||||
loadDatabaseOffers: async () => {
|
const database = databases[i];
|
||||||
await Promise.all(
|
collectionCount += database.collections().length;
|
||||||
get().databases?.map(async (database: ViewModels.Database) => {
|
if (collectionCount > 1) {
|
||||||
await database.loadOffer();
|
return false;
|
||||||
}),
|
}
|
||||||
);
|
}
|
||||||
},
|
|
||||||
loadAllOffers: async () => {
|
|
||||||
await Promise.all(
|
|
||||||
get().databases?.map(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadOffer();
|
|
||||||
await database.loadCollections();
|
|
||||||
await Promise.all(
|
|
||||||
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
|
||||||
await collection.loadOffer();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isFirstResourceCreated: () => {
|
|
||||||
const databases = get().databases;
|
|
||||||
|
|
||||||
if (!databases || databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases.some((database) => {
|
|
||||||
// user has created at least one collection
|
|
||||||
if (database.collections()?.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// user has created a database with shared throughput
|
|
||||||
if (database.offer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// use has created an empty database without shared throughput
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
findSelectedDatabase: (): ViewModels.Database => {
|
|
||||||
const selectedNode = useSelectedNode.getState().selectedNode;
|
|
||||||
if (!selectedNode) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (selectedNode.nodeKind === "Database") {
|
|
||||||
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedNode.nodeKind === "Collection") {
|
|
||||||
return selectedNode.database;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedNode.collection?.database;
|
|
||||||
},
|
|
||||||
validateDatabaseId: (id: string): boolean => {
|
|
||||||
return !get().databases.some((database) => database.id() === id);
|
|
||||||
},
|
|
||||||
validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => {
|
|
||||||
const database = get().databases.find((db) => db.id() === databaseId);
|
|
||||||
// For a new tables account, database is undefined when creating the first table
|
|
||||||
if (!database && userContext.apiType === "Tables") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
loadDatabaseOffers: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
get().databases?.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loadAllOffers: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
get().databases?.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
await database.loadCollections();
|
await database.loadCollections();
|
||||||
return !database.collections().some((collection) => collection.id() === collectionId);
|
await Promise.all(
|
||||||
},
|
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
||||||
})
|
await collection.loadOffer();
|
||||||
)
|
}),
|
||||||
);
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isFirstResourceCreated: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
|
||||||
|
if (!databases || databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases.some((database) => {
|
||||||
|
// user has created at least one collection
|
||||||
|
if (database.collections()?.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// user has created a database with shared throughput
|
||||||
|
if (database.offer()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// use has created an empty database without shared throughput
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
findSelectedDatabase: (): ViewModels.Database => {
|
||||||
|
const selectedNode = useSelectedNode.getState().selectedNode;
|
||||||
|
if (!selectedNode) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (selectedNode.nodeKind === "Database") {
|
||||||
|
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode.nodeKind === "Collection") {
|
||||||
|
return selectedNode.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedNode.collection?.database;
|
||||||
|
},
|
||||||
|
validateDatabaseId: (id: string): boolean => {
|
||||||
|
return !get().databases.some((database) => database.id() === id);
|
||||||
|
},
|
||||||
|
validateCollectionId: async (databaseId: string, collectionId: string): Promise<boolean> => {
|
||||||
|
const database = get().databases.find((db) => db.id() === databaseId);
|
||||||
|
// For a new tables account, database is undefined when creating the first table
|
||||||
|
if (!database && userContext.apiType === "Tables") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await database.loadCollections();
|
||||||
|
return !database.collections().some((collection) => collection.id() === collectionId);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
import { ConnectionStatusType, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
export interface SelectedNodeState {
|
export interface SelectedNodeState {
|
||||||
@@ -17,7 +17,7 @@ export interface SelectedNodeState {
|
|||||||
isQueryCopilotCollectionSelected: () => boolean;
|
isQueryCopilotCollectionSelected: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSelectedNode = create<SelectedNodeState>((set, get) => ({
|
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
|
||||||
selectedNode: undefined,
|
selectedNode: undefined,
|
||||||
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
|
setSelectedNode: (node: ViewModels.TreeNode) => set({ selectedNode: node }),
|
||||||
isDatabaseNodeOrNoneSelected: (): boolean => {
|
isDatabaseNodeOrNoneSelected: (): boolean => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { PropsWithChildren, useEffect } from "react";
|
import { PropsWithChildren, useEffect } from "react";
|
||||||
import { KeyBindingMap, tinykeys } from "tinykeys";
|
import { KeyBindingMap, tinykeys } from "tinykeys";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a keyboard shortcut handler.
|
* Represents a keyboard shortcut handler.
|
||||||
@@ -126,7 +126,7 @@ export const clearKeyboardActionGroup = (group: KeyboardActionGroup) => {
|
|||||||
useKeyboardActionHandlers.getState().setHandlers(group, {});
|
useKeyboardActionHandlers.getState().setHandlers(group, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const useKeyboardActionHandlers = create<KeyboardShortcutState>((set, get) => ({
|
const useKeyboardActionHandlers: UseStore<KeyboardShortcutState> = create((set, get) => ({
|
||||||
allHandlers: {},
|
allHandlers: {},
|
||||||
groups: {},
|
groups: {},
|
||||||
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
|
setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
|||||||
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { FabricArtifactInfo, updateUserContext, userContext } from "UserContext";
|
import { FabricArtifactInfo, updateUserContext, userContext } from "UserContext";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
|
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||||
|
|
||||||
|
// Fabric Native accounts are always autoscale and have a fixed throughput of 5K
|
||||||
|
export const DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT = AutoPilotUtils.autoPilotThroughput5K;
|
||||||
|
|
||||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||||
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export enum StorageKey {
|
|||||||
DefaultQueryResultsView,
|
DefaultQueryResultsView,
|
||||||
AppState,
|
AppState,
|
||||||
MongoGuidRepresentation,
|
MongoGuidRepresentation,
|
||||||
|
IgnorePartitionKeyOnDocumentUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasRUThresholdBeenConfigured = (): boolean => {
|
export const hasRUThresholdBeenConfigured = (): boolean => {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export enum Action {
|
|||||||
CloudShellUserConsent,
|
CloudShellUserConsent,
|
||||||
CloudShellTerminalSession,
|
CloudShellTerminalSession,
|
||||||
OpenVSCode,
|
OpenVSCode,
|
||||||
|
ImportSampleData,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
|||||||
@@ -24,3 +24,10 @@ export const isVectorSearchEnabled = (): boolean => {
|
|||||||
(isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch) || isFabricNative())
|
(isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch) || isFabricNative())
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isFullTextSearchPreviewFeaturesEnabled = (): boolean => {
|
||||||
|
return (
|
||||||
|
userContext.apiType === "SQL" &&
|
||||||
|
isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearchPreviewFeatures)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
|
|
||||||
interface CarouselState {
|
interface CarouselState {
|
||||||
shouldOpen: boolean;
|
shouldOpen: boolean;
|
||||||
@@ -10,15 +9,11 @@ interface CarouselState {
|
|||||||
setShowCopilotCarousel: (showCopilotCarousel: boolean) => void;
|
setShowCopilotCarousel: (showCopilotCarousel: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCarousel = create<CarouselState>()(
|
export const useCarousel: UseStore<CarouselState> = create((set) => ({
|
||||||
subscribeWithSelector(
|
shouldOpen: false,
|
||||||
(set) => ({
|
showCoachMark: false,
|
||||||
shouldOpen: false,
|
showCopilotCarousel: false,
|
||||||
showCoachMark: false,
|
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
|
||||||
showCopilotCarousel: false,
|
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
|
||||||
setShouldOpen: (shouldOpen: boolean) => set({ shouldOpen }),
|
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }),
|
||||||
setShowCoachMark: (showCoachMark: boolean) => set({ showCoachMark }),
|
}));
|
||||||
setShowCopilotCarousel: (showCopilotCarousel: boolean) => set({ showCopilotCarousel }),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
interface ClientWriteEnabledState {
|
interface ClientWriteEnabledState {
|
||||||
clientWriteEnabled: boolean;
|
clientWriteEnabled: boolean;
|
||||||
setClientWriteEnabled: (writeEnabled: boolean) => void;
|
setClientWriteEnabled: (writeEnabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useClientWriteEnabled = create<ClientWriteEnabledState>((set) => ({
|
export const useClientWriteEnabled: UseStore<ClientWriteEnabledState> = create((set) => ({
|
||||||
clientWriteEnabled: true,
|
clientWriteEnabled: true,
|
||||||
setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }),
|
setClientWriteEnabled: (clientWriteEnabled: boolean) => set({ clientWriteEnabled }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
|
import { getDataTransferJobs } from "Common/dataAccess/dataTransfers";
|
||||||
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
|
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface DataTransferJobsState {
|
export interface DataTransferJobsState {
|
||||||
dataTransferJobs: DataTransferJobGetResults[];
|
dataTransferJobs: DataTransferJobGetResults[];
|
||||||
@@ -9,7 +9,9 @@ export interface DataTransferJobsState {
|
|||||||
setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void;
|
setPollingDataTransferJobs: (pollingDataTransferJobs: Set<string>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDataTransferJobs = create<DataTransferJobsState>((set) => ({
|
type DataTransferJobStore = UseStore<DataTransferJobsState>;
|
||||||
|
|
||||||
|
export const useDataTransferJobs: DataTransferJobStore = create((set) => ({
|
||||||
dataTransferJobs: [],
|
dataTransferJobs: [],
|
||||||
pollingDataTransferJobs: new Set<string>(),
|
pollingDataTransferJobs: new Set<string>(),
|
||||||
setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),
|
setDataTransferJobs: (dataTransferJobs: DataTransferJobGetResults[]) => set({ dataTransferJobs }),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface NotebookSnapshotHooks {
|
export interface NotebookSnapshotHooks {
|
||||||
snapshot?: string;
|
snapshot?: string;
|
||||||
@@ -7,7 +7,7 @@ export interface NotebookSnapshotHooks {
|
|||||||
setError: (error: string) => void;
|
setError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebookSnapshotStore = create<NotebookSnapshotHooks>((set) => ({
|
export const useNotebookSnapshotStore: UseStore<NotebookSnapshotHooks> = create((set) => ({
|
||||||
snapshot: undefined,
|
snapshot: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),
|
setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||||
|
|
||||||
export interface NotificationConsoleState {
|
export interface NotificationConsoleState {
|
||||||
@@ -15,7 +15,7 @@ export interface NotificationConsoleState {
|
|||||||
setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void;
|
setConsoleAnimationFinished: (consoleAnimationFinished: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotificationConsole = create<NotificationConsoleState>((set) => ({
|
export const useNotificationConsole: UseStore<NotificationConsoleState> = create((set) => ({
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
consoleData: undefined,
|
consoleData: undefined,
|
||||||
inProgressConsoleDataIdToBeDeleted: "",
|
inProgressConsoleDataIdToBeDeleted: "",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
|
|
||||||
interface TeachingBubbleState {
|
interface TeachingBubbleState {
|
||||||
showPostgreTeachingBubble: boolean;
|
showPostgreTeachingBubble: boolean;
|
||||||
@@ -8,13 +7,9 @@ interface TeachingBubbleState {
|
|||||||
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void;
|
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePostgres = create<TeachingBubbleState>()(
|
export const usePostgres: UseStore<TeachingBubbleState> = create((set) => ({
|
||||||
subscribeWithSelector(
|
showPostgreTeachingBubble: false,
|
||||||
(set) => ({
|
showResetPasswordBubble: false,
|
||||||
showPostgreTeachingBubble: false,
|
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
|
||||||
showResetPasswordBubble: false,
|
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }),
|
||||||
setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }),
|
}));
|
||||||
setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { QueryResults } from "Contracts/ViewModels";
|
|||||||
import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { CopilotMessage, CopilotSchemaAllocationInfo } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
import { guid } from "Explorer/Tables/Utilities";
|
import { guid } from "Explorer/Tables/Utilities";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { ContainerInfo } from "../Contracts/DataModels";
|
import { ContainerInfo } from "../Contracts/DataModels";
|
||||||
|
|
||||||
@@ -97,12 +96,120 @@ export interface QueryCopilotState {
|
|||||||
resetQueryCopilotStates: () => void;
|
resetQueryCopilotStates: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
type QueryCopilotStore = UseStore<Partial<QueryCopilotState>>;
|
||||||
subscribeWithSelector(
|
|
||||||
(set) => ({
|
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
copilotEnabled: false,
|
copilotEnabled: false,
|
||||||
copilotUserDBEnabled: false,
|
copilotUserDBEnabled: false,
|
||||||
copilotSampleDBEnabled: false,
|
copilotSampleDBEnabled: false,
|
||||||
|
generatedQuery: "",
|
||||||
|
likeQuery: false,
|
||||||
|
userPrompt: "",
|
||||||
|
showFeedbackModal: false,
|
||||||
|
hideFeedbackModalForLikedQueries: false,
|
||||||
|
correlationId: "",
|
||||||
|
query: "SELECT * FROM c",
|
||||||
|
selectedQuery: "",
|
||||||
|
isGeneratingQuery: null,
|
||||||
|
isGeneratingExplanation: false,
|
||||||
|
isExecuting: false,
|
||||||
|
dislikeQuery: undefined,
|
||||||
|
showCallout: false,
|
||||||
|
showSamplePrompts: false,
|
||||||
|
queryIterator: undefined,
|
||||||
|
queryResults: undefined,
|
||||||
|
errors: [],
|
||||||
|
isSamplePromptsOpen: false,
|
||||||
|
showDeletePopup: false,
|
||||||
|
showFeedbackBar: false,
|
||||||
|
showCopyPopup: false,
|
||||||
|
showErrorMessageBar: false,
|
||||||
|
showInvalidQueryMessageBar: false,
|
||||||
|
generatedQueryComments: "",
|
||||||
|
wasCopilotUsed: false,
|
||||||
|
showWelcomeSidebar: true,
|
||||||
|
showCopilotSidebar: false,
|
||||||
|
chatMessages: [],
|
||||||
|
shouldIncludeInMessages: true,
|
||||||
|
showExplanationBubble: false,
|
||||||
|
notebookServerInfo: {
|
||||||
|
notebookServerEndpoint: undefined,
|
||||||
|
authToken: undefined,
|
||||||
|
forwardingId: undefined,
|
||||||
|
},
|
||||||
|
containerStatus: {
|
||||||
|
status: undefined,
|
||||||
|
durationLeftInMinutes: undefined,
|
||||||
|
phoenixServerInfo: undefined,
|
||||||
|
},
|
||||||
|
schemaAllocationInfo: {
|
||||||
|
databaseId: undefined,
|
||||||
|
containerId: undefined,
|
||||||
|
},
|
||||||
|
isAllocatingContainer: false,
|
||||||
|
copilotEnabledforExecution: false,
|
||||||
|
|
||||||
|
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
||||||
|
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
||||||
|
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
|
||||||
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||||
|
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||||
|
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
||||||
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||||
|
set({ hideFeedbackModalForLikedQueries }),
|
||||||
|
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||||
|
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
||||||
|
setQuery: (query: string) => set({ query }),
|
||||||
|
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
||||||
|
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
||||||
|
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
||||||
|
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
||||||
|
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
||||||
|
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
||||||
|
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
||||||
|
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
||||||
|
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
||||||
|
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
||||||
|
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
||||||
|
setErrors: (errors: QueryError[]) => set({ errors }),
|
||||||
|
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
||||||
|
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
||||||
|
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
||||||
|
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
||||||
|
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
||||||
|
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
|
||||||
|
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
||||||
|
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
||||||
|
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
||||||
|
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
||||||
|
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
||||||
|
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
||||||
|
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
||||||
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
|
set({ notebookServerInfo }),
|
||||||
|
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||||
|
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
|
||||||
|
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
|
||||||
|
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
|
||||||
|
|
||||||
|
resetContainerConnection: (): void => {
|
||||||
|
useTabs.getState().closeAllNotebookTabs(true);
|
||||||
|
useQueryCopilot.getState().setNotebookServerInfo(undefined);
|
||||||
|
useQueryCopilot.getState().setIsAllocatingContainer(false);
|
||||||
|
useQueryCopilot.getState().setContainerStatus({
|
||||||
|
status: undefined,
|
||||||
|
durationLeftInMinutes: undefined,
|
||||||
|
phoenixServerInfo: undefined,
|
||||||
|
});
|
||||||
|
useQueryCopilot.getState().setSchemaAllocationInfo({
|
||||||
|
databaseId: undefined,
|
||||||
|
containerId: undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetQueryCopilotStates: () => {
|
||||||
|
set((state) => ({
|
||||||
|
...state,
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
userPrompt: "",
|
userPrompt: "",
|
||||||
@@ -111,15 +218,15 @@ export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
|||||||
correlationId: "",
|
correlationId: "",
|
||||||
query: "SELECT * FROM c",
|
query: "SELECT * FROM c",
|
||||||
selectedQuery: "",
|
selectedQuery: "",
|
||||||
isGeneratingQuery: null as boolean,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined as (boolean | undefined),
|
dislikeQuery: undefined,
|
||||||
showCallout: false,
|
showCallout: false,
|
||||||
showSamplePrompts: false,
|
showSamplePrompts: false,
|
||||||
queryIterator: undefined as MinimalQueryIterator | undefined,
|
queryIterator: undefined,
|
||||||
queryResults: undefined as QueryResults | undefined,
|
queryResults: undefined,
|
||||||
errors: [] as QueryError[],
|
errors: [],
|
||||||
isSamplePromptsOpen: false,
|
isSamplePromptsOpen: false,
|
||||||
showDeletePopup: false,
|
showDeletePopup: false,
|
||||||
showFeedbackBar: false,
|
showFeedbackBar: false,
|
||||||
@@ -128,135 +235,25 @@ export const useQueryCopilot = create<Partial<QueryCopilotState>>()(
|
|||||||
showInvalidQueryMessageBar: false,
|
showInvalidQueryMessageBar: false,
|
||||||
generatedQueryComments: "",
|
generatedQueryComments: "",
|
||||||
wasCopilotUsed: false,
|
wasCopilotUsed: false,
|
||||||
showWelcomeSidebar: true,
|
|
||||||
showCopilotSidebar: false,
|
showCopilotSidebar: false,
|
||||||
chatMessages: [] as CopilotMessage[],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
forwardingId: undefined,
|
||||||
} as DataModels.NotebookWorkspaceConnectionInfo,
|
},
|
||||||
containerStatus: {
|
containerStatus: {
|
||||||
status: undefined,
|
status: undefined,
|
||||||
durationLeftInMinutes: undefined,
|
durationLeftInMinutes: undefined,
|
||||||
phoenixServerInfo: undefined,
|
phoenixServerInfo: undefined,
|
||||||
} as ContainerInfo,
|
},
|
||||||
schemaAllocationInfo: {
|
schemaAllocationInfo: {
|
||||||
databaseId: undefined,
|
databaseId: undefined,
|
||||||
containerId: undefined,
|
containerId: undefined,
|
||||||
} as CopilotSchemaAllocationInfo,
|
},
|
||||||
isAllocatingContainer: false,
|
isAllocatingContainer: false,
|
||||||
copilotEnabledforExecution: false,
|
}));
|
||||||
|
},
|
||||||
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
}));
|
||||||
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
|
||||||
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
|
||||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
|
||||||
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
|
||||||
set({ hideFeedbackModalForLikedQueries }),
|
|
||||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
|
||||||
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
|
||||||
setQuery: (query: string) => set({ query }),
|
|
||||||
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
|
||||||
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
|
||||||
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
|
||||||
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
|
||||||
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
|
||||||
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
|
||||||
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
|
||||||
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
|
||||||
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
|
||||||
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
|
||||||
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
|
||||||
setErrors: (errors: QueryError[]) => set({ errors }),
|
|
||||||
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
|
||||||
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
|
||||||
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
|
||||||
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
|
||||||
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
|
||||||
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
|
|
||||||
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
|
||||||
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
|
||||||
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
|
||||||
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
|
||||||
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
|
||||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
|
||||||
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
|
||||||
set({ notebookServerInfo }),
|
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
setIsAllocatingContainer: (isAllocatingContainer: boolean) => set({ isAllocatingContainer }),
|
|
||||||
setSchemaAllocationInfo: (schemaAllocationInfo: CopilotSchemaAllocationInfo) => set({ schemaAllocationInfo }),
|
|
||||||
setCopilotEnabledforExecution: (copilotEnabledforExecution: boolean) => set({ copilotEnabledforExecution }),
|
|
||||||
|
|
||||||
resetContainerConnection: (): void => {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useQueryCopilot.getState().setNotebookServerInfo(undefined);
|
|
||||||
useQueryCopilot.getState().setIsAllocatingContainer(false);
|
|
||||||
useQueryCopilot.getState().setContainerStatus({
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
});
|
|
||||||
useQueryCopilot.getState().setSchemaAllocationInfo({
|
|
||||||
databaseId: undefined,
|
|
||||||
containerId: undefined,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetQueryCopilotStates: () => {
|
|
||||||
set((state) => ({
|
|
||||||
...state,
|
|
||||||
generatedQuery: "",
|
|
||||||
likeQuery: false,
|
|
||||||
userPrompt: "",
|
|
||||||
showFeedbackModal: false,
|
|
||||||
hideFeedbackModalForLikedQueries: false,
|
|
||||||
correlationId: "",
|
|
||||||
query: "SELECT * FROM c",
|
|
||||||
selectedQuery: "",
|
|
||||||
isGeneratingQuery: false,
|
|
||||||
isGeneratingExplanation: false,
|
|
||||||
isExecuting: false,
|
|
||||||
dislikeQuery: undefined,
|
|
||||||
showCallout: false,
|
|
||||||
showSamplePrompts: false,
|
|
||||||
queryIterator: undefined,
|
|
||||||
queryResults: undefined,
|
|
||||||
errors: [],
|
|
||||||
isSamplePromptsOpen: false,
|
|
||||||
showDeletePopup: false,
|
|
||||||
showFeedbackBar: false,
|
|
||||||
showCopyPopup: false,
|
|
||||||
showErrorMessageBar: false,
|
|
||||||
showInvalidQueryMessageBar: false,
|
|
||||||
generatedQueryComments: "",
|
|
||||||
wasCopilotUsed: false,
|
|
||||||
showCopilotSidebar: false,
|
|
||||||
chatMessages: [],
|
|
||||||
shouldIncludeInMessages: true,
|
|
||||||
showExplanationBubble: false,
|
|
||||||
notebookServerInfo: {
|
|
||||||
notebookServerEndpoint: undefined,
|
|
||||||
authToken: undefined,
|
|
||||||
forwardingId: undefined,
|
|
||||||
},
|
|
||||||
containerStatus: {
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
phoenixServerInfo: undefined,
|
|
||||||
},
|
|
||||||
schemaAllocationInfo: {
|
|
||||||
databaseId: undefined,
|
|
||||||
containerId: undefined,
|
|
||||||
},
|
|
||||||
isAllocatingContainer: false,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
export interface SidePanelState {
|
export interface SidePanelState {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -9,7 +9,7 @@ export interface SidePanelState {
|
|||||||
closeSidePanel: () => void;
|
closeSidePanel: () => void;
|
||||||
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
|
getRef?: React.RefObject<HTMLElement>; // Optional ref for focusing the last element.
|
||||||
}
|
}
|
||||||
export const useSidePanel = create<SidePanelState>((set) => ({
|
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
panelWidth: "440px",
|
panelWidth: "440px",
|
||||||
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import {
|
|||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
saveSubComponentState,
|
saveSubComponentState,
|
||||||
} from "Shared/AppStatePersistenceUtility";
|
} from "Shared/AppStatePersistenceUtility";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { subscribeWithSelector } from "zustand/middleware";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||||
@@ -52,198 +51,194 @@ export enum ReactTabKind {
|
|||||||
QueryCopilot,
|
QueryCopilot,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTabs = create<TabsState>()(
|
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
subscribeWithSelector(
|
openedTabs: [] as TabsBase[],
|
||||||
(set, get) => ({
|
openedReactTabs: [ReactTabKind.Home],
|
||||||
openedTabs: [] as TabsBase[],
|
activeTab: undefined as TabsBase,
|
||||||
openedReactTabs: [ReactTabKind.Home],
|
activeReactTab: ReactTabKind.Home,
|
||||||
activeTab: undefined as TabsBase,
|
queryCopilotTabInitialInput: "",
|
||||||
activeReactTab: ReactTabKind.Home,
|
isTabExecuting: false,
|
||||||
queryCopilotTabInitialInput: "",
|
isQueryErrorThrown: false,
|
||||||
isTabExecuting: false,
|
activateTab: (tab: TabsBase): void => {
|
||||||
isQueryErrorThrown: false,
|
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
||||||
activateTab: (tab: TabsBase): void => {
|
set({ activeTab: tab, activeReactTab: undefined });
|
||||||
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
tab.onActivate();
|
||||||
set({ activeTab: tab, activeReactTab: undefined });
|
}
|
||||||
tab.onActivate();
|
},
|
||||||
|
activateNewTab: (tab: TabsBase): void => {
|
||||||
|
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
|
||||||
|
tab.triggerPersistState = get().persistTabsState;
|
||||||
|
tab.onActivate();
|
||||||
|
get().persistTabsState();
|
||||||
|
},
|
||||||
|
activateReactTab: (tabKind: ReactTabKind): void => {
|
||||||
|
// Clear the selected node when switching to a react tab.
|
||||||
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
|
set({ activeTab: undefined, activeReactTab: tabKind });
|
||||||
|
},
|
||||||
|
updateTab: (tab: TabsBase) => {
|
||||||
|
if (get().activeTab?.tabId === tab.tabId) {
|
||||||
|
set({ activeTab: tab });
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
openedTabs: state.openedTabs.map((openedTab) => {
|
||||||
|
if (openedTab.tabId === tab.tabId) {
|
||||||
|
return tab;
|
||||||
}
|
}
|
||||||
},
|
return openedTab;
|
||||||
activateNewTab: (tab: TabsBase): void => {
|
}),
|
||||||
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
|
}));
|
||||||
tab.triggerPersistState = get().persistTabsState;
|
},
|
||||||
tab.onActivate();
|
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
|
||||||
get().persistTabsState();
|
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
|
||||||
},
|
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
|
||||||
activateReactTab: (tabKind: ReactTabKind): void => {
|
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||||
// Clear the selected node when switching to a react tab.
|
const activeTab = get().activeTab;
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
activeTab && comparator(activeTab) && activeTab.onActivate();
|
||||||
set({ activeTab: undefined, activeReactTab: tabKind });
|
},
|
||||||
},
|
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
|
||||||
updateTab: (tab: TabsBase) => {
|
get()
|
||||||
if (get().activeTab?.tabId === tab.tabId) {
|
.openedTabs.filter(comparator)
|
||||||
set({ activeTab: tab });
|
.forEach((tab) => tab.onCloseTabButtonClick()),
|
||||||
|
closeTab: (tab: TabsBase): void => {
|
||||||
|
let tabIndex: number;
|
||||||
|
const { activeTab, openedTabs, openedReactTabs } = get();
|
||||||
|
const updatedTabs = openedTabs.filter((openedTab, index) => {
|
||||||
|
if (tab.tabId === openedTab.tabId) {
|
||||||
|
tabIndex = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (updatedTabs.length === 0 && !isFabricMirrored()) {
|
||||||
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
|
||||||
|
const tabToTheRight = updatedTabs[tabIndex];
|
||||||
|
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
||||||
|
const newActiveTab = tabToTheRight ?? lastOpenTab;
|
||||||
|
set({ activeTab: newActiveTab });
|
||||||
|
if (newActiveTab) {
|
||||||
|
newActiveTab.onActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ openedTabs: updatedTabs });
|
||||||
|
|
||||||
|
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
|
||||||
|
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
get().persistTabsState();
|
||||||
|
},
|
||||||
|
closeAllNotebookTabs: (hardClose): void => {
|
||||||
|
const isNotebook = (tabKind: CollectionTabKind): boolean => {
|
||||||
|
if (
|
||||||
|
tabKind === CollectionTabKind.Notebook ||
|
||||||
|
tabKind === CollectionTabKind.NotebookV2 ||
|
||||||
|
tabKind === CollectionTabKind.SchemaAnalyzer ||
|
||||||
|
tabKind === CollectionTabKind.Terminal
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabList = get().openedTabs;
|
||||||
|
if (tabList && tabList.length > 0) {
|
||||||
|
tabList.forEach((tab: NotebookTabV2) => {
|
||||||
|
const tabKind: CollectionTabKind = tab.tabKind;
|
||||||
|
if (tabKind && isNotebook(tabKind)) {
|
||||||
|
tab.onCloseTabButtonClick(hardClose);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
set((state) => ({
|
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
|
||||||
openedTabs: state.openedTabs.map((openedTab) => {
|
set({ activeTab: undefined, activeReactTab: undefined });
|
||||||
if (openedTab.tabId === tab.tabId) {
|
}
|
||||||
return tab;
|
}
|
||||||
}
|
},
|
||||||
return openedTab;
|
openAndActivateReactTab: (tabKind: ReactTabKind) => {
|
||||||
}),
|
if (get().openedReactTabs.indexOf(tabKind) === -1) {
|
||||||
}));
|
set((state) => ({
|
||||||
},
|
openedReactTabs: [...state.openedReactTabs, tabKind],
|
||||||
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
|
}));
|
||||||
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
|
}
|
||||||
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
|
|
||||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
|
||||||
const activeTab = get().activeTab;
|
|
||||||
activeTab && comparator(activeTab) && activeTab.onActivate();
|
|
||||||
},
|
|
||||||
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
|
|
||||||
get()
|
|
||||||
.openedTabs.filter(comparator)
|
|
||||||
.forEach((tab) => tab.onCloseTabButtonClick()),
|
|
||||||
closeTab: (tab: TabsBase): void => {
|
|
||||||
let tabIndex: number;
|
|
||||||
const { activeTab, openedTabs, openedReactTabs } = get();
|
|
||||||
const updatedTabs = openedTabs.filter((openedTab, index) => {
|
|
||||||
if (tab.tabId === openedTab.tabId) {
|
|
||||||
tabIndex = index;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
if (updatedTabs.length === 0 && !isFabricMirrored()) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab.tabId === activeTab?.tabId && tabIndex !== -1) {
|
set({ activeTab: undefined, activeReactTab: tabKind });
|
||||||
const tabToTheRight = updatedTabs[tabIndex];
|
},
|
||||||
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
closeReactTab: (tabKind: ReactTabKind) => {
|
||||||
const newActiveTab = tabToTheRight ?? lastOpenTab;
|
const { activeReactTab, openedTabs, openedReactTabs } = get();
|
||||||
set({ activeTab: newActiveTab });
|
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab);
|
||||||
if (newActiveTab) {
|
if (activeReactTab === tabKind) {
|
||||||
newActiveTab.onActivate();
|
openedTabs?.length > 0
|
||||||
}
|
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
|
||||||
}
|
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] });
|
||||||
|
}
|
||||||
|
|
||||||
set({ openedTabs: updatedTabs });
|
set({ openedReactTabs: updatedOpenedReactTabs });
|
||||||
|
},
|
||||||
|
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
|
||||||
|
setIsTabExecuting: (state: boolean) => {
|
||||||
|
set({ isTabExecuting: state });
|
||||||
|
},
|
||||||
|
setIsQueryErrorThrown: (state: boolean) => {
|
||||||
|
set({ isQueryErrorThrown: state });
|
||||||
|
},
|
||||||
|
getCurrentTabIndex: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
return state.openedReactTabs.indexOf(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
|
||||||
|
if (nonReactTabIndex !== -1) {
|
||||||
|
return state.openedReactTabs.length + nonReactTabIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
|
return -1;
|
||||||
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
|
},
|
||||||
}
|
selectTabByIndex: (index: number) => {
|
||||||
|
const state = get();
|
||||||
|
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
|
||||||
|
const clampedIndex = clamp(index, totalTabCount - 1, 0);
|
||||||
|
|
||||||
get().persistTabsState();
|
if (clampedIndex < state.openedReactTabs.length) {
|
||||||
},
|
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
|
||||||
closeAllNotebookTabs: (hardClose): void => {
|
} else {
|
||||||
const isNotebook = (tabKind: CollectionTabKind): boolean => {
|
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
|
||||||
if (
|
}
|
||||||
tabKind === CollectionTabKind.Notebook ||
|
},
|
||||||
tabKind === CollectionTabKind.NotebookV2 ||
|
selectLeftTab: () => {
|
||||||
tabKind === CollectionTabKind.SchemaAnalyzer ||
|
const state = get();
|
||||||
tabKind === CollectionTabKind.Terminal
|
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
|
||||||
) {
|
},
|
||||||
return true;
|
selectRightTab: () => {
|
||||||
}
|
const state = get();
|
||||||
return false;
|
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
|
||||||
};
|
},
|
||||||
|
closeActiveTab: () => {
|
||||||
|
const state = get();
|
||||||
|
if (state.activeReactTab !== undefined) {
|
||||||
|
state.closeReactTab(state.activeReactTab);
|
||||||
|
} else if (state.activeTab !== undefined) {
|
||||||
|
state.closeTab(state.activeTab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeAllTabs: () => {
|
||||||
|
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
|
||||||
|
},
|
||||||
|
persistTabsState: () => {
|
||||||
|
const state = get();
|
||||||
|
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
|
||||||
|
|
||||||
const tabList = get().openedTabs;
|
saveSubComponentState<OpenTab[]>(
|
||||||
if (tabList && tabList.length > 0) {
|
AppStateComponentNames.DataExplorerAction,
|
||||||
tabList.forEach((tab: NotebookTabV2) => {
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
const tabKind: CollectionTabKind = tab.tabKind;
|
undefined,
|
||||||
if (tabKind && isNotebook(tabKind)) {
|
openTabsStates,
|
||||||
tab.onCloseTabButtonClick(hardClose);
|
);
|
||||||
}
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
if (get().openedTabs.length === 0 && !isFabricMirrored()) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openAndActivateReactTab: (tabKind: ReactTabKind) => {
|
|
||||||
if (get().openedReactTabs.indexOf(tabKind) === -1) {
|
|
||||||
set((state) => ({
|
|
||||||
openedReactTabs: [...state.openedReactTabs, tabKind],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
set({ activeTab: undefined, activeReactTab: tabKind });
|
|
||||||
},
|
|
||||||
closeReactTab: (tabKind: ReactTabKind) => {
|
|
||||||
const { activeReactTab, openedTabs, openedReactTabs } = get();
|
|
||||||
const updatedOpenedReactTabs = openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab);
|
|
||||||
if (activeReactTab === tabKind) {
|
|
||||||
openedTabs?.length > 0
|
|
||||||
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
|
|
||||||
: set({ activeTab: undefined, activeReactTab: updatedOpenedReactTabs[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
set({ openedReactTabs: updatedOpenedReactTabs });
|
|
||||||
},
|
|
||||||
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
|
|
||||||
setIsTabExecuting: (state: boolean) => {
|
|
||||||
set({ isTabExecuting: state });
|
|
||||||
},
|
|
||||||
setIsQueryErrorThrown: (state: boolean) => {
|
|
||||||
set({ isQueryErrorThrown: state });
|
|
||||||
},
|
|
||||||
getCurrentTabIndex: () => {
|
|
||||||
const state = get();
|
|
||||||
if (state.activeReactTab !== undefined) {
|
|
||||||
return state.openedReactTabs.indexOf(state.activeReactTab);
|
|
||||||
} else if (state.activeTab !== undefined) {
|
|
||||||
const nonReactTabIndex = state.openedTabs.indexOf(state.activeTab);
|
|
||||||
if (nonReactTabIndex !== -1) {
|
|
||||||
return state.openedReactTabs.length + nonReactTabIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
selectTabByIndex: (index: number) => {
|
|
||||||
const state = get();
|
|
||||||
const totalTabCount = state.openedReactTabs.length + state.openedTabs.length;
|
|
||||||
const clampedIndex = clamp(index, totalTabCount - 1, 0);
|
|
||||||
|
|
||||||
if (clampedIndex < state.openedReactTabs.length) {
|
|
||||||
set({ activeTab: undefined, activeReactTab: state.openedReactTabs[clampedIndex] });
|
|
||||||
} else {
|
|
||||||
set({ activeTab: state.openedTabs[clampedIndex - state.openedReactTabs.length], activeReactTab: undefined });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectLeftTab: () => {
|
|
||||||
const state = get();
|
|
||||||
state.selectTabByIndex(state.getCurrentTabIndex() - 1);
|
|
||||||
},
|
|
||||||
selectRightTab: () => {
|
|
||||||
const state = get();
|
|
||||||
state.selectTabByIndex(state.getCurrentTabIndex() + 1);
|
|
||||||
},
|
|
||||||
closeActiveTab: () => {
|
|
||||||
const state = get();
|
|
||||||
if (state.activeReactTab !== undefined) {
|
|
||||||
state.closeReactTab(state.activeReactTab);
|
|
||||||
} else if (state.activeTab !== undefined) {
|
|
||||||
state.closeTab(state.activeTab);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closeAllTabs: () => {
|
|
||||||
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
|
|
||||||
},
|
|
||||||
persistTabsState: () => {
|
|
||||||
const state = get();
|
|
||||||
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
|
|
||||||
|
|
||||||
saveSubComponentState<OpenTab[]>(
|
|
||||||
AppStateComponentNames.DataExplorerAction,
|
|
||||||
OPEN_TABS_SUBCOMPONENT_NAME,
|
|
||||||
undefined,
|
|
||||||
openTabsStates,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Collection } from "Contracts/ViewModels";
|
import { Collection } from "Contracts/ViewModels";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
interface TeachingBubbleState {
|
interface TeachingBubbleState {
|
||||||
step: number;
|
step: number;
|
||||||
@@ -12,7 +12,7 @@ interface TeachingBubbleState {
|
|||||||
setSampleCollection: (sampleCollection: Collection) => void;
|
setSampleCollection: (sampleCollection: Collection) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTeachingBubble = create<TeachingBubbleState>((set) => ({
|
export const useTeachingBubble: UseStore<TeachingBubbleState> = create((set) => ({
|
||||||
step: 1,
|
step: 1,
|
||||||
isSampleDBExpanded: false,
|
isSampleDBExpanded: false,
|
||||||
isDocumentsTabOpened: false,
|
isDocumentsTabOpened: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import postRobot from "post-robot";
|
import postRobot from "post-robot";
|
||||||
import { create } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
interface TerminalState {
|
interface TerminalState {
|
||||||
terminalWindow: Window;
|
terminalWindow: Window;
|
||||||
@@ -7,7 +7,7 @@ interface TerminalState {
|
|||||||
sendMessage: (message: string) => void;
|
sendMessage: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTerminal = create<TerminalState>((set, get) => ({
|
export const useTerminal: UseStore<TerminalState> = create((set, get) => ({
|
||||||
terminalWindow: undefined,
|
terminalWindow: undefined,
|
||||||
setTerminal: (terminalWindow: Window) => {
|
setTerminal: (terminalWindow: Window) => {
|
||||||
set({ terminalWindow });
|
set({ terminalWindow });
|
||||||
|
|||||||
79
test/fx.ts
79
test/fx.ts
@@ -87,27 +87,70 @@ export async function getTestExplorerUrl(accountType: TestAccount, iframeSrc?: s
|
|||||||
params.set("feature.enableCopilot", "false");
|
params.set("feature.enableCopilot", "false");
|
||||||
|
|
||||||
const nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN;
|
const nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN;
|
||||||
if (nosqlRbacToken) {
|
|
||||||
params.set("nosqlRbacToken", nosqlRbacToken);
|
|
||||||
params.set("enableaaddataplane", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
||||||
if (nosqlReadOnlyRbacToken) {
|
|
||||||
params.set("nosqlReadOnlyRbacToken", nosqlReadOnlyRbacToken);
|
|
||||||
params.set("enableaaddataplane", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableRbacToken = process.env.TABLE_TESTACCOUNT_TOKEN;
|
const tableRbacToken = process.env.TABLE_TESTACCOUNT_TOKEN;
|
||||||
if (tableRbacToken) {
|
|
||||||
params.set("tableRbacToken", tableRbacToken);
|
|
||||||
params.set("enableaaddataplane", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
const gremlinRbacToken = process.env.GREMLIN_TESTACCOUNT_TOKEN;
|
const gremlinRbacToken = process.env.GREMLIN_TESTACCOUNT_TOKEN;
|
||||||
if (gremlinRbacToken) {
|
const cassandraRbacToken = process.env.CASSANDRA_TESTACCOUNT_TOKEN;
|
||||||
params.set("gremlinRbacToken", gremlinRbacToken);
|
const mongoRbacToken = process.env.MONGO_TESTACCOUNT_TOKEN;
|
||||||
params.set("enableaaddataplane", "true");
|
const mongo32RbacToken = process.env.MONGO32_TESTACCOUNT_TOKEN;
|
||||||
|
const mongoReadOnlyRbacToken = process.env.MONGO_READONLY_TESTACCOUNT_TOKEN;
|
||||||
|
|
||||||
|
switch (accountType) {
|
||||||
|
case TestAccount.SQL:
|
||||||
|
if (nosqlRbacToken) {
|
||||||
|
params.set("nosqlRbacToken", nosqlRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.SQLReadOnly:
|
||||||
|
if (nosqlReadOnlyRbacToken) {
|
||||||
|
params.set("nosqlReadOnlyRbacToken", nosqlReadOnlyRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.Tables:
|
||||||
|
if (tableRbacToken) {
|
||||||
|
params.set("tableRbacToken", tableRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.Gremlin:
|
||||||
|
if (gremlinRbacToken) {
|
||||||
|
params.set("gremlinRbacToken", gremlinRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.Cassandra:
|
||||||
|
if (cassandraRbacToken) {
|
||||||
|
params.set("cassandraRbacToken", cassandraRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.Mongo:
|
||||||
|
if (mongoRbacToken) {
|
||||||
|
params.set("mongoRbacToken", mongoRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.Mongo32:
|
||||||
|
if (mongo32RbacToken) {
|
||||||
|
params.set("mongo32RbacToken", mongo32RbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestAccount.MongoReadonly:
|
||||||
|
if (mongoReadOnlyRbacToken) {
|
||||||
|
params.set("mongoReadOnlyRbacToken", mongoReadOnlyRbacToken);
|
||||||
|
params.set("enableaaddataplane", "true");
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iframeSrc) {
|
if (iframeSrc) {
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ const nosqlReadOnlyRbacToken =
|
|||||||
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
||||||
const gremlinRbacToken = urlSearchParams.get("gremlinRbacToken") || process.env.GREMLIN_TESTACCOUNT_TOKEN || "";
|
const gremlinRbacToken = urlSearchParams.get("gremlinRbacToken") || process.env.GREMLIN_TESTACCOUNT_TOKEN || "";
|
||||||
|
|
||||||
|
const cassandraRbacToken = urlSearchParams.get("cassandraRbacToken") || process.env.CASSANDRA_TESTACCOUNT_TOKEN || "";
|
||||||
|
|
||||||
|
const mongoRbacToken = urlSearchParams.get("mongoRbacToken") || process.env.MONGO_TESTACCOUNT_TOKEN || "";
|
||||||
|
const mongo32RbacToken = urlSearchParams.get("mongo32RbacToken") || process.env.MONGO32_TESTACCOUNT_TOKEN || "";
|
||||||
|
const mongoReadOnlyRbacToken =
|
||||||
|
urlSearchParams.get("mongoReadOnlyRbacToken") || process.env.MONGO_READONLY_TESTACCOUNT_TOKEN || "";
|
||||||
|
|
||||||
const initTestExplorer = async (): Promise<void> => {
|
const initTestExplorer = async (): Promise<void> => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authorizationToken: `bearer ${authToken}`,
|
authorizationToken: `bearer ${authToken}`,
|
||||||
@@ -41,6 +48,18 @@ const initTestExplorer = async (): Promise<void> => {
|
|||||||
case "tables":
|
case "tables":
|
||||||
rbacToken = tableRbacToken;
|
rbacToken = tableRbacToken;
|
||||||
break;
|
break;
|
||||||
|
case "cassandra":
|
||||||
|
rbacToken = cassandraRbacToken;
|
||||||
|
break;
|
||||||
|
case "mongo":
|
||||||
|
rbacToken = mongoRbacToken;
|
||||||
|
break;
|
||||||
|
case "mongo32":
|
||||||
|
rbacToken = mongo32RbacToken;
|
||||||
|
break;
|
||||||
|
case "mongo-readonly":
|
||||||
|
rbacToken = mongoReadOnlyRbacToken;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rbacToken.length > 0) {
|
if (rbacToken.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user