Compare commits

...

28 Commits

Author SHA1 Message Date
Steve Faulkner
26210bcdc5 Merge branch 'master' into fail-safe-nuget-publish 2021-03-04 18:14:03 -06:00
Steve Faulkner
498c39c877 End to End Test Improvements (#474)
* End to End Test Improvements

* indenting

* Log completed

* Fix up some test

* Add delay

Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2021-03-04 18:12:31 -06:00
Steve Faulkner
d62134d228 Allow publishing same nuget package versions 2021-03-04 13:46:32 -06:00
Steve Faulkner
87e016f03c Always publish nuget master build (#472) 2021-03-04 12:24:43 -06:00
Srinath Narayanan
3a1841ad3c Remove injected cell before download (#467)
* remove newcell on download

* addressed pr comments
2021-03-04 00:05:30 -08:00
Jordi Bunster
d314a20b81 Fix subscription leak (#465)
* Fix subscription leak

* Update src/Explorer/SplashScreen/SplashScreen.tsx

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>

* Array needs to exist in the first place

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
2021-03-03 21:59:43 -08:00
Jordi Bunster
7188e8d8c2 Remove Explorer.mostRecentActivity (#455)
This also moves the UI concerns of MostRecentActivity over to SplashScreen

Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2021-03-03 01:24:08 -08:00
Srinath Narayanan
3cd2ec93f2 fixed notebook viewer bug (#461) 2021-03-02 14:19:10 -08:00
Srinath Narayanan
b8e9903287 Added spinner for notebook delete (#458)
* initial UI for delete published nb spinner

* added notebook delete spinner

* addressed PR comments
2021-03-02 13:59:10 -08:00
victor-meng
4127d0f522 Fix sample container is not populated with items (#459) 2021-03-02 12:41:50 -06:00
Srinath Narayanan
56b5a9861b Removed published, favourites tabs for hosted gallery (#457)
* added hosted explorer check

* added check for non null container
2021-03-01 05:24:11 -08:00
Steve Faulkner
10664162c7 Refactor Telemetry to include account name and experience (#452) 2021-02-28 15:56:09 -06:00
Srinath Narayanan
cf01ffa957 removed enableGalleryPublish, enableLinkInjection feature flags (#454)
* removed enableGalleryPublish, enableLinkInjection

* removed ENABLE_GALLERY_PUBLISH
2021-02-26 13:07:44 -08:00
victor-meng
3cc1945140 Fix serverless create issue (#453) 2021-02-25 19:29:29 -06:00
Garrett Ausfeldt
864d9393f2 Change schema endpoints (#447)
* fix resource bug (id is a method)

* change schema endpoints

* fix test

Co-authored-by: REDMOND\gaausfel <gaausfel@microsoft.com>
2021-02-25 11:36:01 -08:00
Steve Faulkner
8629bcbe2d Move "Open Full Screen" to React Panel (#449) 2021-02-24 19:04:28 -06:00
Steve Faulkner
6c90ef2e62 Remove Dialog Adapter (#446) 2021-02-24 18:41:28 -06:00
Steve Faulkner
2d2d8b6efe Remove Unused Tabs (#450) 2021-02-24 17:48:33 -06:00
victor-meng
7cbf7202b0 Fix start with samples in Gremlin API (#448) 2021-02-24 12:17:15 -06:00
Jordi Bunster
e8e5eb55cb Remove SplashScreenComponentAdapter (#440)
Merge SplashScreenComponentAdapter and SplashScreenComponent into one SplashScreen React component.
2021-02-23 11:15:57 -08:00
Steve Faulkner
f0c82a430b Remove AdHoc Access and Token Renewal Pane (#445) 2021-02-23 11:16:00 -06:00
Steve Faulkner
3777b6922e Revert "ci: add Azure Static Web Apps workflow file " This reverts commit aec951694a. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # Your branch is up to date with 'origin/master'. # # Changes to be committed: # deleted: .github/workflows/azure-static-web-apps-victorious-sea-0ea0a4710.yml # 2021-02-22 20:21:40 -06:00
Azure Static Web Apps
aec951694a ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2021-02-22 19:58:22 -06:00
Steve Faulkner
07474b8271 Remove window.authType (#437) 2021-02-22 14:43:58 -06:00
Deborah Chen
e092e5140f Fixing issue with autoscale A/B test (#442)
Currently, the first time a user opens the New Container pane, the autoscale setting is not being read from the flight info. This is because the flight into is being set in resetData(), which is not called the 1st time a user opens the pane. 

This change adds logic to set the flight info on the open call.
2021-02-22 18:54:55 +00:00
Steve Faulkner
1f4074f3e8 Update Coding Guidelines (#441)
Co-authored-by: Christopher Anderson <anderson.chris.john@gmail.com>
2021-02-18 13:18:50 -06:00
Chris-MS-896
1ec0d9a0be End to end testcase for mongoDb index policy (#426)
* 'update for index policy'

* input

* 'refactor for ci/cd'

* 'refactor for ci/cd'

* no message

* 'refractor'

* 'format change'

* added changes

* "refactor"

* ‘test lint’

* "format"

* “push setting back”

* ‘refractor’

* Update test/utils/shared.ts

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>

Co-authored-by: Srinath Narayanan <srnara@microsoft.com>
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-02-18 05:40:58 -08:00
Srinath Narayanan
eddc334cb5 mongo index editor for AAD login + hostedExplorer (#438) 2021-02-17 12:52:47 -08:00
153 changed files with 2303 additions and 3355 deletions

View File

@@ -4,6 +4,8 @@ PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP= PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT= PORTAL_RUNNER_DATABASE_ACCOUNT=
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY= PORTAL_RUNNER_DATABASE_ACCOUNT_KEY=
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT=
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY=
PORTAL_RUNNER_CONNECTION_STRING= PORTAL_RUNNER_CONNECTION_STRING=
NOTEBOOKS_TEST_RUNNER_TENANT_ID= NOTEBOOKS_TEST_RUNNER_TENANT_ID=
NOTEBOOKS_TEST_RUNNER_CLIENT_ID= NOTEBOOKS_TEST_RUNNER_CLIENT_ID=

View File

@@ -162,7 +162,7 @@ src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
src/Explorer/Panes/UploadFilePane.ts src/Explorer/Panes/UploadFilePane.ts
src/Explorer/Panes/UploadItemsPane.ts src/Explorer/Panes/UploadItemsPane.ts
src/Explorer/SplashScreen/SplashScreenComponentAdapter.test.ts src/Explorer/SplashScreen/SplashScreen.test.ts
src/Explorer/Tables/Constants.ts src/Explorer/Tables/Constants.ts
src/Explorer/Tables/DataTable/CacheBase.ts src/Explorer/Tables/DataTable/CacheBase.ts
src/Explorer/Tables/DataTable/DataTableBindingManager.ts src/Explorer/Tables/DataTable/DataTableBindingManager.ts
@@ -200,7 +200,6 @@ src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/SparkMasterTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts src/Explorer/Tabs/TabsBase.ts
@@ -377,8 +376,7 @@ src/Explorer/Notebook/temp/inputs/editor.tsx
src/Explorer/Notebook/temp/markdown-cell.tsx src/Explorer/Notebook/temp/markdown-cell.tsx
src/Explorer/Notebook/temp/source.tsx src/Explorer/Notebook/temp/source.tsx
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
src/Explorer/SplashScreen/SplashScreenComponent.tsx src/Explorer/SplashScreen/SplashScreen.tsx
src/Explorer/SplashScreen/SplashScreenComponentApdapter.tsx
src/Explorer/Tabs/GalleryTab.tsx src/Explorer/Tabs/GalleryTab.tsx
src/Explorer/Tabs/NotebookViewerTab.tsx src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx src/Explorer/Tabs/TerminalTab.tsx

View File

@@ -143,23 +143,9 @@ jobs:
env: env:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendhosted: endtoendhosted:
name: "End to End Hosted Tests" name: "End to End Tests"
needs: [lint, format, compile, unittest] needs: [cleanupaccounts]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: End to End Hosted Tests
run: |
npm ci
npm start &
node utils/cleanupDBs.js
npm run wait-for-server
npm run test:e2e
shell: bash
env: env:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }} PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
@@ -176,15 +162,55 @@ jobs:
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }} CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }} TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html" DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
strategy:
matrix:
test-file:
- ./test/cassandra/container.spec.ts
- ./test/mongo/container.spec.ts
- ./test/mongo/mongoIndexPolicy.spec.ts
- ./test/mongo/openMongoAccount.spec.ts
- ./test/notebooks/uploadAndOpenNotebook.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
- ./test/sql/container.spec.ts
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: npm start &
- run: node utils/cleanupDBs.js
- run: npm run wait-for-server
- name: ${{ matrix['test-file'] }}
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }}
shell: bash
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: screenshots name: screenshots
path: failed-* path: failed-*
cleanupaccounts:
name: "Cleanup Test Database Accounts"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: node utils/cleanupDBs.js
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -200,7 +226,7 @@ jobs:
- run: cp ./configs/prod.json config.json - run: cp ./configs/prod.json config.json
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
name: packages name: packages
with: with:
@@ -208,7 +234,7 @@ jobs:
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility] needs: [build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -225,7 +251,7 @@ jobs:
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec - run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
name: packages name: packages
with: with:

194
CODING_GUIDELINES.md Normal file
View File

@@ -0,0 +1,194 @@
# Coding Guidelines and Recommendations
Cosmos Explorer has been under constant development for over 5 years. As a result, there are many different patterns and practices in the codebase. This document serves as a guide to how we write code and helps avoid propagating practices which are no longer preferred. Each requirement in this document is labeled and color-coded to show the relative importance. In order from highest to lowest importance:
✅ DO this. If you feel you need an exception, engage with the project owners _prior_ to implementation.
⛔️ DO NOT do this. If you feel you need an exception, engage with the project owners _prior_ to implementation.
☑️ YOU SHOULD strongly consider this but it is not a requirement. If not following this advice, please comment code with why and proactively begin a discussion as part of the PR process.
⚠️ YOU SHOULD NOT strongly consider not doing this. If not following this advice, please comment code with why and proactively begin a discussion as part of the PR process.
💭 YOU MAY consider this advice if appropriate to your situation. Other team members may comment on this as part of PR review, but there is no need to be proactive.
## Development Environment
☑️ YOU SHOULD
- Use VSCode and install the following extensions. This setup will catch most linting/formatting/type errors as you develop:
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
💭 YOU MAY
- Use the [GitHub CLI](https://cli.github.com/). It has helpful workflows for submitting PRs as well as for checking out other team member's PRs.
- Use Windows, Linux (including WSL), or OSX. We have team members developing on all three environments.
✅ DO
- Maintain cross-platform compatibility when modifying any engineering or build systems
## Code Formatting
✅ DO
- Use [Prettier](https://prettier.io/) to format your code
- This will occur automatically if using the recommended editor setup
- `npm run format` will also format code
## Linting
✅ DO
- Use [ESLint](https://eslint.org/) to check for code errors.
- This will occur automatically if using the recommended editor setup
- `npm run lint` will also check for linting errors
💭 YOU MAY
- Consider adding new lint rules.
- If you find yourself performing "nits" as part of PR review, consider adding a lint rule that will automatically catch the error in the future
⚠️ YOU SHOULD NOT
- Disable lint rules
- Lint rules exist as guidance and to catch common mistakes
- You will find places we disable specific lint rules however it should be exceptional.
- If a rule does need to be disabled, prefer disabling a specific line instead of the entire file.
⛔️ DO NOT
- Add [TSLint](https://palantir.github.io/tslint/) rules
- TSLint has been deprecated and is on track to be removed
- Always prefer ESLint rules
## UI Components
☑️ YOU SHOULD
- Write new components using [React](https://reactjs.org/). We are actively migrating Cosmos Explorer off of [Knockout](https://knockoutjs.com/).
- Use [Fluent](https://developer.microsoft.com/en-us/fluentui#/) components.
- Fluent components are designed to be highly accessible and composable
- Using Fluent allows us to build upon the work of the Fluent team and leads to a lower total cost of ownership for UI code
### React
☑️ YOU SHOULD
- Use pure functional components when no state is required
💭 YOU MAY
- Use functional (hooks) or class components
- The project contains examples of both
- Neither is strongly preferred at this time
⛔️ DO NOT
- Use inheritance for sharing component behavior.
- React documentation covers this topic in detail https://reactjs.org/docs/composition-vs-inheritance.html
- Suffix your file or component name with "Component"
- Even though the code has examples of it, we are ending the practice.
## Libraries
⚠️ YOU SHOULD NOT
- Add new libraries to package.json.
- Adding libraries may bring in code that explodes the bundled size or attempts to run NodeJS code in the browser
- Consult with project owners for help with library selection if one is needed
⛔️ DO NOT
- Use underscore.js
- Much of this library is now native to JS and will be automatically transpiled
- Use jQuery
- Much of this library is not native to the DOM.
- We are planning to remove it
## Testing
⛔️ DO NOT
- Decrease test coverage
- Unit/Functional test coverage is checked as part of the CI process
### Unit Tests
✅ DO
- Write unit tests for non-UI and utility code.
- Write your tests using [Jest](https://jestjs.io/)
☑️ YOU SHOULD
- Abstract non-UI and utility code so it can run either the NodeJS or Browser environment
### Functional(Component) Tests
✅ DO
- Write tests for UI components
- Write your tests using [Jest](https://jestjs.io/)
- Use either Enzyme or React Testing Library to perform component tests.
### Mocking
✅ DO
- Use Jest's built-in mocking helpers
☑️ YOU SHOULD
- Write code that does not require mocking
- Build components that do not require mocking extremely large or difficult to mock objects (like Explorer.ts). Pass _only_ what you need.
⛔️ DO NOT
- Use sinon.js for mocking
- Sinon has been deprecated and planned for removal
### End to End Tests
✅ DO
- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/)
- Write or modify an existing E2E test that covers the primary use case of any major feature.
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle.
☑️ YOU SHOULD
- Write tests that use accessible attributes to perform actions. Role, Title, Label, etc
- More information https://testing-library.com/docs/queries/about#priority
⚠️ YOU SHOULD NOT
- Add test specfic `data-*` attributes to dom elements
- This is a common current practice, but one we would like to avoid in the future
- End to end tests need to use semantic HTML and accesible attributes to be truely end to end
- No user or screen reader actually navigates an app using `data-*` attributes
- Add arbitrary time delays to wait for page to render or element to be ready.
- All the time delays add up and slow down testing.
- Prefer using the framework's "wait for..." functionality.
### Migrating Knockout to React
✅ DO
- Consult other team members before beginning migration work. There is a significant amount of flux in patterns we are using and it is important we do not propagate incorrect patterns.
- Start by converting HTML to JSX: https://magic.reactjs.net/htmltojsx.htm. Add functionality as a second step.
☑️ YOU SHOULD
- Write React components that require no dependency on Knockout or observables to trigger rendering.
## Browser Support
✅ DO
- Support all [browsers supported by the Azure Portal](https://docs.microsoft.com/en-us/azure/azure-portal/azure-portal-supported-browsers-devices)
- Support IE11
- In practice, this should not need to be considered as part of a normal development workflow
- Polyfills and transpilation are already provided by our engineering systems.
- This requirement will be removed on March 30th, 2021 when Azure drops IE11 support.

View File

@@ -13,6 +13,7 @@ For more information see the [Code of Conduct FAQ](https://opensource.microsoft.
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Microsoft Open Source Code of Conduct ## Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources: Resources:
@@ -20,33 +21,3 @@ Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
## Browser support
Please make sure to support all modern browsers as well as Internet Explorer 11.
For IE support, polyfill is preferred over new usage of lodash or underscore. We already polyfill almost everything by importing babel-polyfill at the top of entry points.
## Coding guidelines, conventions and recommendations
### Typescript
* Follow this [typescript style guide](https://github.com/excelmicro/typescript) which is based on [airbnb's style guide](https://github.com/airbnb/javascript).
* Conventions speficic to this project:
- Use double-quotes for string
- Don't use `null`, use `undefined`
- Pascal case for private static readonly fields
- Camel case for classnames in markup
* Don't use class unless necessary
* Code related to notebooks should be dynamically imported so that it is loaded from a separate bundle only if the account is notebook-enabled. There are already top-level notebook components which are dynamically imported and their dependencies can be statically imported from these files.
* Prefer using [Fluent UI controls](https://developer.microsoft.com/en-us/fluentui#/controls/web) over creating your own, in order to maintain consistency and support a11y.
### React
* Prefer using React class components over function components and hooks unless you have a simple component and require no nested functions:
* Nested functions may be harder to test independently
* Switching from function component to class component later mayb be painful
## Testing
Any PR should not decrease testing coverage.
## Recommended Tools and VS Code extensions
* [Bookmarks](https://github.com/alefragnani/vscode-bookmarks)
* [Bracket pair colorizer](https://github.com/CoenraadS/Bracket-Pair-Colorizer-2)
* [GitHub Pull Requests and Issues](https://github.com/Microsoft/vscode-pull-request-github)

View File

@@ -1,4 +1,3 @@
{ {
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
"ENABLE_GALLERY_PUBLISH": true
} }

68
package-lock.json generated
View File

@@ -218,6 +218,11 @@
} }
} }
}, },
"@azure/ms-rest-azure-env": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz",
"integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw=="
},
"@azure/ms-rest-azure-js": { "@azure/ms-rest-azure-js": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz",
@@ -246,6 +251,16 @@
"xml2js": "^0.4.19" "xml2js": "^0.4.19"
} }
}, },
"@azure/ms-rest-nodeauth": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz",
"integrity": "sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==",
"requires": {
"@azure/ms-rest-azure-env": "^2.0.0",
"@azure/ms-rest-js": "^2.0.4",
"adal-node": "^0.1.28"
}
},
"@azure/msal-common": { "@azure/msal-common": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.7.2.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.7.2.tgz",
@@ -5641,6 +5656,38 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
}, },
"adal-node": {
"version": "0.1.28",
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz",
"integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=",
"requires": {
"@types/node": "^8.0.47",
"async": ">=0.6.0",
"date-utils": "*",
"jws": "3.x.x",
"request": ">= 2.52.0",
"underscore": ">= 1.3.1",
"uuid": "^3.1.0",
"xmldom": ">= 0.1.x",
"xpath.js": "~1.1.0"
},
"dependencies": {
"@types/node": {
"version": "8.10.66",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz",
"integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw=="
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
}
}
},
"agent-base": { "agent-base": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -6059,7 +6106,6 @@
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": { "requires": {
"lodash": "^4.17.14" "lodash": "^4.17.14"
} }
@@ -7288,6 +7334,11 @@
"tiny-emitter": "^2.0.0" "tiny-emitter": "^2.0.0"
} }
}, },
"clipboard-copy": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-4.0.1.tgz",
"integrity": "sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng=="
},
"cliui": { "cliui": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -8520,6 +8571,11 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
"integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==" "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw=="
}, },
"date-utils": {
"version": "1.2.21",
"resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz",
"integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q="
},
"dayjs": { "dayjs": {
"version": "1.8.19", "version": "1.8.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz",
@@ -22579,6 +22635,16 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true "dev": true
}, },
"xmldom": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz",
"integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA=="
},
"xpath.js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz",
"integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ=="
},
"xregexp": { "xregexp": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz",

View File

@@ -8,6 +8,7 @@
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1", "@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.12.12",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "6.0.2",
@@ -49,6 +50,7 @@
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.1.1",

View File

@@ -3,6 +3,7 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/name"] }, "partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [ "data": [
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)", "g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",

View File

@@ -105,8 +105,6 @@ export class Features {
public static readonly hostedDataExplorer = "hosteddataexplorerenabled"; public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks"; public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl"; public static readonly notebookServerUrl = "notebookserverurl";
@@ -130,7 +128,6 @@ export class Flights {
public static readonly MongoIndexEditor = "mongoindexeditor"; public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing"; public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest"; public static readonly AutoscaleTest = "autoscaletest";
public static readonly GalleryPublish = "gallerypublish";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@@ -220,7 +220,6 @@ describe("MongoProxyClient", () => {
describe("getEndpoint", () => { describe("getEndpoint", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
delete window.authType;
updateUserContext({ updateUserContext({
databaseAccount, databaseAccount,
}); });
@@ -241,7 +240,9 @@ describe("MongoProxyClient", () => {
}); });
it("returns a guest endpoint", () => { it("returns a guest endpoint", () => {
window.authType = AuthType.EncryptedToken; updateUserContext({
authType: AuthType.EncryptedToken,
});
const endpoint = getEndpoint(); const endpoint = getEndpoint();
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
}); });

View File

@@ -20,7 +20,7 @@ const defaultHeaders = {
}; };
function authHeaders() { function authHeaders() {
if (window.authType === AuthType.EncryptedToken) { if (userContext.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: userContext.accessToken }; return { [HttpHeaders.guestAccessToken]: userContext.accessToken };
} else { } else {
return { [HttpHeaders.authorization]: userContext.authorizationToken }; return { [HttpHeaders.authorization]: userContext.authorizationToken };
@@ -337,7 +337,7 @@ export function createMongoCollectionWithProxy(
export function getEndpoint(): string { export function getEndpoint(): string {
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer"; let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
if (window.authType === AuthType.EncryptedToken) { if (userContext.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo"); url = url.replace("api/mongo", "api/guest/mongo");
} }
return url; return url;

View File

@@ -27,13 +27,17 @@ describe("createCollection", () => {
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD; updateUserContext({
authType: AuthType.AAD,
});
await createCollection(createCollectionParams); await createCollection(createCollectionParams);
expect(armRequest).toHaveBeenCalled(); expect(armRequest).toHaveBeenCalled();
}); });
it("should call SDK if not logged in with non-AAD method", async () => { it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey; updateUserContext({
authType: AuthType.MasterKey,
});
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
databases: { databases: {
createIfNotExists: () => { createIfNotExists: () => {

View File

@@ -35,7 +35,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
); );
try { try {
let collection: DataModels.Collection; let collection: DataModels.Collection;
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.createNewDatabase) { if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = { const createDatabaseParams: DataModels.CreateDatabaseParams = {
autoPilotMaxThroughput: params.autoPilotMaxThroughput, autoPilotMaxThroughput: params.autoPilotMaxThroughput,

View File

@@ -34,7 +34,7 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Creating database resources is not allowed for tables accounts"); throw new Error("Creating database resources is not allowed for tables accounts");
} }
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
? createDatabaseWithARM(params) ? createDatabaseWithARM(params)
: createDatabaseWithSDK(params)); : createDatabaseWithSDK(params));

View File

@@ -22,7 +22,7 @@ export async function createStoredProcedure(
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -19,7 +19,7 @@ export async function createTrigger(
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -22,7 +22,7 @@ export async function createUserDefinedFunction(
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -20,13 +20,17 @@ describe("deleteCollection", () => {
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD; updateUserContext({
authType: AuthType.AAD,
});
await deleteCollection("database", "collection"); await deleteCollection("database", "collection");
expect(armRequest).toHaveBeenCalled(); expect(armRequest).toHaveBeenCalled();
}); });
it("should call SDK if not logged in with non-AAD method", async () => { it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey; updateUserContext({
authType: AuthType.MasterKey,
});
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
database: () => { database: () => {
return { return {

View File

@@ -13,7 +13,7 @@ import { client } from "../CosmosClient";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> { export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try { try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteCollectionWithARM(databaseId, collectionId); await deleteCollectionWithARM(databaseId, collectionId);
} else { } else {
await client().database(databaseId).container(collectionId).delete(); await client().database(databaseId).container(collectionId).delete();

View File

@@ -20,13 +20,17 @@ describe("deleteDatabase", () => {
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD; updateUserContext({
authType: AuthType.AAD,
});
await deleteDatabase("database"); await deleteDatabase("database");
expect(armRequest).toHaveBeenCalled(); expect(armRequest).toHaveBeenCalled();
}); });
it("should call SDK if not logged in with non-AAD method", async () => { it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey; updateUserContext({
authType: AuthType.MasterKey,
});
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
database: () => { database: () => {
return { return {

View File

@@ -16,7 +16,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Deleting database resources is not allowed for tables accounts"); throw new Error("Deleting database resources is not allowed for tables accounts");
} }
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteDatabaseWithARM(databaseId); await deleteDatabaseWithARM(databaseId);
} else { } else {
await client().database(databaseId).delete(); await client().database(databaseId).delete();

View File

@@ -14,7 +14,7 @@ export async function deleteStoredProcedure(
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`); const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -10,7 +10,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`); const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -10,7 +10,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`); const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -41,7 +41,7 @@ interface MetricsResponse {
} }
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => { export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
if (window.authType !== AuthType.AAD) { if (userContext.authType !== AuthType.AAD) {
return undefined; return undefined;
} }

View File

@@ -3,9 +3,10 @@ import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import * as Constants from "../Constants"; import * as Constants from "../Constants";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> { export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
if (window.authType !== AuthType.AAD) { if (userContext.authType !== AuthType.AAD) {
return undefined; return undefined;
} }
let indexTransformationPercentage: number; let indexTransformationPercentage: number;

View File

@@ -9,6 +9,7 @@ import { updateUserContext } from "../../UserContext";
describe("readCollection", () => { describe("readCollection", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
authType: AuthType.ResourceToken,
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
@@ -17,7 +18,6 @@ describe("readCollection", () => {
}); });
it("should call SDK if logged in with resource token", async () => { it("should call SDK if logged in with resource token", async () => {
window.authType = AuthType.ResourceToken;
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
database: () => { database: () => {
return { return {

View File

@@ -16,7 +16,7 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {

View File

@@ -19,13 +19,17 @@ describe("readCollections", () => {
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD; updateUserContext({
authType: AuthType.AAD,
});
await readCollections("database"); await readCollections("database");
expect(armRequest).toHaveBeenCalled(); expect(armRequest).toHaveBeenCalled();
}); });
it("should call SDK if not logged in with non-AAD method", async () => { it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey; updateUserContext({
authType: AuthType.MasterKey,
});
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
database: () => { database: () => {
return { return {

View File

@@ -15,7 +15,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table

View File

@@ -15,7 +15,7 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {

View File

@@ -19,13 +19,17 @@ describe("readDatabases", () => {
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD; updateUserContext({
authType: AuthType.AAD,
});
await readDatabases(); await readDatabases();
expect(armRequest).toHaveBeenCalled(); expect(armRequest).toHaveBeenCalled();
}); });
it("should call SDK if not logged in with non-AAD method", async () => { it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey; updateUserContext({
authType: AuthType.MasterKey,
});
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
databases: { databases: {
readAll: () => { readAll: () => {

View File

@@ -15,7 +15,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {

View File

@@ -9,7 +9,7 @@ export async function readMongoDBCollectionThroughRP(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<MongoDBCollectionResource> { ): Promise<MongoDBCollectionResource> {
if (window.authType !== AuthType.AAD) { if (userContext.authType !== AuthType.AAD) {
return undefined; return undefined;
} }
let collection: MongoDBCollectionResource; let collection: MongoDBCollectionResource;

View File

@@ -14,7 +14,7 @@ export async function readStoredProcedures(
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -14,7 +14,7 @@ export async function readTriggers(
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -14,7 +14,7 @@ export async function readUserDefinedFunctions(
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -41,7 +41,7 @@ export async function updateCollection(
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table

View File

@@ -58,7 +58,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`); const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try { try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) { if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params); updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { } else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {

View File

@@ -22,7 +22,7 @@ export async function updateStoredProcedure(
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -19,7 +19,7 @@ export async function updateTrigger(
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -22,7 +22,7 @@ export async function updateUserDefinedFunction(
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try { try {
if ( if (
window.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && !userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) { ) {

View File

@@ -26,7 +26,6 @@ export interface ConfigContext {
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
ENABLE_GALLERY_PUBLISH?: boolean;
} }
// Default configuration // Default configuration

View File

@@ -108,7 +108,7 @@ export interface CollectionBase extends TreeNode {
isCollectionExpanded: ko.Observable<boolean>; isCollectionExpanded: ko.Observable<boolean>;
onDocumentDBDocumentsClick(): void; onDocumentDBDocumentsClick(): void;
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
expandCollection(): void; expandCollection(): void;
collapseCollection(): void; collapseCollection(): void;
getDatabase(): Database; getDatabase(): Database;
@@ -140,11 +140,11 @@ export interface Collection extends CollectionBase {
onSettingsClick: () => Promise<void>; onSettingsClick: () => Promise<void>;
onNewGraphClick(): void; onNewGraphClick(): void;
onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string): void; onNewMongoQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
onNewMongoShellClick(): void; onNewMongoShellClick(): void;
onNewStoredProcedureClick(source: Collection, event: MouseEvent): void; onNewStoredProcedureClick(source: Collection, event?: MouseEvent): void;
onNewUserDefinedFunctionClick(source: Collection, event: MouseEvent): void; onNewUserDefinedFunctionClick(source: Collection, event?: MouseEvent): void;
onNewTriggerClick(source: Collection, event: MouseEvent): void; onNewTriggerClick(source: Collection, event?: MouseEvent): void;
storedProcedures: ko.Computed<StoredProcedure[]>; storedProcedures: ko.Computed<StoredProcedure[]>;
userDefinedFunctions: ko.Computed<UserDefinedFunction[]>; userDefinedFunctions: ko.Computed<UserDefinedFunction[]>;
triggers: ko.Computed<Trigger[]>; triggers: ko.Computed<Trigger[]>;
@@ -355,7 +355,7 @@ export enum CollectionTabKind {
Notebook = 13 /* Deprecated */, Notebook = 13 /* Deprecated */,
Terminal = 14, Terminal = 14,
NotebookV2 = 15, NotebookV2 = 15,
SparkMasterTab = 16, SparkMasterTab = 16 /* Deprecated */,
Gallery = 17, Gallery = 17,
NotebookViewer = 18, NotebookViewer = 18,
Schema = 19, Schema = 19,
@@ -371,20 +371,20 @@ export enum TerminalKind {
export interface DataExplorerInputsFrame { export interface DataExplorerInputsFrame {
databaseAccount: any; databaseAccount: any;
subscriptionId: string; subscriptionId?: string;
resourceGroup: string; resourceGroup?: string;
masterKey: string; masterKey?: string;
hasWriteAccess: boolean; hasWriteAccess?: boolean;
authorizationToken: string; authorizationToken?: string;
features: any; features: { [key: string]: string };
csmEndpoint: string; csmEndpoint?: string;
dnsSuffix: string; dnsSuffix?: string;
serverId: string; serverId?: string;
extensionEndpoint: string; extensionEndpoint?: string;
subscriptionType: SubscriptionType; subscriptionType?: SubscriptionType;
quotaId: string; quotaId?: string;
addCollectionDefaultFlight: string; addCollectionDefaultFlight?: string;
isTryCosmosDBSubscription: boolean; isTryCosmosDBSubscription?: boolean;
loadDatabaseAccountTimestamp?: number; loadDatabaseAccountTimestamp?: number;
sharedThroughputMinimum?: number; sharedThroughputMinimum?: number;
sharedThroughputMaximum?: number; sharedThroughputMaximum?: number;

View File

@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true); expect(ko.components.isRegistered("graph-style")).toBe(true);
}); });
it("should register collapsible-panel component", () => {
expect(ko.components.isRegistered("collapsible-panel")).toBe(true);
});
it("should register json-editor component", () => { it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true); expect(ko.components.isRegistered("json-editor")).toBe(true);
}); });
@@ -69,10 +65,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true); expect(ko.components.isRegistered("terminal-tab")).toBe(true);
}); });
it("should register spark-master-tab component", () => {
expect(ko.components.isRegistered("spark-master-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => { it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true); expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
}); });

View File

@@ -1,7 +1,6 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents"; import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
@@ -17,7 +16,6 @@ ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent); ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent()); ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent());
@@ -39,7 +37,6 @@ ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab()); ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab()); ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab()); ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab()); ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab()); ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
@@ -67,7 +64,6 @@ ko.components.register("table-query-select-pane", new PaneComponents.TableQueryS
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent()); ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent());
ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent()); ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent());
ko.components.register("renew-adhoc-access-pane", new PaneComponents.RenewAdHocAccessPane());
ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent()); ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent()); ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent()); ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());

View File

@@ -1,56 +0,0 @@
import * as ko from "knockout";
import template from "./collapsible-panel-component.html";
/**
* Helper class for ko component registration
*/
export class CollapsiblePanelComponent {
constructor() {
return {
viewModel: CollapsiblePanelViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
interface CollapsiblePanelParams {
collapsedTitle: ko.Observable<string>;
expandedTitle: ko.Observable<string>;
isCollapsed?: ko.Observable<boolean>;
collapseToLeft?: boolean;
}
/**
* Collapsible panel:
* Contains a header with [>] button to collapse and an title ("expandedTitle").
* Collapsing the panel:
* - shrinks width to narrow amount
* - hides children
* - shows [<]
* - shows vertical title ("collapsedTitle")
* - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter)
*
* How to use in your markup:
* <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }">
* <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) -->
* </collapsible-panel>
*
* Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component.
* Use the optional "collapseToLeft" parameter to collapse to the left.
*/
class CollapsiblePanelViewModel {
public params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) {
this.params = params;
this.isCollapsed = params.isCollapsed || ko.observable(false);
}
public toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed());
}
}

View File

@@ -1,44 +0,0 @@
<div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }">
<div class="panelHeader" data-bind="visible: !isCollapsed()">
<span
class="collapsedIconContainer collapseExpandButton"
data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }"
>
<img
class="collapsedIcon imgVerticalAlignment"
src="/imgarrowlefticon.svg"
alt="Collapse"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
/>
</span>
<span
class="expandedTitle"
data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }"
></span>
</div>
<div class="collapsibleNav nav" data-bind="visible:isCollapsed">
<ul class="nav">
<li class="collapsedBtn collapseExpandButton">
<span class="collapsedIconContainer" data-bind="click: toggleCollapse">
<img
class="collapsedIcon"
src="/imgarrowlefticon.svg"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
alt="Expand"
/>
</span>
<span class="rotatedInner" data-bind="click: toggleCollapse">
<span data-bind="text: params.collapsedTitle"></span>
</span>
</li>
</ul>
</div>
<div class="panelContent" data-bind="visible:!isCollapsed()">
<!-- ko with:$parent -->
<!-- ko template: { nodes: $componentTemplateNodes } -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog"; import { Dialog as FluentDialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
@@ -50,7 +50,7 @@ const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400; const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px"; const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export class DialogComponent extends React.Component<DialogProps, {}> { export class Dialog extends React.Component<DialogProps> {
constructor(props: DialogProps) { constructor(props: DialogProps) {
super(props); super(props);
} }
@@ -91,7 +91,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
: undefined; : undefined;
return ( return (
<Dialog {...dialogProps}> <FluentDialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />} {choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
@@ -104,7 +104,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
<PrimaryButton {...primaryButtonProps} /> <PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />} {secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter> </DialogFooter>
</Dialog> </FluentDialog>
); );
} }
} }

View File

@@ -1,16 +0,0 @@
/**
* This adapter is responsible to render the Dialog React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as React from "react";
import { DialogComponent, DialogProps } from "./DialogComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DialogComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<DialogProps>;
public renderComponent(): JSX.Element {
return <DialogComponent {...this.parameters()} />;
}
}

View File

@@ -47,13 +47,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" }, { key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" }, { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" }, { key: "feature.enablettl", label: "Enable TTL", value: "true" },
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
{ key: "feature.selfServeType", label: "Self serve feature", value: "sample" }, { key: "feature.selfServeType", label: "Self serve feature", value: "sample" },
{
key: "feature.enableLinkInjection",
label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true",
},
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" }, { key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{ {
key: "feature.enablefixedcollectionwithsharedthroughput", key: "feature.enablefixedcollectionwithsharedthroughput",

View File

@@ -149,12 +149,6 @@ exports[`Feature panel renders all flags 1`] = `
label="Enable TTL" label="Enable TTL"
onChange={[Function]} onChange={[Function]}
/> />
<StyledCheckboxBase
checked={false}
key="feature.enablegallerypublish"
label="Enable Notebook Gallery Publishing"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.selfServeType" key="feature.selfServeType"
@@ -163,8 +157,8 @@ exports[`Feature panel renders all flags 1`] = `
/> />
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enableLinkInjection" key="feature.canexceedmaximumvalue"
label="Enable Injecting Notebook Viewer Link into the first cell" label="Can exceed max value"
onChange={[Function]} onChange={[Function]}
/> />
</Stack> </Stack>
@@ -172,12 +166,6 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow" className="checkboxRow"
horizontalAlign="space-between" horizontalAlign="space-between"
> >
<StyledCheckboxBase
checked={false}
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.enablefixedcollectionwithsharedthroughput" key="feature.enablefixedcollectionwithsharedthroughput"

View File

@@ -74,8 +74,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
private onAddRepoButtonClick = async (): Promise<void> => { private onAddRepoButtonClick = async (): Promise<void> => {
const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, { const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}); });
let enteredUrl = this.state.textFieldValue; let enteredUrl = this.state.textFieldValue;
@@ -105,8 +103,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.NotebooksGitHubManualRepoAdd, Action.NotebooksGitHubManualRepoAdd,
{ {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}, },
startKey startKey
@@ -121,8 +117,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.NotebooksGitHubManualRepoAdd, Action.NotebooksGitHubManualRepoAdd,
{ {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
error: AddRepoComponent.TextFieldErrorMessage, error: AddRepoComponent.TextFieldErrorMessage,
}, },

View File

@@ -13,6 +13,8 @@ import {
LinkBase, LinkBase,
Separator, Separator,
TooltipHost, TooltipHost,
Spinner,
SpinnerSize,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
@@ -29,10 +31,14 @@ export interface GalleryCardComponentProps {
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
onDownloadClick: () => void; onDownloadClick: () => void;
onDeleteClick: () => void; onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
} }
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { interface GalleryCardComponentState {
isDeletingPublishedNotebook: boolean;
}
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> {
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio = public static readonly cardHeightToWidthRatio =
@@ -40,6 +46,14 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private static readonly cardDescriptionMaxChars = 80; private static readonly cardDescriptionMaxChars = 80;
private static readonly cardItemGapBig = 10; private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8; private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
constructor(props: GalleryCardComponentProps) {
super(props);
this.state = {
isDeletingPublishedNotebook: false,
};
}
public render(): JSX.Element { public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete; const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
@@ -59,6 +73,17 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }} tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)} onClick={(event) => this.onClick(event, this.props.onClick)}
> >
{this.state.isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!this.state.isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}> <Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo} imageUrl={this.props.data.isSample && CosmosDBLogo}
@@ -109,7 +134,8 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
</Text> </Text>
<span> <span>
{this.props.data.views !== undefined && this.generateIconText("RedEye", this.props.data.views.toString())} {this.props.data.views !== undefined &&
this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined && {this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())} this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined && {this.props.data.favorites !== undefined &&
@@ -141,10 +167,17 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)} this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete && {this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)} this.generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
this.props.onDeleteClick(
() => this.setState({ isDeletingPublishedNotebook: true }),
() => this.setState({ isDeletingPublishedNotebook: false })
)
)}
</span> </span>
</Card.Section> </Card.Section>
)} )}
</>
)}
</Card> </Card>
); );
} }

View File

@@ -44,7 +44,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`); throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
} }
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, startKey); traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
this.props.onAcceptCodeOfConduct(response.data); this.props.onAcceptCodeOfConduct(response.data);
} catch (error) { } catch (error) {

View File

@@ -7,7 +7,6 @@ import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps { export interface GalleryAndNotebookViewerComponentProps {
container?: Explorer; container?: Explorer;
isGalleryPublishEnabled: boolean;
junoClient: JunoClient; junoClient: JunoClient;
notebookUrl?: string; notebookUrl?: string;
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;
@@ -61,7 +60,6 @@ export class GalleryAndNotebookViewerComponent extends React.Component<
const props: GalleryViewerComponentProps = { const props: GalleryViewerComponentProps = {
container: this.props.container, container: this.props.container,
isGalleryPublishEnabled: this.props.isGalleryPublishEnabled,
junoClient: this.props.junoClient, junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab, selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy, sortBy: this.state.sortBy,

View File

@@ -5,7 +5,6 @@ import { GalleryViewerComponent, GalleryViewerComponentProps, GalleryTab, SortBy
describe("GalleryViewerComponent", () => { describe("GalleryViewerComponent", () => {
it("renders", () => { it("renders", () => {
const props: GalleryViewerComponentProps = { const props: GalleryViewerComponentProps = {
isGalleryPublishEnabled: false,
junoClient: undefined, junoClient: undefined,
selectedTab: GalleryTab.OfficialSamples, selectedTab: GalleryTab.OfficialSamples,
sortBy: SortBy.MostViewed, sortBy: SortBy.MostViewed,

View File

@@ -23,7 +23,7 @@ import {
import * as React from "react"; import * as React from "react";
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient"; import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent"; import { Dialog, DialogProps } from "../Dialog";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent"; import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import "./GalleryViewerComponent.less"; import "./GalleryViewerComponent.less";
import { HttpStatusCodes } from "../../../Common/Constants"; import { HttpStatusCodes } from "../../../Common/Constants";
@@ -36,7 +36,6 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
export interface GalleryViewerComponentProps { export interface GalleryViewerComponentProps {
container?: Explorer; container?: Explorer;
isGalleryPublishEnabled: boolean;
junoClient: JunoClient; junoClient: JunoClient;
selectedTab: GalleryTab; selectedTab: GalleryTab;
sortBy: SortBy; sortBy: SortBy;
@@ -140,25 +139,20 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
text: GalleryViewerComponent.mostRecentText, text: GalleryViewerComponent.mostRecentText,
}, },
]; ];
if (this.props.container?.isGalleryPublishEnabled()) {
this.sortingOptions.push({ this.sortingOptions.push({
key: SortBy.MostFavorited, key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText, text: GalleryViewerComponent.mostFavoritedText,
}); });
}
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false); this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
if (this.props.container?.isGalleryPublishEnabled()) {
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
} }
}
public render(): JSX.Element { public render(): JSX.Element {
this.traceViewGallery(); this.traceViewGallery();
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)]; const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
if (this.props.isGalleryPublishEnabled) {
tabs.push( tabs.push(
this.createPublicGalleryTab( this.createPublicGalleryTab(
GalleryTab.PublicGallery, GalleryTab.PublicGallery,
@@ -166,9 +160,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
this.state.isCodeOfConductAccepted this.state.isCodeOfConductAccepted
) )
); );
}
if (this.props.container?.isGalleryPublishEnabled()) { if (this.props.container) {
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks)); tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks)); tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
} }
@@ -196,7 +189,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<div className="galleryContainer"> <div className="galleryContainer">
<Pivot {...pivotProps}>{pivotItems}</Pivot> <Pivot {...pivotProps}>{pivotItems}</Pivot>
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />} {this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</div> </div>
); );
} }
@@ -406,11 +399,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Stack.Item styles={{ root: { minWidth: 200 } }}> <Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} /> <Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item> </Stack.Item>
{this.props.isGalleryPublishEnabled && (
<Stack.Item> <Stack.Item>
<InfoComponent /> <InfoComponent />
</Stack.Item> </Stack.Item>
)}
</Stack> </Stack>
<Stack.Item>{content}</Stack.Item> <Stack.Item>{content}</Stack.Item>
</Stack> </Stack>
@@ -664,10 +655,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
private onRenderCell = (data?: IGalleryItem): JSX.Element => { private onRenderCell = (data?: IGalleryItem): JSX.Element => {
let isFavorite: boolean; const isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
if (this.props.container?.isGalleryPublishEnabled()) {
isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
}
const props: GalleryCardComponentProps = { const props: GalleryCardComponentProps = {
data, data,
isFavorite, isFavorite,
@@ -678,7 +666,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
onFavoriteClick: () => this.favoriteItem(data), onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data), onUnfavoriteClick: () => this.unfavoriteItem(data),
onDownloadClick: () => this.downloadItem(data), onDownloadClick: () => this.downloadItem(data),
onDeleteClick: () => this.deleteItem(data), onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) =>
this.deleteItem(data, beforeDelete, afterDelete),
}; };
return ( return (
@@ -722,11 +711,18 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
}; };
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem, beforeDelete: () => void, afterDelete: () => void): Promise<void> => {
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => { GalleryUtils.deleteItem(
this.props.container,
this.props.junoClient,
data,
(item) => {
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id); this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
this.refreshSelectedTab(item); this.refreshSelectedTab(item);
}); },
beforeDelete,
afterDelete
);
}; };
private onPivotChange = (item: PivotItem): void => { private onPivotChange = (item: PivotItem): void => {

View File

@@ -72,11 +72,18 @@ exports[`GalleryViewerComponent renders 1`] = `
"key": 3, "key": 3,
"text": "Most recent", "text": "Most recent",
}, },
Object {
"key": 2,
"text": "Most favorited",
},
] ]
} }
selectedKey={0} selectedKey={0}
/> />
</StackItem> </StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack> </Stack>
<StackItem> <StackItem>
<StyledSpinnerBase <StyledSpinnerBase
@@ -85,6 +92,94 @@ exports[`GalleryViewerComponent renders 1`] = `
</StackItem> </StackItem>
</Stack> </Stack>
</PivotItem> </PivotItem>
<PivotItem
headerText="Public gallery"
itemKey="PublicGallery"
key="PublicGallery"
style={
Object {
"marginTop": 20,
}
}
>
<div
className="publicGalleryTabContainer"
>
<Stack
tokens={
Object {
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
Object {
"childrenGap": 20,
"padding": 10,
}
}
>
<StackItem
grow={true}
>
<StyledSearchBoxBase
onChange={[Function]}
placeholder="Search"
/>
</StackItem>
<StackItem>
<StyledLabelBase>
Sort by
</StyledLabelBase>
</StackItem>
<StackItem
styles={
Object {
"root": Object {
"minWidth": 200,
},
}
}
>
<StyledWithResponsiveMode
onChange={[Function]}
options={
Array [
Object {
"key": 0,
"text": "Most viewed",
},
Object {
"key": 1,
"text": "Most downloaded",
},
Object {
"key": 3,
"text": "Most recent",
},
Object {
"key": 2,
"text": "Most favorited",
},
]
}
selectedKey={0}
/>
</StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack>
<StackItem>
<StyledSpinnerBase
size={3}
/>
</StackItem>
</Stack>
</div>
</PivotItem>
</StyledPivotBase> </StyledPivotBase>
</div> </div>
`; `;

View File

@@ -11,11 +11,10 @@ import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent"; import { Dialog, DialogProps, TextFieldProps } from "../Dialog";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { NotebookV4 } from "@nteract/commutable/lib/v4";
import { SessionStorageUtility } from "../../../Shared/StorageUtility"; import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { DialogHost } from "../../../Utils/GalleryUtils"; import { DialogHost } from "../../../Utils/GalleryUtils";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
@@ -103,7 +102,7 @@ export class NotebookViewerComponent
); );
const notebook: Notebook = await response.json(); const notebook: Notebook = await response.json();
this.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId); GalleryUtils.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
this.notebookComponentBootstrapper.setContent("json", notebook); this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook, showProgressBar: false }); this.setState({ content: notebook, showProgressBar: false });
@@ -133,17 +132,6 @@ export class NotebookViewerComponent
} }
} }
private removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
if (!newCellId) {
return;
}
const notebookV4 = notebook as NotebookV4;
if (notebookV4 && notebookV4.cells[0].source[0].search(newCellId)) {
delete notebookV4.cells[0];
notebook = notebookV4;
}
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="notebookViewerContainer"> <div className="notebookViewerContainer">
@@ -179,7 +167,7 @@ export class NotebookViewerComponent
hidePrompts: this.props.hidePrompts, hidePrompts: this.props.hidePrompts,
})} })}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />} {this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</div> </div>
); );
} }

View File

@@ -221,8 +221,6 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
if (window.confirm("Are you sure you want to delete this query?")) { if (window.confirm("Are you sure you want to delete this query?")) {
const container = window.dataExplorer; const container = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(), paneTitle: container && container.browseQueriesPane.title(),
}); });
@@ -231,8 +229,6 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteSavedQuery, Action.DeleteSavedQuery,
{ {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(), paneTitle: container && container.browseQueriesPane.title(),
}, },
@@ -242,8 +238,6 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteSavedQuery, Action.DeleteSavedQuery,
{ {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(), paneTitle: container && container.browseQueriesPane.title(),
error: getErrorMessage(error), error: getErrorMessage(error),

View File

@@ -44,7 +44,6 @@ import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/genera
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection"; import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress"; import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { isEmpty } from "underscore";
interface SettingsV2TabInfo { interface SettingsV2TabInfo {
tab: SettingsV2TabTypes; tab: SettingsV2TabTypes;
@@ -317,8 +316,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.props.settingsTab.isExecuting(true); this.props.settingsTab.isExecuting(true);
const startKey: number = traceStart(Action.SettingsV2Updated, { const startKey: number = traceStart(Action.SettingsV2Updated, {
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
}); });
@@ -334,10 +331,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceFailure( traceFailure(
Action.SettingsV2Updated, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId, databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(), collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
error: getErrorMessage(error), error: getErrorMessage(error),
@@ -410,10 +406,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceSuccess( traceSuccess(
Action.Tab, Action.Tab,
{ {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.collection.databaseId, databaseName: this.collection.databaseId,
collectionName: this.collection.id(), collectionName: this.collection.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
}, },
@@ -710,9 +705,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceSuccess( traceSuccess(
Action.SettingsV2Updated, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.database.id(), databaseName: this.database.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
}, },
@@ -811,10 +805,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceSuccess( traceSuccess(
Action.MongoIndexUpdated, Action.MongoIndexUpdated,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId, databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(), collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
}, },
@@ -824,10 +817,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceFailure( traceFailure(
Action.MongoIndexUpdated, Action.MongoIndexUpdated,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId, databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(), collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
error: getErrorMessage(error), error: getErrorMessage(error),
@@ -876,10 +868,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
traceSuccess( traceSuccess(
Action.SettingsV2Updated, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId, databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(), collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
}, },
@@ -1004,16 +994,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />, content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
}); });
} else if (this.container.isPreferredApiMongoDB()) { } else if (this.container.isPreferredApiMongoDB()) {
if (isEmpty(this.container.features())) { if (this.container.isEnableMongoCapabilityPresent()) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexingPolicyAADError,
});
} else if (this.container.isEnableMongoCapabilityPresent()) {
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab, tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />, content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />,
}); });
} else {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexingPolicyAADError,
});
} }
} }

View File

@@ -458,11 +458,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
changedSelectedValueTo: changedSelectedValueTo:
option.key === "true" ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff, option.key === "true" ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
subscriptionId: userContext.subscriptionId,
databaseAccountName: this.props.databaseAccount?.name,
databaseName: this.props.databaseName, databaseName: this.props.databaseName,
collectionName: this.props.collectionName, collectionName: this.props.collectionName,
apiKind: userContext.defaultExperience,
dataExplorerArea: "Scale Tab V2", dataExplorerArea: "Scale Tab V2",
}); });
}; };

View File

@@ -28,16 +28,11 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function], "_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function], "_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -440,22 +435,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane { UploadItemsPane {
"container": [Circular], "container": [Circular],
"fileUploadSummaryText": [Function], "fileUploadSummaryText": [Function],
@@ -695,9 +674,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"addDatabaseText": [Function], "addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane { "addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -804,6 +780,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"clickHostedAccountSwitch": [Function], "clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function], "clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined, "closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36, "collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object { "collectionCreationDefaults": Object {
@@ -859,9 +836,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane { "editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -951,11 +925,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function], "isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
@@ -965,8 +937,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function], "isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function], "isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function], "isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -992,13 +962,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"memoryUsageInfo": [Function], "memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane { "newVertexPane": NewVertexPane {
"buildString": [Function], "buildString": [Function],
"container": [Circular], "container": [Circular],
@@ -1021,7 +984,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "openDialog": undefined,
"openSidePanel": undefined, "openSidePanel": undefined,
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
@@ -1051,24 +1014,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function], "resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function], "resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function], "resourceTokenDatabaseId": [Function],
@@ -1164,22 +1109,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function], "signInAad": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter { "splitter": Splitter {
"bounds": Object { "bounds": Object {
"max": 400, "max": 400,
@@ -1237,9 +1168,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function], "openedTabs": [Function],
}, },
"toggleLeftPaneExpandedKeyPress": [Function], "toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane { "uploadFilePane": UploadFilePane {
"container": [Circular], "container": [Circular],
"extensions": [Function], "extensions": [Function],
@@ -1309,16 +1237,11 @@ exports[`SettingsComponent renders 1`] = `
} }
container={ container={
Explorer { Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function], "_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function], "_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -1721,22 +1644,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane { UploadItemsPane {
"container": [Circular], "container": [Circular],
"fileUploadSummaryText": [Function], "fileUploadSummaryText": [Function],
@@ -1976,9 +1883,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"addDatabaseText": [Function], "addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane { "addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -2085,6 +1989,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"clickHostedAccountSwitch": [Function], "clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function], "clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined, "closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36, "collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object { "collectionCreationDefaults": Object {
@@ -2140,9 +2045,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane { "editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -2232,11 +2134,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function], "isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
@@ -2246,8 +2146,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function], "isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function], "isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function], "isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -2273,13 +2171,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"memoryUsageInfo": [Function], "memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane { "newVertexPane": NewVertexPane {
"buildString": [Function], "buildString": [Function],
"container": [Circular], "container": [Circular],
@@ -2302,7 +2193,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "openDialog": undefined,
"openSidePanel": undefined, "openSidePanel": undefined,
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
@@ -2332,24 +2223,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function], "resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function], "resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function], "resourceTokenDatabaseId": [Function],
@@ -2445,22 +2318,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function], "signInAad": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter { "splitter": Splitter {
"bounds": Object { "bounds": Object {
"max": 400, "max": 400,
@@ -2518,9 +2377,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function], "openedTabs": [Function],
}, },
"toggleLeftPaneExpandedKeyPress": [Function], "toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane { "uploadFilePane": UploadFilePane {
"container": [Circular], "container": [Circular],
"extensions": [Function], "extensions": [Function],
@@ -2603,16 +2459,11 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function], "_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function], "_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -3015,22 +2866,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane { UploadItemsPane {
"container": [Circular], "container": [Circular],
"fileUploadSummaryText": [Function], "fileUploadSummaryText": [Function],
@@ -3270,9 +3105,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"addDatabaseText": [Function], "addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane { "addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -3379,6 +3211,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"clickHostedAccountSwitch": [Function], "clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function], "clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined, "closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36, "collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object { "collectionCreationDefaults": Object {
@@ -3434,9 +3267,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane { "editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -3526,11 +3356,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function], "isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
@@ -3540,8 +3368,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function], "isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function], "isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function], "isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -3567,13 +3393,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"memoryUsageInfo": [Function], "memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane { "newVertexPane": NewVertexPane {
"buildString": [Function], "buildString": [Function],
"container": [Circular], "container": [Circular],
@@ -3596,7 +3415,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "openDialog": undefined,
"openSidePanel": undefined, "openSidePanel": undefined,
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
@@ -3626,24 +3445,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function], "resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function], "resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function], "resourceTokenDatabaseId": [Function],
@@ -3739,22 +3540,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function], "signInAad": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter { "splitter": Splitter {
"bounds": Object { "bounds": Object {
"max": 400, "max": 400,
@@ -3812,9 +3599,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function], "openedTabs": [Function],
}, },
"toggleLeftPaneExpandedKeyPress": [Function], "toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane { "uploadFilePane": UploadFilePane {
"container": [Circular], "container": [Circular],
"extensions": [Function], "extensions": [Function],
@@ -3884,16 +3668,11 @@ exports[`SettingsComponent renders 1`] = `
} }
container={ container={
Explorer { Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function], "_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function], "_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function], "_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function], "_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
@@ -4296,22 +4075,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane { UploadItemsPane {
"container": [Circular], "container": [Circular],
"fileUploadSummaryText": [Function], "fileUploadSummaryText": [Function],
@@ -4551,9 +4314,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"addDatabaseText": [Function], "addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane { "addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -4660,6 +4420,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"clickHostedAccountSwitch": [Function], "clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function], "clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined, "closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36, "collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object { "collectionCreationDefaults": Object {
@@ -4715,9 +4476,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"deleteDatabaseText": [Function], "deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane { "editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property", "addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name", "attributeNameLabel": "Property Name",
@@ -4807,11 +4565,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function], "isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function], "isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
@@ -4821,8 +4577,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function], "isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function], "isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function], "isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function], "isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function], "isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function], "isRightPanelV2Enabled": [Function],
@@ -4848,13 +4602,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
"memoryUsageInfo": [Function], "memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane { "newVertexPane": NewVertexPane {
"buildString": [Function], "buildString": [Function],
"container": [Circular], "container": [Circular],
@@ -4877,7 +4624,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function], "openDialog": undefined,
"openSidePanel": undefined, "openSidePanel": undefined,
"provideFeedbackEmail": [Function], "provideFeedbackEmail": [Function],
"queriesClient": QueriesClient { "queriesClient": QueriesClient {
@@ -4907,24 +4654,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function], "refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function], "refreshNotebookList": [Function],
"refreshTreeTitle": [Function], "refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function], "resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function], "resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function], "resourceTokenDatabaseId": [Function],
@@ -5020,22 +4749,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function], "signInAad": [Function],
"sparkClusterConnectionInfo": [Function], "sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter { "splitter": Splitter {
"bounds": Object { "bounds": Object {
"max": 400, "max": 400,
@@ -5093,9 +4808,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function], "openedTabs": [Function],
}, },
"toggleLeftPaneExpandedKeyPress": [Function], "toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane { "uploadFilePane": UploadFilePane {
"container": [Circular], "container": [Circular],
"extensions": [Function], "extensions": [Function],

View File

@@ -207,9 +207,6 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
this.isAutoPilotSelected.subscribe((value) => { this.isAutoPilotSelected.subscribe((value) => {
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff, changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
databaseAccountName: userContext.databaseAccount?.name,
subscriptionId: userContext.subscriptionId,
apiKind: userContext.defaultExperience,
dataExplorerArea: "Scale Tab V1", dataExplorerArea: "Scale Tab V1",
}); });
}); });

View File

@@ -61,6 +61,7 @@ describe("ContainerSampleGenerator", () => {
const database = { const database = {
id: ko.observable(sampleDatabaseId), id: ko.observable(sampleDatabaseId),
collections: ko.observableArray<ViewModels.Collection>([collection]), collections: ko.observableArray<ViewModels.Collection>([collection]),
loadCollections: () => {},
} as ViewModels.Database; } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
@@ -109,6 +110,7 @@ describe("ContainerSampleGenerator", () => {
const database = { const database = {
id: ko.observable(sampleDatabaseId), id: ko.observable(sampleDatabaseId),
collections: ko.observableArray<ViewModels.Collection>([collection]), collections: ko.observableArray<ViewModels.Collection>([collection]),
loadCollections: () => {},
} as ViewModels.Database; } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
collection.databaseId = database.id(); collection.databaseId = database.id();

View File

@@ -63,6 +63,7 @@ export class ContainerSampleGenerator {
if (!database) { if (!database) {
return undefined; return undefined;
} }
await database.loadCollections();
return database.findCollectionWithId(this.sampleDataFile.collectionId); return database.findCollectionWithId(this.sampleDataFile.collectionId);
} }

View File

@@ -3,7 +3,6 @@ import * as ComponentRegisterer from "./ComponentRegisterer";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ko from "knockout"; import * as ko from "knockout";
import * as MostRecentActivity from "./MostRecentActivity/MostRecentActivity";
import * as path from "path"; import * as path from "path";
import * as SharedConstants from "../Shared/Constants"; import * as SharedConstants from "../Shared/Constants";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
@@ -21,7 +20,6 @@ import { readDatabases } from "../Common/dataAccess/readDatabases";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility"; import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import GraphStylingPane from "./Panes/GraphStylingPane"; import GraphStylingPane from "./Panes/GraphStylingPane";
import hasher from "hasher";
import NewVertexPane from "./Panes/NewVertexPane"; import NewVertexPane from "./Panes/NewVertexPane";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
import Q from "q"; import Q from "q";
@@ -29,7 +27,7 @@ import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import TerminalTab from "./Tabs/TerminalTab"; import TerminalTab from "./Tabs/TerminalTab";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import { ActionContracts, MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager"; import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker"; import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
@@ -41,24 +39,21 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { DialogComponentAdapter } from "./Controls/DialogReactComponent/DialogComponentAdapter"; import { DialogProps, TextFieldProps } from "./Controls/Dialog";
import { DialogProps, TextFieldProps } from "./Controls/DialogReactComponent/DialogComponent";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane";
import { ExplorerMetrics } from "../Common/Constants"; import { ExplorerMetrics } from "../Common/Constants";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { FileSystemUtil } from "./Notebook/FileSystemUtil"; import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { handleOpenAction } from "./OpenActions";
import { IGalleryItem } from "../Juno/JunoClient"; import { IGalleryItem } from "../Juno/JunoClient";
import { LoadQueryPane } from "./Panes/LoadQueryPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { sendMessage, sendCachedDataMessage, handleCachedDataMessage } from "../Common/MessageHandler"; import { sendMessage, sendCachedDataMessage } from "../Common/MessageHandler";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil"; import { NotebookUtil } from "./Notebook/NotebookUtil";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager"; import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane"; import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory"; import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter"; import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken"; import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
@@ -66,7 +61,7 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane"; import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane"; import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter"; import { SplashScreen } from "./SplashScreen/SplashScreen";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane";
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane"; import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
@@ -98,22 +93,14 @@ BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
var tmp = ComponentRegisterer; var tmp = ComponentRegisterer;
enum ShareAccessToggleState {
ReadWrite,
Read,
}
interface AdHocAccessData {
readWriteUrl: string;
readUrl: string;
}
export interface ExplorerParams { export interface ExplorerParams {
setIsNotificationConsoleExpanded: (isExpanded: boolean) => void; setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
setNotificationConsoleData: (consoleData: ConsoleData) => void; setNotificationConsoleData: (consoleData: ConsoleData) => void;
setInProgressConsoleDataIdToBeDeleted: (id: string) => void; setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
openSidePanel: (headerText: string, panelContent: JSX.Element) => void; openSidePanel: (headerText: string, panelContent: JSX.Element) => void;
closeSidePanel: () => void; closeSidePanel: () => void;
closeDialog: () => void;
openDialog: (props: DialogProps) => void;
} }
export default class Explorer { export default class Explorer {
@@ -152,7 +139,6 @@ export default class Explorer {
public queriesClient: QueriesClient; public queriesClient: QueriesClient;
public tableDataClient: TableDataClient; public tableDataClient: TableDataClient;
public splitter: Splitter; public splitter: Splitter;
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
// Notification Console // Notification Console
private setIsNotificationConsoleExpanded: (isExpanded: boolean) => void; private setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
@@ -161,8 +147,8 @@ export default class Explorer {
// Panes // Panes
public contextPanes: ContextualPaneBase[]; public contextPanes: ContextualPaneBase[];
private openSidePanel: (headerText: string, panelContent: JSX.Element) => void; public openSidePanel: (headerText: string, panelContent: JSX.Element) => void;
private closeSidePanel: () => void; public closeSidePanel: () => void;
// Resource Tree // Resource Tree
public databases: ko.ObservableArray<ViewModels.Database>; public databases: ko.ObservableArray<ViewModels.Database>;
@@ -204,7 +190,6 @@ export default class Explorer {
public cassandraAddCollectionPane: CassandraAddCollectionPane; public cassandraAddCollectionPane: CassandraAddCollectionPane;
public settingsPane: SettingsPane; public settingsPane: SettingsPane;
public executeSprocParamsPane: ExecuteSprocParamsPane; public executeSprocParamsPane: ExecuteSprocParamsPane;
public renewAdHocAccessPane: RenewAdHocAccessPane;
public uploadItemsPane: UploadItemsPane; public uploadItemsPane: UploadItemsPane;
public uploadItemsPaneAdapter: UploadItemsPaneAdapter; public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
public loadQueryPane: LoadQueryPane; public loadQueryPane: LoadQueryPane;
@@ -218,8 +203,6 @@ export default class Explorer {
public copyNotebookPaneAdapter: ReactAdapter; public copyNotebookPaneAdapter: ReactAdapter;
// features // features
public isGalleryPublishEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isCopyNotebookPaneEnabled: ko.Observable<boolean>; public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
@@ -229,17 +212,6 @@ export default class Explorer {
public canExceedMaximumValue: ko.Computed<boolean>; public canExceedMaximumValue: ko.Computed<boolean>;
public isAutoscaleDefaultEnabled: ko.Observable<boolean>; public isAutoscaleDefaultEnabled: ko.Observable<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<AdHocAccessData>;
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
public renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>;
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
public shareAccessUrl: ko.Observable<string>;
public shareUrlCopyHelperText: ko.Observable<string>;
public shareTokenCopyHelperText: ko.Observable<string>;
public shouldShowDataAccessExpiryDialog: ko.Observable<boolean>;
public shouldShowContextSwitchPrompt: ko.Observable<boolean>;
public isSchemaEnabled: ko.Computed<boolean>; public isSchemaEnabled: ko.Computed<boolean>;
// Notebooks // Notebooks
@@ -256,12 +228,12 @@ export default class Explorer {
public isSynapseLinkUpdating: ko.Observable<boolean>; public isSynapseLinkUpdating: ko.Observable<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded public notebookManager?: any; // This is dynamically loaded
public openDialog: ExplorerParams["openDialog"];
public closeDialog: ExplorerParams["closeDialog"];
private _panes: ContextualPaneBase[] = []; private _panes: ContextualPaneBase[] = [];
private _importExplorerConfigComplete: boolean = false;
private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = (database) => false; private _isSystemDatabasePredicate: (database: ViewModels.Database) => boolean = (database) => false;
private _isInitializingNotebooks: boolean; private _isInitializingNotebooks: boolean;
private _isInitializingSparkConnectionInfo: boolean;
private notebookBasePath: ko.Observable<string>; private notebookBasePath: ko.Observable<string>;
private _arcadiaManager: ArcadiaResourceManager; private _arcadiaManager: ArcadiaResourceManager;
private notebookToImport: { private notebookToImport: {
@@ -271,11 +243,6 @@ export default class Explorer {
// React adapters // React adapters
private commandBarComponentAdapter: CommandBarComponentAdapter; private commandBarComponentAdapter: CommandBarComponentAdapter;
private splashScreenAdapter: SplashScreenComponentAdapter;
private dialogComponentAdapter: DialogComponentAdapter;
private _dialogProps: ko.Observable<DialogProps>;
private addSynapseLinkDialog: DialogComponentAdapter;
private _addSynapseLinkDialogProps: ko.Observable<DialogProps>;
private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter; private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter;
private static readonly MaxNbDatabasesToAutoExpand = 5; private static readonly MaxNbDatabasesToAutoExpand = 5;
@@ -286,6 +253,8 @@ export default class Explorer {
this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted; this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted;
this.openSidePanel = params?.openSidePanel; this.openSidePanel = params?.openSidePanel;
this.closeSidePanel = params?.closeSidePanel; this.closeSidePanel = params?.closeSidePanel;
this.closeDialog = params?.closeDialog;
this.openDialog = params?.openDialog;
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, { const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
@@ -320,7 +289,6 @@ export default class Explorer {
this.isAccountReady = ko.observable<boolean>(false); this.isAccountReady = ko.observable<boolean>(false);
this.selfServeType = ko.observable<SelfServeType>(undefined); this.selfServeType = ko.observable<SelfServeType>(undefined);
this._isInitializingNotebooks = false; this._isInitializingNotebooks = false;
this._isInitializingSparkConnectionInfo = false;
this.arcadiaToken = ko.observable<string>(); this.arcadiaToken = ko.observable<string>();
this.arcadiaToken.subscribe((token: string) => { this.arcadiaToken.subscribe((token: string) => {
if (token) { if (token) {
@@ -357,8 +325,6 @@ export default class Explorer {
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
isNotebookEnabled: this.isNotebookEnabled(), isNotebookEnabled: this.isNotebookEnabled(),
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}); });
@@ -410,33 +376,6 @@ export default class Explorer {
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>(); this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
this.resourceTokenPartitionKey = ko.observable<string>(); this.resourceTokenPartitionKey = ko.observable<string>();
this.isAuthWithResourceToken = ko.observable<boolean>(false); this.isAuthWithResourceToken = ko.observable<boolean>(false);
this.shareAccessData = ko.observable<AdHocAccessData>({
readWriteUrl: undefined,
readUrl: undefined,
});
this.tokenForRenewal = ko.observable<string>("");
this.renewTokenError = ko.observable<string>("");
this.shareAccessUrl = ko.observable<string>();
this.shareUrlCopyHelperText = ko.observable<string>("Click to copy");
this.shareTokenCopyHelperText = ko.observable<string>("Click to copy");
this.shareAccessToggleState = ko.observable<ShareAccessToggleState>(ShareAccessToggleState.ReadWrite);
this.shareAccessToggleState.subscribe((toggleState: ShareAccessToggleState) => {
if (toggleState === ShareAccessToggleState.ReadWrite) {
this.shareAccessUrl(this.shareAccessData && this.shareAccessData().readWriteUrl);
} else {
this.shareAccessUrl(this.shareAccessData && this.shareAccessData().readUrl);
}
});
this.shouldShowShareDialogContents = ko.observable<boolean>(false);
this.shouldShowDataAccessExpiryDialog = ko.observable<boolean>(false);
this.shouldShowContextSwitchPrompt = ko.observable<boolean>(false);
this.isGalleryPublishEnabled = ko.computed<boolean>(
() => configContext.ENABLE_GALLERY_PUBLISH || this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
);
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
);
this.isGitHubPaneEnabled = ko.observable<boolean>(false); this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false); this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false); this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -715,13 +654,6 @@ export default class Explorer {
container: this, container: this,
}); });
this.renewAdHocAccessPane = new RenewAdHocAccessPane({
id: "renewadhocaccesspane",
visible: ko.observable<boolean>(false),
container: this,
});
this.uploadItemsPane = new UploadItemsPane({ this.uploadItemsPane = new UploadItemsPane({
id: "uploaditemspane", id: "uploaditemspane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
@@ -790,7 +722,6 @@ export default class Explorer {
this.cassandraAddCollectionPane, this.cassandraAddCollectionPane,
this.settingsPane, this.settingsPane,
this.executeSprocParamsPane, this.executeSprocParamsPane,
this.renewAdHocAccessPane,
this.uploadItemsPane, this.uploadItemsPane,
this.loadQueryPane, this.loadQueryPane,
this.saveQueryPane, this.saveQueryPane,
@@ -925,7 +856,6 @@ export default class Explorer {
this.notebookManager = new notebookManagerModule.default(); this.notebookManager = new notebookManagerModule.default();
this.notebookManager.initialize({ this.notebookManager.initialize({
container: this, container: this,
dialogProps: this._dialogProps,
notebookBasePath: this.notebookBasePath, notebookBasePath: this.notebookBasePath,
resourceTree: this.resourceTree, resourceTree: this.resourceTree,
refreshCommandBarButtons: () => this.refreshCommandBarButtons(), refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
@@ -992,34 +922,6 @@ export default class Explorer {
featureSubcription.dispose(); featureSubcription.dispose();
}); });
this._dialogProps = ko.observable<DialogProps>({
isModal: false,
visible: false,
title: undefined,
subText: undefined,
primaryButtonText: undefined,
secondaryButtonText: undefined,
onPrimaryButtonClick: undefined,
onSecondaryButtonClick: undefined,
});
this.dialogComponentAdapter = new DialogComponentAdapter();
this.dialogComponentAdapter.parameters = this._dialogProps;
this.splashScreenAdapter = new SplashScreenComponentAdapter(this);
this.mostRecentActivity = new MostRecentActivity.MostRecentActivity(this);
this._addSynapseLinkDialogProps = ko.observable<DialogProps>({
isModal: false,
visible: false,
title: undefined,
subText: undefined,
primaryButtonText: undefined,
secondaryButtonText: undefined,
onPrimaryButtonClick: undefined,
onSecondaryButtonClick: undefined,
});
this.addSynapseLinkDialog = new DialogComponentAdapter();
this.addSynapseLinkDialog.parameters = this._addSynapseLinkDialogProps;
} }
public openEnableSynapseLinkDialog(): void { public openEnableSynapseLinkDialog(): void {
@@ -1062,7 +964,7 @@ export default class Explorer {
ConsoleDataType.Info, ConsoleDataType.Info,
"Enabled Azure Synapse Link for this account" "Enabled Azure Synapse Link for this account"
); );
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, startTime); TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime);
this.databaseAccount(databaseAccount); this.databaseAccount(databaseAccount);
} catch (error) { } catch (error) {
NotificationConsoleUtils.clearInProgressMessageWithId(logId); NotificationConsoleUtils.clearInProgressMessageWithId(logId);
@@ -1070,7 +972,7 @@ export default class Explorer {
ConsoleDataType.Error, ConsoleDataType.Error,
`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}` `Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`
); );
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, startTime); TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime);
} finally { } finally {
this.isSynapseLinkUpdating(false); this.isSynapseLinkUpdating(false);
} }
@@ -1081,257 +983,12 @@ export default class Explorer {
TelemetryProcessor.traceCancel(Action.EnableAzureSynapseLink); TelemetryProcessor.traceCancel(Action.EnableAzureSynapseLink);
}, },
}; };
this._addSynapseLinkDialogProps(addSynapseLinkDialogProps); this.openDialog(addSynapseLinkDialogProps);
TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink); TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
// TODO: return result // TODO: return result
} }
public copyUrlLink(src: any, event: MouseEvent): void {
const urlLinkInput: HTMLInputElement = document.getElementById("shareUrlLink") as HTMLInputElement;
urlLinkInput && urlLinkInput.select();
document.execCommand("copy");
this.shareUrlCopyHelperText("Copied");
setTimeout(() => this.shareUrlCopyHelperText("Click to copy"), Constants.ClientDefaults.copyHelperTimeoutMs);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
description: "Copy full screen URL",
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ShareDialog,
});
}
public onCopyUrlLinkKeyPress(src: any, event: KeyboardEvent): boolean {
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
this.copyUrlLink(src, null);
return false;
}
return true;
}
public copyToken(src: any, event: MouseEvent): void {
const tokenInput: HTMLInputElement = document.getElementById("shareToken") as HTMLInputElement;
tokenInput && tokenInput.select();
document.execCommand("copy");
this.shareTokenCopyHelperText("Copied");
setTimeout(() => this.shareTokenCopyHelperText("Click to copy"), Constants.ClientDefaults.copyHelperTimeoutMs);
}
public onCopyTokenKeyPress(src: any, event: KeyboardEvent): boolean {
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
this.copyToken(src, null);
return false;
}
return true;
}
public renewToken = (): void => {
TelemetryProcessor.trace(Action.ConnectEncryptionToken);
this.renewTokenError("");
const id: string = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
"Initiating connection to account"
);
this.renewExplorerShareAccess(this, this.tokenForRenewal())
.fail((error: any) => {
const stringifiedError: string = getErrorMessage(error);
this.renewTokenError("Invalid connection string specified");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to initiate connection to account: ${stringifiedError}`
);
})
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
};
public generateSharedAccessData(): void {
const id: string = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Generating share url");
AuthHeadersUtil.generateEncryptedToken().then(
(tokenResponse: DataModels.GenerateTokenResponse) => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully generated share url");
this.shareAccessData({
readWriteUrl: this._getShareAccessUrlForToken(tokenResponse.readWrite),
readUrl: this._getShareAccessUrlForToken(tokenResponse.read),
});
!this.shareAccessData().readWriteUrl && this.shareAccessToggleState(ShareAccessToggleState.Read); // select read toggle by default for readers
this.shareAccessToggleState.valueHasMutated(); // to set initial url and token state
this.shareAccessData.valueHasMutated();
this._openShareDialog();
},
(error: any) => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to generate share url: ${getErrorMessage(error)}`
);
console.error(error);
}
);
}
public renewShareAccess(token: string): Q.Promise<void> {
if (!this.renewExplorerShareAccess) {
return Q.reject("Not implemented");
}
const deferred: Q.Deferred<void> = Q.defer<void>();
const id: string = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
"Initiating connection to account"
);
this.renewExplorerShareAccess(this, token)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
deferred.resolve();
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to connect: ${getErrorMessage(error)}`
);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
public displayGuestAccessTokenRenewalPrompt(): void {
if (!$("#dataAccessTokenModal").dialog("instance")) {
const connectButton = {
text: "Connect",
class: "connectDialogButtons connectButton connectOkBtns",
click: () => {
this.renewAdHocAccessPane.open();
$("#dataAccessTokenModal").dialog("close");
},
};
const cancelButton = {
text: "Cancel",
class: "connectDialogButtons cancelBtn",
click: () => {
$("#dataAccessTokenModal").dialog("close");
},
};
$("#dataAccessTokenModal").dialog({
autoOpen: false,
buttons: [connectButton, cancelButton],
closeOnEscape: false,
draggable: false,
dialogClass: "no-close",
height: 180,
modal: true,
position: { my: "center center", at: "center center", of: window },
resizable: false,
title: "Temporary access expired",
width: 435,
close: (event: Event, ui: JQueryUI.DialogUIParams) => this.shouldShowDataAccessExpiryDialog(false),
});
$("#dataAccessTokenModal").dialog("option", "classes", {
"ui-dialog-titlebar": "connectTitlebar",
});
}
this.shouldShowDataAccessExpiryDialog(true);
$("#dataAccessTokenModal").dialog("open");
}
public isConnectExplorerVisible(): boolean {
return $("#connectExplorer").is(":visible") || false;
}
public displayContextSwitchPromptForConnectionString(connectionString: string): void {
const yesButton = {
text: "OK",
class: "connectDialogButtons okBtn connectOkBtns",
click: () => {
$("#contextSwitchPrompt").dialog("close");
this.tabsManager.closeTabs(); // clear all tabs so we dont leave any tabs from previous session open
this.renewShareAccess(connectionString);
},
};
const noButton = {
text: "Cancel",
class: "connectDialogButtons cancelBtn",
click: () => {
$("#contextSwitchPrompt").dialog("close");
},
};
if (!$("#contextSwitchPrompt").dialog("instance")) {
$("#contextSwitchPrompt").dialog({
autoOpen: false,
buttons: [yesButton, noButton],
closeOnEscape: false,
draggable: false,
dialogClass: "no-close",
height: 255,
modal: true,
position: { my: "center center", at: "center center", of: window },
resizable: false,
title: "Switch account",
width: 440,
close: (event: Event, ui: JQueryUI.DialogUIParams) => this.shouldShowDataAccessExpiryDialog(false),
});
$("#contextSwitchPrompt").dialog("option", "classes", {
"ui-dialog-titlebar": "connectTitlebar",
});
$("#contextSwitchPrompt").dialog("option", "open", (event: Event, ui: JQueryUI.DialogUIParams) => {
$(".ui-dialog ").css("z-index", 1001);
$("#contextSwitchPrompt").parent().siblings(".ui-widget-overlay").css("z-index", 1000);
});
}
$("#contextSwitchPrompt").dialog("option", "buttons", [yesButton, noButton]); // rebind buttons so callbacks accept current connection string
this.shouldShowContextSwitchPrompt(true);
$("#contextSwitchPrompt").dialog("open");
}
public displayConnectExplorerForm(): void {
$("#divExplorer").hide();
$("#connectExplorer").css("display", "flex");
}
public hideConnectExplorerForm(): void {
$("#connectExplorer").hide();
$("#divExplorer").show();
}
public isReadWriteToggled: () => boolean = (): boolean => {
return this.shareAccessToggleState() === ShareAccessToggleState.ReadWrite;
};
public isReadToggled: () => boolean = (): boolean => {
return this.shareAccessToggleState() === ShareAccessToggleState.Read;
};
public toggleReadWrite: (src: any, event: MouseEvent) => void = (src: any, event: MouseEvent) => {
this.shareAccessToggleState(ShareAccessToggleState.ReadWrite);
};
public toggleRead: (src: any, event: MouseEvent) => void = (src: any, event: MouseEvent) => {
this.shareAccessToggleState(ShareAccessToggleState.Read);
};
public onToggleKeyDown: (src: any, event: KeyboardEvent) => boolean = (src: any, event: KeyboardEvent) => {
if (event.keyCode === Constants.KeyCodes.LeftArrow) {
this.toggleReadWrite(src, null);
return false;
} else if (event.keyCode === Constants.KeyCodes.RightArrow) {
this.toggleRead(src, null);
return false;
}
return true;
};
public isDatabaseNodeOrNoneSelected(): boolean { public isDatabaseNodeOrNoneSelected(): boolean {
return this.isNoneSelected() || this.isDatabaseNodeSelected(); return this.isNoneSelected() || this.isDatabaseNodeSelected();
} }
@@ -1410,15 +1067,11 @@ export default class Explorer {
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> { public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, { const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
let resourceTreeStartKey: number = null; let resourceTreeStartKey: number = null;
if (isInitialLoad) { if (isInitialLoad) {
resourceTreeStartKey = TelemetryProcessor.traceStart(Action.LoadResourceTree, { resourceTreeStartKey = TelemetryProcessor.traceStart(Action.LoadResourceTree, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
} }
@@ -1432,8 +1085,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabases, Action.LoadDatabases,
{ {
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}, },
startKey startKey
@@ -1465,8 +1116,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.LoadDatabases, Action.LoadDatabases,
{ {
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
error: errorMessage, error: errorMessage,
errorStack: getErrorStack(error), errorStack: getErrorStack(error),
@@ -1486,8 +1135,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadResourceTree, Action.LoadResourceTree,
{ {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}, },
resourceTreeStartKey resourceTreeStartKey
@@ -1499,8 +1146,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.LoadResourceTree, Action.LoadResourceTree,
{ {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error), error: getErrorMessage(error),
errorStack: getErrorStack(error), errorStack: getErrorStack(error),
@@ -1523,8 +1168,6 @@ export default class Explorer {
public onRefreshResourcesClick = (source: any, event: MouseEvent): void => { public onRefreshResourcesClick = (source: any, event: MouseEvent): void => {
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, { const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
description: "Refresh button clicked", description: "Refresh button clicked",
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
@@ -1645,6 +1288,7 @@ export default class Explorer {
); );
return; return;
} }
const resetConfirmationDialogProps: DialogProps = { const resetConfirmationDialogProps: DialogProps = {
isModal: true, isModal: true,
visible: true, visible: true,
@@ -1655,7 +1299,7 @@ export default class Explorer {
onPrimaryButtonClick: this._resetNotebookWorkspace, onPrimaryButtonClick: this._resetNotebookWorkspace,
onSecondaryButtonClick: this._closeModalDialog, onSecondaryButtonClick: this._closeModalDialog,
}; };
this._dialogProps(resetConfirmationDialogProps); this.openDialog(resetConfirmationDialogProps);
} }
private async _containsDefaultNotebookWorkspace(databaseAccount: DataModels.DatabaseAccount): Promise<boolean> { private async _containsDefaultNotebookWorkspace(databaseAccount: DataModels.DatabaseAccount): Promise<boolean> {
@@ -1719,69 +1363,13 @@ export default class Explorer {
}; };
private _closeModalDialog = () => { private _closeModalDialog = () => {
this._dialogProps().visible = false; this.closeDialog();
this._dialogProps.valueHasMutated();
}; };
private _closeSynapseLinkModalDialog = () => { private _closeSynapseLinkModalDialog = () => {
this._addSynapseLinkDialogProps().visible = false; this.closeDialog();
this._addSynapseLinkDialogProps.valueHasMutated();
}; };
public handleMessage(message: any) {
const openAction: ActionContracts.DataExplorerAction = message.openAction;
if (!!openAction) {
if (this.isRefreshingExplorer()) {
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
handleOpenAction(openAction, this.nonSystemDatabases(), this);
subscription.dispose();
});
} else {
handleOpenAction(openAction, this.nonSystemDatabases(), this);
}
}
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
handleCachedDataMessage(message);
return;
}
if (message.type) {
switch (message.type) {
case MessageTypes.UpdateLocationHash:
if (!message.locationHash) {
break;
}
hasher.replaceHash(message.locationHash);
RouteHandler.getInstance().parseHash(message.locationHash);
break;
case MessageTypes.SendNotification:
if (!message.message) {
break;
}
NotificationConsoleUtils.logConsoleMessage(
message.consoleDataType || ConsoleDataType.Info,
message.message,
message.id
);
break;
case MessageTypes.ClearNotification:
if (!message.id) {
break;
}
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
break;
case MessageTypes.LoadingStatus:
if (!message.text) {
break;
}
this._setLoadingStatusText(message.text, message.title);
break;
}
return;
}
this.splashScreenAdapter.forceRender();
}
public findSelectedDatabase(): ViewModels.Database { public findSelectedDatabase(): ViewModels.Database {
if (!this.selectedNode()) { if (!this.selectedNode()) {
return null; return null;
@@ -1848,19 +1436,20 @@ export default class Explorer {
this.collectionCreationDefaults = inputs.defaultCollectionThroughput; this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
} }
this.features(inputs.features); this.features(inputs.features);
this.serverId(inputs.serverId); this.serverId(inputs.serverId ?? Constants.ServerIds.productionPortal);
this.databaseAccount(databaseAccount); this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType); this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType);
this.hasWriteAccess(inputs.hasWriteAccess); this.hasWriteAccess(inputs.hasWriteAccess ?? true);
if (inputs.addCollectionDefaultFlight) {
this.flight(inputs.addCollectionDefaultFlight); this.flight(inputs.addCollectionDefaultFlight);
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription); }
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken); this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription ?? false);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken ?? false);
this.setFeatureFlagsFromFlights(inputs.flights); this.setFeatureFlagsFromFlights(inputs.flights);
this.setSelfServeType(inputs); this.setSelfServeType(inputs);
this._importExplorerConfigComplete = true;
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: inputs.extensionEndpoint || "", BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT,
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT), ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
}); });
@@ -1876,9 +1465,7 @@ export default class Explorer {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount, Action.LoadDatabaseAccount,
{ {
resourceId: this.databaseAccount && this.databaseAccount().id,
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
databaseAccount: this.databaseAccount && this.databaseAccount(),
}, },
inputs.loadDatabaseAccountTimestamp inputs.loadDatabaseAccountTimestamp
); );
@@ -1897,9 +1484,6 @@ export default class Explorer {
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) { if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
this.isMongoIndexingEnabled(true); this.isMongoIndexingEnabled(true);
} }
if (flights.indexOf(Constants.Flights.GalleryPublish) !== -1) {
this.isGalleryPublishEnabled = ko.computed<boolean>(() => true);
}
} }
public findSelectedCollection(): ViewModels.Collection { public findSelectedCollection(): ViewModels.Collection {
@@ -2012,8 +1596,6 @@ export default class Explorer {
: this.databases().filter((db) => db.isDatabaseExpanded()); : this.databases().filter((db) => db.isDatabaseExpanded());
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, { const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
}); });
databasesToLoad.forEach(async (database: ViewModels.Database) => { databasesToLoad.forEach(async (database: ViewModels.Database) => {
@@ -2039,8 +1621,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.LoadCollections, Action.LoadCollections,
{ {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error), error: getErrorMessage(error),
errorStack: getErrorStack(error), errorStack: getErrorStack(error),
@@ -2052,83 +1632,6 @@ export default class Explorer {
return deferred.promise; return deferred.promise;
} }
// TODO: Abstract this elsewhere
private _openShareDialog: () => void = (): void => {
if (!$("#shareDataAccessFlyout").dialog("instance")) {
const accountMetadataInfo = {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ShareDialog,
};
const openFullscreenButton = {
text: "Open",
class: "openFullScreenBtn openFullScreenCancelBtn",
click: () => {
TelemetryProcessor.trace(
Action.SelectItem,
ActionModifiers.Mark,
_.extend({}, { description: "Open full screen" }, accountMetadataInfo)
);
const hiddenAnchorElement: HTMLAnchorElement = document.createElement("a");
hiddenAnchorElement.href = this.shareAccessUrl();
hiddenAnchorElement.target = "_blank";
$("#shareDataAccessFlyout").dialog("close");
hiddenAnchorElement.click();
},
};
const cancelButton = {
text: "Cancel",
class: "shareCancelButton openFullScreenCancelBtn",
click: () => {
TelemetryProcessor.trace(
Action.SelectItem,
ActionModifiers.Mark,
_.extend({}, { description: "Cancel open full screen" }, accountMetadataInfo)
);
$("#shareDataAccessFlyout").dialog("close");
},
};
$("#shareDataAccessFlyout").dialog({
autoOpen: false,
buttons: [openFullscreenButton, cancelButton],
closeOnEscape: true,
draggable: false,
dialogClass: "no-close",
position: { my: "right top", at: "right bottom", of: $(".OpenFullScreen") },
resizable: false,
title: "Open Full Screen",
width: 400,
close: (event: Event, ui: JQueryUI.DialogUIParams) => this.shouldShowShareDialogContents(false),
});
$("#shareDataAccessFlyout").dialog("option", "classes", {
"ui-widget-content": "shareUrlDialog",
"ui-widget-header": "shareUrlTitle",
"ui-dialog-titlebar-close": "shareClose",
"ui-button": "shareCloseIcon",
"ui-button-icon": "cancelIcon",
"ui-icon": "",
});
$("#shareDataAccessFlyout").dialog("option", "open", (event: Event, ui: JQueryUI.DialogUIParams) =>
$(".openFullScreenBtn").focus()
);
}
$("#shareDataAccessFlyout").dialog("close");
this.shouldShowShareDialogContents(true);
$("#shareDataAccessFlyout").dialog("open");
};
private _getShareAccessUrlForToken(token: string): string {
if (!token) {
return undefined;
}
const urlPrefixWithKeyParam: string = `${configContext.hostedExplorerURL}?key=`;
const currentActiveTab = this.tabsManager.activeTab();
return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`;
}
private _initSettings() { private _initSettings() {
if (!ExplorerSettings.hasSettingsDefined()) { if (!ExplorerSettings.hasSettingsDefined()) {
ExplorerSettings.createDefaultSettings(); ExplorerSettings.createDefaultSettings();
@@ -2262,12 +1765,7 @@ export default class Explorer {
public async publishNotebook(name: string, content: string | unknown, parentDomElement?: HTMLElement): Promise<void> { public async publishNotebook(name: string, content: string | unknown, parentDomElement?: HTMLElement): Promise<void> {
if (this.notebookManager) { if (this.notebookManager) {
await this.notebookManager.openPublishNotebookPane( await this.notebookManager.openPublishNotebookPane(name, content, parentDomElement);
name,
content,
parentDomElement,
this.isLinkInjectionEnabled()
);
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter; this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
this.isPublishNotebookPaneEnabled(true); this.isPublishNotebookPaneEnabled(true);
} }
@@ -2282,7 +1780,7 @@ export default class Explorer {
} }
public showOkModalDialog(title: string, msg: string): void { public showOkModalDialog(title: string, msg: string): void {
this._dialogProps({ this.openDialog({
isModal: true, isModal: true,
visible: true, visible: true,
title, title,
@@ -2305,7 +1803,7 @@ export default class Explorer {
textFieldProps?: TextFieldProps, textFieldProps?: TextFieldProps,
isPrimaryButtonDisabled?: boolean isPrimaryButtonDisabled?: boolean
): void { ): void {
this._dialogProps({ this.openDialog({
isModal: true, isModal: true,
visible: true, visible: true,
title, title,
@@ -2508,7 +2006,7 @@ export default class Explorer {
} }
private async _refreshNotebooksEnabledStateForAccount(): Promise<void> { private async _refreshNotebooksEnabledStateForAccount(): Promise<void> {
const authType = window.authType as AuthType; const authType = userContext.authType;
if ( if (
authType === AuthType.EncryptedToken || authType === AuthType.EncryptedToken ||
authType === AuthType.ResourceToken || authType === AuthType.ResourceToken ||
@@ -2557,7 +2055,7 @@ export default class Explorer {
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => { public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const armEndpoint = configContext.ARM_ENDPOINT; const armEndpoint = configContext.ARM_ENDPOINT;
const authType = window.authType as AuthType; const authType = userContext.authType;
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
// explorer is not aware of the database account yet // explorer is not aware of the database account yet
this.isSparkEnabledForAccount(false); this.isSparkEnabledForAccount(false);
@@ -2586,7 +2084,7 @@ export default class Explorer {
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => { public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const armEndpoint = configContext.ARM_ENDPOINT; const armEndpoint = configContext.ARM_ENDPOINT;
const authType = window.authType as AuthType; const authType = userContext.authType;
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) { if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
// explorer is not aware of the database account yet // explorer is not aware of the database account yet
return false; return false;
@@ -2639,7 +2137,7 @@ export default class Explorer {
} }
if (item.type === NotebookContentItemType.Directory && item.children && item.children.length > 0) { if (item.type === NotebookContentItemType.Directory && item.children && item.children.length > 0) {
this._dialogProps({ this.openDialog({
isModal: true, isModal: true,
visible: true, visible: true,
title: "Unable to delete file", title: "Unable to delete file",
@@ -2683,8 +2181,6 @@ export default class Explorer {
); );
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, { const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}); });
@@ -2695,8 +2191,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.CreateNewNotebook, Action.CreateNewNotebook,
{ {
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}, },
startKey startKey
@@ -2710,8 +2204,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateNewNotebook, Action.CreateNewNotebook,
{ {
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
error: errorMessage, error: errorMessage,
errorStack: getErrorStack(error), errorStack: getErrorStack(error),

View File

@@ -1031,10 +1031,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.Tab, Action.Tab,
{ {
databaseAccountName: this.props.resourceId,
databaseName: this.props.databaseId, databaseName: this.props.databaseId,
collectionName: this.props.collectionId, collectionName: this.props.collectionId,
defaultExperience: Constants.DefaultAccountExperience.Graph,
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Graph", tabTitle: "Graph",
}, },

View File

@@ -7,7 +7,7 @@ import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import * as CommandBarUtil from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";

View File

@@ -1,5 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager"; import NotebookManager from "../../Notebook/NotebookManager";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
@@ -19,7 +19,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
@@ -61,7 +60,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -125,7 +123,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -207,7 +204,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -295,7 +291,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false); mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.notebookManager = new NotebookManager(); mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);

View File

@@ -1,616 +0,0 @@
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import * as Constants from "../../../Common/Constants";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import ScaleIcon from "../../../../images/Scale_15x15.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { configContext, Platform } from "../../../ConfigContext";
import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class CommandBarComponentButtonFactory {
private static counter: number = 0;
public static createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
if (container.isAuthWithResourceToken()) {
return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container);
}
const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container);
const buttons: CommandButtonComponentProps[] = [];
if (container.isFeatureEnabled && container.isFeatureEnabled("regionselectbutton")) {
const regions = [{ name: "West US" }, { name: "East US" }, { name: "North Europe" }];
buttons.push({
iconSrc: null,
onCommandClick: () => {},
commandButtonLabel: null,
hasPopup: false,
isDropdown: true,
dropdownPlaceholder: "West US",
dropdownSelectedKey: "West US",
dropdownWidth: 100,
children: regions.map(
(region) =>
({
iconSrc: null,
onCommandClick: () => {},
commandButtonLabel: region.name,
dropdownItemKey: region.name,
hasPopup: false,
disabled: false,
ariaLabel: "",
} as CommandButtonComponentProps)
),
ariaLabel: "",
});
}
buttons.push(newCollectionBtn);
const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
buttons.push(CommandBarComponentButtonFactory.createDivider());
buttons.push(addSynapseLink);
}
if (!container.isPreferredApiTable()) {
newCollectionBtn.children = [CommandBarComponentButtonFactory.createNewCollectionGroup(container)];
const newDatabaseBtn = CommandBarComponentButtonFactory.createNewDatabase(container);
newCollectionBtn.children.push(newDatabaseBtn);
}
buttons.push(CommandBarComponentButtonFactory.createDivider());
if (container.isNotebookEnabled()) {
const newNotebookButton = CommandBarComponentButtonFactory.createNewNotebookButton(container);
newNotebookButton.children = [
CommandBarComponentButtonFactory.createNewNotebookButton(container),
CommandBarComponentButtonFactory.createuploadNotebookButton(container),
];
buttons.push(newNotebookButton);
if (container.notebookManager?.gitHubOAuthService) {
buttons.push(CommandBarComponentButtonFactory.createManageGitHubAccountButton(container));
}
}
if (!container.isRunningOnNationalCloud()) {
if (!container.isNotebookEnabled()) {
buttons.push(CommandBarComponentButtonFactory.createEnableNotebooksButton(container));
}
if (container.isPreferredApiMongoDB()) {
buttons.push(CommandBarComponentButtonFactory.createOpenMongoTerminalButton(container));
}
if (container.isPreferredApiCassandra()) {
buttons.push(CommandBarComponentButtonFactory.createOpenCassandraTerminalButton(container));
}
}
if (container.isNotebookEnabled()) {
buttons.push(CommandBarComponentButtonFactory.createOpenTerminalButton(container));
buttons.push(CommandBarComponentButtonFactory.createNotebookWorkspaceResetButton(container));
}
if (!container.isDatabaseNodeOrNoneSelected()) {
if (container.isNotebookEnabled()) {
buttons.push(CommandBarComponentButtonFactory.createDivider());
}
const isSqlQuerySupported = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
if (isSqlQuerySupported) {
const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container);
buttons.push(newSqlQueryBtn);
}
const isSupportedOpenQueryApi =
container.isPreferredApiDocumentDB() || container.isPreferredApiMongoDB() || container.isPreferredApiGraph();
const isSupportedOpenQueryFromDiskApi = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container);
openQueryBtn.children = [
CommandBarComponentButtonFactory.createOpenQueryButton(container),
CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container),
];
buttons.push(openQueryBtn);
} else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) {
buttons.push(CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container));
}
if (CommandBarComponentButtonFactory.areScriptsSupported(container)) {
const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = {
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
newStoredProcedureBtn.children = CommandBarComponentButtonFactory.createScriptCommandButtons(container);
buttons.push(newStoredProcedureBtn);
}
}
return buttons;
}
public static createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) {
const label = "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && (<any>selectedCollection).onNewMongoShellClick();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB(),
};
buttons.push(newMongoShellBtn);
}
return buttons;
}
public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (configContext.platform === Platform.Hosted) {
return buttons;
}
if (!container.isPreferredApiCassandra()) {
const label = "Settings";
const settingsPaneButton: CommandButtonComponentProps = {
iconSrc: SettingsIcon,
iconAlt: label,
onCommandClick: () => container.settingsPane.open(),
commandButtonLabel: null,
ariaLabel: label,
tooltipText: label,
hasPopup: true,
disabled: false,
};
buttons.push(settingsPaneButton);
}
if (container.isHostedDataExplorerEnabled()) {
const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = {
iconSrc: OpenInTabIcon,
iconAlt: label,
onCommandClick: () => container.generateSharedAccessData(),
commandButtonLabel: null,
ariaLabel: label,
tooltipText: label,
hasPopup: false,
disabled: !container.isHostedDataExplorerEnabled(),
className: "OpenFullScreen",
};
buttons.push(fullScreenButton);
}
if (configContext.platform !== Platform.Emulator) {
const label = "Feedback";
const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon,
iconAlt: label,
onCommandClick: () => container.provideFeedbackEmail(),
commandButtonLabel: null,
ariaLabel: label,
tooltipText: label,
hasPopup: false,
disabled: false,
};
buttons.push(feedbackButtonOptions);
}
return buttons;
}
public static createDivider(): CommandButtonComponentProps {
const label = `divider${CommandBarComponentButtonFactory.counter++}`;
return {
isDivider: true,
commandButtonLabel: label,
hasPopup: false,
iconSrc: null,
iconAlt: null,
onCommandClick: null,
ariaLabel: label,
};
}
private static areScriptsSupported(container: Explorer): boolean {
return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
}
private static createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
const label = container.addCollectionText();
return {
iconSrc: AddCollectionIcon,
iconAlt: label,
onCommandClick: () => container.onNewCollectionClicked(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
id: "createNewContainerCommandButton",
};
}
private static createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) {
return null;
}
if (container.isServerlessEnabled()) {
return null;
}
if (
container.databaseAccount &&
container.databaseAccount() &&
container.databaseAccount().properties &&
container.databaseAccount().properties.enableAnalyticalStorage
) {
return null;
}
const capabilities =
(container.databaseAccount &&
container.databaseAccount() &&
container.databaseAccount().properties &&
container.databaseAccount().properties.capabilities) ||
[];
if (capabilities.some((capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics)) {
return null;
}
const label = "Enable Azure Synapse Link";
return {
iconSrc: SynapseIcon,
iconAlt: label,
onCommandClick: () => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label,
hasPopup: false,
disabled: container.isSynapseLinkUpdating(),
ariaLabel: label,
};
}
private static createNewDatabase(container: Explorer): CommandButtonComponentProps {
const label = container.addDatabaseText();
return {
iconSrc: AddDatabaseIcon,
iconAlt: label,
onCommandClick: () => {
container.addDatabasePane.open();
document.getElementById("linkAddDatabase").focus();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
};
}
private static createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
const label = "New SQL Query";
return {
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
} else if (container.isPreferredApiMongoDB()) {
const label = "New Query";
return {
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && (<any>selectedCollection).onNewMongoQueryClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
}
return null;
}
public static createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
const shouldEnableScriptsCommands: boolean =
!container.isDatabaseNodeOrNoneSelected() && CommandBarComponentButtonFactory.areScriptsSupported(container);
if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = {
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newStoredProcedureBtn);
}
if (shouldEnableScriptsCommands) {
const label = "New UDF";
const newUserDefinedFunctionBtn: CommandButtonComponentProps = {
iconSrc: AddUdfIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newUserDefinedFunctionBtn);
}
if (shouldEnableScriptsCommands) {
const label = "New Trigger";
const newTriggerBtn: CommandButtonComponentProps = {
iconSrc: AddTriggerIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newTriggerBtn);
}
return buttons;
}
private static createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook";
return {
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onNewNotebookClicked(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
private static createuploadNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "Upload to Notebook Server";
return {
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onUploadToNotebookServerClicked(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
private static createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query";
return {
iconSrc: BrowseQueriesIcon,
iconAlt: label,
onCommandClick: () => container.browseQueriesPane.open(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: false,
};
}
private static createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query From Disk";
return {
iconSrc: OpenQueryFromDiskIcon,
iconAlt: label,
onCommandClick: () => container.loadQueryPane.open(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: false,
};
}
private static createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) {
return null;
}
const label = "Enable Notebooks (Preview)";
const tooltip =
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const description =
"Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
return {
iconSrc: EnableNotebooksIcon,
iconAlt: label,
onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description),
commandButtonLabel: label,
hasPopup: false,
disabled: !container.isNotebooksEnabledForAccount(),
ariaLabel: label,
tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip,
};
}
private static createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Terminal";
return {
iconSrc: CosmosTerminalIcon,
iconAlt: label,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
private static createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Mongo Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const title = "Set up workspace";
const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (container.isNotebookEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton ? "" : tooltip,
};
}
private static createOpenCassandraTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Cassandra Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const title = "Set up workspace";
const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (container.isNotebookEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
} else {
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton ? "" : tooltip,
};
}
private static createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
const label = "Reset Workspace";
return {
iconSrc: ResetWorkspaceIcon,
iconAlt: label,
onCommandClick: () => container.resetNotebookWorkspace(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
private static createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
return {
iconSrc: GitHubIcon,
iconAlt: label,
onCommandClick: () => {
if (!connectedToGitHub) {
TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, {
databaseAccountName: container.databaseAccount() && container.databaseAccount().name,
defaultExperience: container.defaultExperience && container.defaultExperience(),
dataExplorerArea: Areas.Notebook,
});
}
container.gitHubReposPane.open();
},
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
private static createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] {
const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container);
const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container);
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
newSqlQueryBtn.onCommandClick = () => {
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
};
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
if (!openQueryBtn.disabled) {
openQueryBtn.children = [
CommandBarComponentButtonFactory.createOpenQueryButton(container),
CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container),
];
}
return [newSqlQueryBtn, openQueryBtn];
}
}

View File

@@ -0,0 +1,579 @@
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import * as Constants from "../../../Common/Constants";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { configContext, Platform } from "../../../ConfigContext";
import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as React from "react";
import { OpenFullScreen } from "../../OpenFullScreen";
let counter = 0;
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
if (container.isAuthWithResourceToken()) {
return createStaticCommandBarButtonsForResourceToken(container);
}
const newCollectionBtn = createNewCollectionGroup(container);
const buttons: CommandButtonComponentProps[] = [];
buttons.push(newCollectionBtn);
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
buttons.push(createDivider());
buttons.push(addSynapseLink);
}
if (!container.isPreferredApiTable()) {
newCollectionBtn.children = [createNewCollectionGroup(container)];
const newDatabaseBtn = createNewDatabase(container);
newCollectionBtn.children.push(newDatabaseBtn);
}
buttons.push(createDivider());
if (container.isNotebookEnabled()) {
const newNotebookButton = createNewNotebookButton(container);
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
buttons.push(newNotebookButton);
if (container.notebookManager?.gitHubOAuthService) {
buttons.push(createManageGitHubAccountButton(container));
}
}
if (!container.isRunningOnNationalCloud()) {
if (!container.isNotebookEnabled()) {
buttons.push(createEnableNotebooksButton(container));
}
if (container.isPreferredApiMongoDB()) {
buttons.push(createOpenMongoTerminalButton(container));
}
if (container.isPreferredApiCassandra()) {
buttons.push(createOpenCassandraTerminalButton(container));
}
}
if (container.isNotebookEnabled()) {
buttons.push(createOpenTerminalButton(container));
buttons.push(createNotebookWorkspaceResetButton(container));
}
if (!container.isDatabaseNodeOrNoneSelected()) {
if (container.isNotebookEnabled()) {
buttons.push(createDivider());
}
const isSqlQuerySupported = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
if (isSqlQuerySupported) {
const newSqlQueryBtn = createNewSQLQueryButton(container);
buttons.push(newSqlQueryBtn);
}
const isSupportedOpenQueryApi =
container.isPreferredApiDocumentDB() || container.isPreferredApiMongoDB() || container.isPreferredApiGraph();
const isSupportedOpenQueryFromDiskApi = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
buttons.push(openQueryBtn);
} else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) {
buttons.push(createOpenQueryFromDiskButton(container));
}
if (areScriptsSupported(container)) {
const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = {
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
newStoredProcedureBtn.children = createScriptCommandButtons(container);
buttons.push(newStoredProcedureBtn);
}
}
return buttons;
}
export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) {
const label = "New Shell";
const newMongoShellBtn: CommandButtonComponentProps = {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB(),
};
buttons.push(newMongoShellBtn);
}
return buttons;
}
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (configContext.platform === Platform.Hosted) {
return buttons;
}
if (!container.isPreferredApiCassandra()) {
const label = "Settings";
const settingsPaneButton: CommandButtonComponentProps = {
iconSrc: SettingsIcon,
iconAlt: label,
onCommandClick: () => container.settingsPane.open(),
commandButtonLabel: undefined,
ariaLabel: label,
tooltipText: label,
hasPopup: true,
disabled: false,
};
buttons.push(settingsPaneButton);
}
if (container.isHostedDataExplorerEnabled()) {
const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = {
iconSrc: OpenInTabIcon,
iconAlt: label,
onCommandClick: () => {
container.openSidePanel("Open Full Screen", <OpenFullScreen />);
},
commandButtonLabel: undefined,
ariaLabel: label,
tooltipText: label,
hasPopup: false,
disabled: !container.isHostedDataExplorerEnabled(),
className: "OpenFullScreen",
};
buttons.push(fullScreenButton);
}
if (configContext.platform !== Platform.Emulator) {
const label = "Feedback";
const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon,
iconAlt: label,
onCommandClick: () => container.provideFeedbackEmail(),
commandButtonLabel: undefined,
ariaLabel: label,
tooltipText: label,
hasPopup: false,
disabled: false,
};
buttons.push(feedbackButtonOptions);
}
return buttons;
}
export function createDivider(): CommandButtonComponentProps {
const label = `divider${counter++}`;
return {
isDivider: true,
commandButtonLabel: label,
hasPopup: false,
iconSrc: undefined,
iconAlt: undefined,
onCommandClick: undefined,
ariaLabel: label,
};
}
function areScriptsSupported(container: Explorer): boolean {
return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
}
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
const label = container.addCollectionText();
return {
iconSrc: AddCollectionIcon,
iconAlt: label,
onCommandClick: () => container.onNewCollectionClicked(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
id: "createNewContainerCommandButton",
};
}
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) {
return undefined;
}
if (container.isServerlessEnabled()) {
return undefined;
}
if (
container.databaseAccount &&
container.databaseAccount() &&
container.databaseAccount().properties &&
container.databaseAccount().properties.enableAnalyticalStorage
) {
return undefined;
}
const capabilities =
(container.databaseAccount &&
container.databaseAccount() &&
container.databaseAccount().properties &&
container.databaseAccount().properties.capabilities) ||
[];
if (capabilities.some((capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics)) {
return undefined;
}
const label = "Enable Azure Synapse Link";
return {
iconSrc: SynapseIcon,
iconAlt: label,
onCommandClick: () => container.openEnableSynapseLinkDialog(),
commandButtonLabel: label,
hasPopup: false,
disabled: container.isSynapseLinkUpdating(),
ariaLabel: label,
};
}
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
const label = container.addDatabaseText();
return {
iconSrc: AddDatabaseIcon,
iconAlt: label,
onCommandClick: () => {
container.addDatabasePane.open();
document.getElementById("linkAddDatabase").focus();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
};
}
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
const label = "New SQL Query";
return {
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
} else if (container.isPreferredApiMongoDB()) {
const label = "New Query";
return {
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
}
return undefined;
}
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
const shouldEnableScriptsCommands: boolean =
!container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(container);
if (shouldEnableScriptsCommands) {
const label = "New Stored Procedure";
const newStoredProcedureBtn: CommandButtonComponentProps = {
iconSrc: AddStoredProcedureIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newStoredProcedureBtn);
}
if (shouldEnableScriptsCommands) {
const label = "New UDF";
const newUserDefinedFunctionBtn: CommandButtonComponentProps = {
iconSrc: AddUdfIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newUserDefinedFunctionBtn);
}
if (shouldEnableScriptsCommands) {
const label = "New Trigger";
const newTriggerBtn: CommandButtonComponentProps = {
iconSrc: AddTriggerIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: container.isDatabaseNodeOrNoneSelected(),
};
buttons.push(newTriggerBtn);
}
return buttons;
}
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook";
return {
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onNewNotebookClicked(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "Upload to Notebook Server";
return {
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onUploadToNotebookServerClicked(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createOpenQueryButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query";
return {
iconSrc: BrowseQueriesIcon,
iconAlt: label,
onCommandClick: () => container.browseQueriesPane.open(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: false,
};
}
function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Query From Disk";
return {
iconSrc: OpenQueryFromDiskIcon,
iconAlt: label,
onCommandClick: () => container.loadQueryPane.open(),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled: false,
};
}
function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) {
return undefined;
}
const label = "Enable Notebooks (Preview)";
const tooltip =
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const description =
"Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
return {
iconSrc: EnableNotebooksIcon,
iconAlt: label,
onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description),
commandButtonLabel: label,
hasPopup: false,
disabled: !container.isNotebooksEnabledForAccount(),
ariaLabel: label,
tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip,
};
}
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Terminal";
return {
iconSrc: CosmosTerminalIcon,
iconAlt: label,
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Mongo Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const title = "Set up workspace";
const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (container.isNotebookEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else {
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton ? "" : tooltip,
};
}
function createOpenCassandraTerminalButton(container: Explorer): CommandButtonComponentProps {
const label = "Open Cassandra Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
const title = "Set up workspace";
const description =
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
return {
iconSrc: HostedTerminalIcon,
iconAlt: label,
onCommandClick: () => {
if (container.isNotebookEnabled()) {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
} else {
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
}
},
commandButtonLabel: label,
hasPopup: false,
disabled: disableButton,
ariaLabel: label,
tooltipText: !disableButton ? "" : tooltip,
};
}
function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps {
const label = "Reset Workspace";
return {
iconSrc: ResetWorkspaceIcon,
iconAlt: label,
onCommandClick: () => container.resetNotebookWorkspace(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
return {
iconSrc: GitHubIcon,
iconAlt: label,
onCommandClick: () => {
if (!connectedToGitHub) {
TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, {
dataExplorerArea: Areas.Notebook,
});
}
container.gitHubReposPane.open();
},
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label,
};
}
function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] {
const newSqlQueryBtn = createNewSQLQueryButton(container);
const openQueryBtn = createOpenQueryButton(container);
newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
newSqlQueryBtn.onCommandClick = () => {
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
};
openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected();
if (!openQueryBtn.disabled) {
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
}
return [newSqlQueryBtn, openQueryBtn];
}

View File

@@ -1,10 +1,5 @@
import * as ViewModels from "../../Contracts/ViewModels";
import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility"; import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
import CollectionIcon from "../../../images/tree-collection.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import Explorer from "../Explorer";
export enum Type { export enum Type {
OpenCollection, OpenCollection,
OpenNotebook, OpenNotebook,
@@ -36,11 +31,11 @@ interface StoredData {
/** /**
* Stores most recent activity * Stores most recent activity
*/ */
export class MostRecentActivity { class MostRecentActivity {
private static readonly schemaVersion: string = "1"; private static readonly schemaVersion: string = "1";
private static itemsMaxNumber: number = 5; private static itemsMaxNumber: number = 5;
private storedData: StoredData; private storedData: StoredData;
constructor(private container: Explorer) { constructor() {
// Retrieve from local storage // Retrieve from local storage
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) { if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity); const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity);
@@ -121,42 +116,6 @@ export class MostRecentActivity {
this.saveToLocalStorage(); this.saveToLocalStorage();
} }
public onItemClicked(item: Item) {
switch (item.type) {
case Type.OpenCollection: {
const openCollectionitem = item.data as OpenCollectionItem;
const collection = this.container.findCollection(
openCollectionitem.databaseId,
openCollectionitem.collectionId
);
if (collection) {
collection.openTab();
}
break;
}
case Type.OpenNotebook: {
const openNotebookItem = item.data as OpenNotebookItem;
const notebookItem = this.container.createNotebookContentItemFile(openNotebookItem.name, openNotebookItem.path);
notebookItem && this.container.openNotebook(notebookItem);
break;
}
default:
console.error("Unknown item type", item);
break;
}
}
public static getItemIcon(item: Item): string {
switch (item.type) {
case Type.OpenCollection:
return CollectionIcon;
case Type.OpenNotebook:
return NotebookIcon;
default:
return null;
}
}
/** /**
* Find items by doing strict comparison and remove from array if duplicate is found * Find items by doing strict comparison and remove from array if duplicate is found
* @param item * @param item
@@ -203,3 +162,5 @@ export class MostRecentActivity {
} }
} }
} }
export const mostRecentActivity = new MostRecentActivity();

View File

@@ -224,8 +224,6 @@ export class NotebookClientV2 {
const traceErrorFct = (title: string, message: string) => { const traceErrorFct = (title: string, message: string) => {
TelemetryProcessor.traceFailure(Action.NotebookErrorNotification, { TelemetryProcessor.traceFailure(Action.NotebookErrorNotification, {
databaseAccountName: this.databaseAccountName,
defaultExperience: this.defaultExperience,
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
title, title,
message, message,
@@ -270,8 +268,6 @@ export class NotebookClientV2 {
private handleNotification = (msg: Notification): void => { private handleNotification = (msg: Notification): void => {
if (msg.level === "error") { if (msg.level === "error") {
TelemetryProcessor.traceFailure(Action.NotebookErrorNotification, { TelemetryProcessor.traceFailure(Action.NotebookErrorNotification, {
databaseAccountName: this.databaseAccountName,
defaultExperience: this.defaultExperience,
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
title: msg.title, title: msg.title,
message: msg.message, message: msg.message,

View File

@@ -54,8 +54,6 @@ interface NotebookServiceConfig extends JupyterServerConfig {
const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => { const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, { TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
databaseAccountName: state.cdb.databaseAccountName,
defaultExperience: state.cdb.defaultExperience,
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
title, title,
error, error,

View File

@@ -18,7 +18,6 @@ import { contents } from "rx-jupyter";
import { NotebookContainerClient } from "./NotebookContainerClient"; import { NotebookContainerClient } from "./NotebookContainerClient";
import { MemoryUsageInfo } from "../../Contracts/DataModels"; import { MemoryUsageInfo } from "../../Contracts/DataModels";
import { NotebookContentClient } from "./NotebookContentClient"; import { NotebookContentClient } from "./NotebookContentClient";
import { DialogProps } from "../Controls/DialogReactComponent/DialogComponent";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter"; import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
import { getFullName } from "../../Utils/UserUtils"; import { getFullName } from "../../Utils/UserUtils";
@@ -31,7 +30,6 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export interface NotebookManagerOptions { export interface NotebookManagerOptions {
container: Explorer; container: Explorer;
notebookBasePath: ko.Observable<string>; notebookBasePath: ko.Observable<string>;
dialogProps: ko.Observable<DialogProps>;
resourceTree: ResourceTreeAdapter; resourceTree: ResourceTreeAdapter;
refreshCommandBarButtons: () => void; refreshCommandBarButtons: () => void;
refreshNotebookList: () => void; refreshNotebookList: () => void;
@@ -89,9 +87,7 @@ export default class NotebookManager {
this.notebookContentProvider this.notebookContentProvider
); );
if (this.params.container.isGalleryPublishEnabled()) {
this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient); this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient);
}
this.copyNotebookPaneAdapter = new CopyNotebookPaneAdapter( this.copyNotebookPaneAdapter = new CopyNotebookPaneAdapter(
this.params.container, this.params.container,
@@ -127,10 +123,9 @@ export default class NotebookManager {
public async openPublishNotebookPane( public async openPublishNotebookPane(
name: string, name: string,
content: string | ImmutableNotebook, content: string | ImmutableNotebook,
parentDomElement: HTMLElement, parentDomElement: HTMLElement
isLinkInjectionEnabled: boolean
): Promise<void> { ): Promise<void> {
await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement, isLinkInjectionEnabled); await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement);
} }
public openCopyNotebookPane(name: string, content: string): void { public openCopyNotebookPane(name: string, content: string): void {
@@ -165,9 +160,6 @@ export default class NotebookManager {
primaryButtonLabel || "Commit", primaryButtonLabel || "Commit",
() => { () => {
TelemetryProcessor.trace(Action.NotebooksGitHubCommit, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.NotebooksGitHubCommit, ActionModifiers.Mark, {
databaseAccountName:
this.params.container.databaseAccount() && this.params.container.databaseAccount().name,
defaultExperience: this.params.container.defaultExperience && this.params.container.defaultExperience(),
dataExplorerArea: Areas.Notebook, dataExplorerArea: Areas.Notebook,
}); });
resolve(commitMsg); resolve(commitMsg);
@@ -181,10 +173,8 @@ export default class NotebookManager {
multiline: true, multiline: true,
defaultValue: commitMsg, defaultValue: commitMsg,
rows: 3, rows: 3,
onChange: (_, newValue: string) => { onChange: (_: unknown, newValue: string) => {
commitMsg = newValue; commitMsg = newValue;
this.params.dialogProps().primaryButtonDisabled = !commitMsg;
this.params.dialogProps.valueHasMutated();
}, },
}, },
!commitMsg !commitMsg

View File

@@ -20,7 +20,6 @@ describe("OpenActions", () => {
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane; explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
explorer.cassandraAddCollectionPane.open = jest.fn(); explorer.cassandraAddCollectionPane.open = jest.fn();
explorer.closeAllPanes = () => {}; explorer.closeAllPanes = () => {};
explorer.isConnectExplorerVisible = () => false;
database = { database = {
id: ko.observable("db"), id: ko.observable("db"),

View File

@@ -133,25 +133,19 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection] (<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
) { ) {
explorer.closeAllPanes(); explorer.closeAllPanes();
!explorer.isConnectExplorerVisible() && explorer.addCollectionPane.open(); explorer.addCollectionPane.open();
} else if ( } else if (
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection || action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection] (<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
) { ) {
explorer.closeAllPanes(); explorer.closeAllPanes();
!explorer.isConnectExplorerVisible() && explorer.cassandraAddCollectionPane.open(); explorer.cassandraAddCollectionPane.open();
} else if ( } else if (
action.paneKind === ActionContracts.PaneKind.GlobalSettings || action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings] (<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
) { ) {
explorer.closeAllPanes(); explorer.closeAllPanes();
!explorer.isConnectExplorerVisible() && explorer.settingsPane.open(); explorer.settingsPane.open();
} else if (
action.paneKind === ActionContracts.PaneKind.AdHocAccess ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AdHocAccess]
) {
explorer.closeAllPanes();
!explorer.isConnectExplorerVisible() && explorer.renewAdHocAccessPane.open();
} }
} }

View File

@@ -0,0 +1,17 @@
jest.mock("../hooks/useFullScreenURLs");
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
import { OpenFullScreen } from "./OpenFullScreen";
it("renders the correct URLs", () => {
(useFullScreenURLs as jest.Mock).mockReturnValue({
readWrite: "read and write url",
read: "read only url",
});
render(<OpenFullScreen />);
expect(screen.getByLabelText("Read and Write")).toHaveValue("https://cosmos.azure.com/?key=read and write url");
expect(screen.getByLabelText("Read Only")).toHaveValue("https://cosmos.azure.com/?key=read only url");
});

View File

@@ -0,0 +1,61 @@
import { Spinner, Stack, Text, TextField } from "office-ui-fabric-react";
import { DefaultButton, PrimaryButton } from "office-ui-fabric-react/lib/Button";
import * as React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
import copyToClipboard from "clipboard-copy";
export const OpenFullScreen: React.FunctionComponent = () => {
const result = useFullScreenURLs();
if (!result) {
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
}
const readWriteUrl = `https://cosmos.azure.com/?key=${result.readWrite}`;
const readUrl = `https://cosmos.azure.com/?key=${result.read}`;
return (
<>
<Stack tokens={{ childrenGap: 10 }}>
<Text>
Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read only
access urls below to share with others. For security purposes, the URLs grant time-bound access to the
account. When access expires, you can reconnect, using a valid connection string for the account.
</Text>
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
onClick={() => {
copyToClipboard(readWriteUrl);
}}
text="Copy"
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton
onClick={() => {
window.open(readWriteUrl, "_blank");
}}
text="Open"
iconProps={{ iconName: "OpenInNewWindow" }}
/>
</Stack>
<TextField label="Read Only" readOnly defaultValue={readUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
onClick={() => {
copyToClipboard(readUrl);
}}
text="Copy"
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton
onClick={() => {
window.open(readUrl, "_blank");
}}
text="Open"
iconProps={{ iconName: "OpenInNewWindow" }}
/>
</Stack>
</Stack>
</>
);
};

View File

@@ -680,6 +680,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.formWarnings(""); this.formWarnings("");
this.databaseCreateNewShared(this.getSharedThroughputDefault()); this.databaseCreateNewShared(this.getSharedThroughputDefault());
this.shouldCreateMongoWildcardIndex(this.container.isMongoIndexingEnabled()); this.shouldCreateMongoWildcardIndex(this.container.isMongoIndexingEnabled());
if (!this.container.isServerlessEnabled()) {
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
}
if (this.isPreferredApiTable() && !databaseId) { if (this.isPreferredApiTable() && !databaseId) {
databaseId = SharedConstants.CollectionCreation.TablesAPIDefaultDatabase; databaseId = SharedConstants.CollectionCreation.TablesAPIDefaultDatabase;
} }
@@ -689,8 +693,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.databaseId(databaseId); this.databaseId(databaseId);
const addCollectionPaneOpenMessage = { const addCollectionPaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({ collection: ko.toJS({
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
@@ -784,8 +786,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
const autoPilot: DataModels.AutoPilotCreationSettings = this._getAutoPilot(); const autoPilot: DataModels.AutoPilotCreationSettings = this._getAutoPilot();
const addCollectionPaneStartMessage = { const addCollectionPaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
new: this.databaseCreateNew(), new: this.databaseCreateNew(),
@@ -859,8 +859,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.close(); this.close();
this.container.refreshAllDatabases(); this.container.refreshAllDatabases();
const addCollectionPaneSuccessMessage = { const addCollectionPaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
new: this.databaseCreateNew(), new: this.databaseCreateNew(),
@@ -893,8 +891,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.formErrors(errorMessage); this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage); this.formErrorsDetails(errorMessage);
const addCollectionPaneFailedMessage = { const addCollectionPaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
new: this.databaseCreateNew(), new: this.databaseCreateNew(),

View File

@@ -272,8 +272,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
super.open(); super.open();
this.resetData(); this.resetData();
const addDatabasePaneOpenMessage = { const addDatabasePaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
subscriptionType: SubscriptionType[this.container.subscriptionType()], subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: userContext.quotaId, subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
@@ -295,8 +293,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
const offerThroughput: number = this._computeOfferThroughput(); const offerThroughput: number = this._computeOfferThroughput();
const addDatabasePaneStartMessage = { const addDatabasePaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
shared: this.databaseCreateNewShared(), shared: this.databaseCreateNewShared(),
@@ -359,8 +355,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.close(); this.close();
this.container.refreshAllDatabases(); this.container.refreshAllDatabases();
const addDatabasePaneSuccessMessage = { const addDatabasePaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
shared: this.databaseCreateNewShared(), shared: this.databaseCreateNewShared(),
@@ -383,8 +377,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.formErrors(errorMessage); this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage); this.formErrorsDetails(errorMessage);
const addDatabasePaneFailedMessage = { const addDatabasePaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({ database: ko.toJS({
id: this.databaseId(), id: this.databaseId(),
shared: this.databaseCreateNewShared(), shared: this.databaseCreateNewShared(),

View File

@@ -41,8 +41,6 @@ export class BrowseQueriesPane extends ContextualPaneBase {
} }
const startKey: number = TelemetryProcessor.traceStart(Action.SetupSavedQueries, { const startKey: number = TelemetryProcessor.traceStart(Action.SetupSavedQueries, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -53,8 +51,6 @@ export class BrowseQueriesPane extends ContextualPaneBase {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.SetupSavedQueries, Action.SetupSavedQueries,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}, },
@@ -65,8 +61,6 @@ export class BrowseQueriesPane extends ContextualPaneBase {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.SetupSavedQueries, Action.SetupSavedQueries,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
error: errorMessage, error: errorMessage,
@@ -97,8 +91,6 @@ export class BrowseQueriesPane extends ContextualPaneBase {
queryTab.initialEditorContent(savedQuery.query); queryTab.initialEditorContent(savedQuery.query);
queryTab.sqlQueryEditorContent(savedQuery.query); queryTab.sqlQueryEditorContent(savedQuery.query);
TelemetryProcessor.trace(Action.LoadSavedQuery, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.LoadSavedQuery, ActionModifiers.Mark, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
queryName: savedQuery.queryName, queryName: savedQuery.queryName,
paneTitle: this.title(), paneTitle: this.title(),

View File

@@ -289,9 +289,10 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public open() { public open() {
super.open(); super.open();
if (!this.container.isServerlessEnabled()) {
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
}
const addCollectionPaneOpenMessage = { const addCollectionPaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({ collection: ko.toJS({
id: this.tableId(), id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -342,8 +343,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} }
const addCollectionPaneStartMessage = { const addCollectionPaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({ collection: ko.toJS({
id: this.tableId(), id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -388,8 +387,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
const addCollectionPaneSuccessMessage = { const addCollectionPaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({ collection: ko.toJS({
id: this.tableId(), id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -418,8 +415,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.formErrors(errorMessage); this.formErrors(errorMessage);
this.isExecuting(false); this.isExecuting(false);
const addCollectionPaneFailedMessage = { const addCollectionPaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: { collection: {
id: this.tableId(), id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,

View File

@@ -35,8 +35,6 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
this.close(); this.close();
this.container.isAccountReady() && this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Close, { TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Close, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -56,8 +54,6 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
this.resizePane(); this.resizePane();
this.container.isAccountReady() && this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Open, { TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Open, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -78,8 +74,6 @@ export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
event.stopPropagation(); event.stopPropagation();
this.container.isAccountReady() && this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Submit, { TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Submit, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });

View File

@@ -42,8 +42,6 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
this.isExecuting(true); this.isExecuting(true);
const selectedCollection = <ViewModels.Collection>this.container.findSelectedCollection(); const selectedCollection = <ViewModels.Collection>this.container.findSelectedCollection();
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collectionId: selectedCollection.id(), collectionId: selectedCollection.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
@@ -63,8 +61,6 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteCollection, Action.DeleteCollection,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collectionId: selectedCollection.id(), collectionId: selectedCollection.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
@@ -94,8 +90,6 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteCollection, Action.DeleteCollection,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collectionId: selectedCollection.id(), collectionId: selectedCollection.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),

View File

@@ -122,8 +122,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
this.setState({ formError: "", isExecuting: true }); this.setState({ formError: "", isExecuting: true });
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, {
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(), collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection", paneTitle: "Delete Collection",
@@ -142,8 +140,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteCollection, Action.DeleteCollection,
{ {
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(), collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection", paneTitle: "Delete Collection",
@@ -171,8 +167,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteCollection, Action.DeleteCollection,
{ {
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(), collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection", paneTitle: "Delete Collection",

View File

@@ -46,8 +46,6 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.isExecuting(true); this.isExecuting(true);
const selectedDatabase = this.container.findSelectedDatabase(); const selectedDatabase = this.container.findSelectedDatabase();
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDatabase, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDatabase, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
databaseId: selectedDatabase.id(), databaseId: selectedDatabase.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
@@ -73,8 +71,6 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteDatabase, Action.DeleteDatabase,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
databaseId: selectedDatabase.id(), databaseId: selectedDatabase.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
@@ -105,8 +101,6 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.DeleteDatabase, Action.DeleteDatabase,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
databaseId: selectedDatabase.id(), databaseId: selectedDatabase.id(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),

View File

@@ -157,8 +157,6 @@ export class GitHubReposPane extends ContextualPaneBase {
this.isExecuting(false); this.isExecuting(false);
this.title(GitHubReposComponent.ManageGitHubRepoTitle); // Used for telemetry this.title(GitHubReposComponent.ManageGitHubRepoTitle); // Used for telemetry
TelemetryProcessor.trace(Action.NotebooksGitHubManageRepo, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.NotebooksGitHubManageRepo, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook, dataExplorerArea: Areas.Notebook,
}); });
this.triggerRender(); this.triggerRender();
@@ -339,8 +337,6 @@ export class GitHubReposPane extends ContextualPaneBase {
private connectToGitHub(scope: string): void { private connectToGitHub(scope: string): void {
this.isExecuting(true); this.isExecuting(true);
TelemetryProcessor.trace(Action.NotebooksGitHubAuthorize, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.NotebooksGitHubAuthorize, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook, dataExplorerArea: Areas.Notebook,
scopesSelected: scope, scopesSelected: scope,
}); });

View File

@@ -11,7 +11,6 @@ import TableQuerySelectPaneTemplate from "./Tables/TableQuerySelectPane.html";
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html"; import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
import SettingsPaneTemplate from "./SettingsPane.html"; import SettingsPaneTemplate from "./SettingsPane.html";
import ExecuteSprocParamsPaneTemplate from "./ExecuteSprocParamsPane.html"; import ExecuteSprocParamsPaneTemplate from "./ExecuteSprocParamsPane.html";
import RenewAdHocAccessPaneTemplate from "./RenewAdHocAccessPane.html";
import UploadItemsPaneTemplate from "./UploadItemsPane.html"; import UploadItemsPaneTemplate from "./UploadItemsPane.html";
import LoadQueryPaneTemplate from "./LoadQueryPane.html"; import LoadQueryPaneTemplate from "./LoadQueryPane.html";
import SaveQueryPaneTemplate from "./SaveQueryPane.html"; import SaveQueryPaneTemplate from "./SaveQueryPane.html";
@@ -144,15 +143,6 @@ export class ExecuteSprocParamsComponent {
} }
} }
export class RenewAdHocAccessPane {
constructor() {
return {
viewModel: PaneComponent,
template: RenewAdHocAccessPaneTemplate,
};
}
}
export class UploadItemsPaneComponent { export class UploadItemsPaneComponent {
constructor() { constructor() {
return { return {

View File

@@ -32,7 +32,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
private notebookObject: ImmutableNotebook; private notebookObject: ImmutableNotebook;
private parentDomElement: HTMLElement; private parentDomElement: HTMLElement;
private isCodeOfConductAccepted: boolean; private isCodeOfConductAccepted: boolean;
private isLinkInjectionEnabled: boolean;
constructor(private container: Explorer, private junoClient: JunoClient) { constructor(private container: Explorer, private junoClient: JunoClient) {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
@@ -101,8 +100,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
name: string, name: string,
author: string, author: string,
notebookContent: string | ImmutableNotebook, notebookContent: string | ImmutableNotebook,
parentDomElement: HTMLElement, parentDomElement: HTMLElement
isLinkInjectionEnabled: boolean
): Promise<void> { ): Promise<void> {
try { try {
const response = await this.junoClient.isCodeOfConductAccepted(); const response = await this.junoClient.isCodeOfConductAccepted();
@@ -130,7 +128,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.parentDomElement = parentDomElement; this.parentDomElement = parentDomElement;
this.isOpened = true; this.isOpened = true;
this.isLinkInjectionEnabled = isLinkInjectionEnabled;
this.triggerRender(); this.triggerRender();
} }
@@ -155,10 +152,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
} }
try { try {
startKey = traceStart(Action.NotebooksGalleryPublish, { startKey = traceStart(Action.NotebooksGalleryPublish, {});
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
});
const response = await this.junoClient.publishNotebook( const response = await this.junoClient.publishNotebook(
this.name, this.name,
@@ -166,8 +160,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.tags?.split(","), this.tags?.split(","),
this.author, this.author,
this.imageSrc, this.imageSrc,
this.content, this.content
this.isLinkInjectionEnabled
); );
const data = response.data; const data = response.data;
@@ -187,8 +180,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
traceSuccess( traceSuccess(
Action.NotebooksGalleryPublish, Action.NotebooksGalleryPublish,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
notebookId: data.id, notebookId: data.id,
isPublishPending, isPublishPending,
}, },
@@ -199,8 +190,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
traceFailure( traceFailure(
Action.NotebooksGalleryPublish, Action.NotebooksGalleryPublish,
{ {
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
error: getErrorMessage(error), error: getErrorMessage(error),
errorStack: getErrorStack(error), errorStack: getErrorStack(error),
}, },
@@ -248,6 +237,5 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.notebookObject = undefined; this.notebookObject = undefined;
this.parentDomElement = undefined; this.parentDomElement = undefined;
this.isCodeOfConductAccepted = undefined; this.isCodeOfConductAccepted = undefined;
this.isLinkInjectionEnabled = undefined;
}; };
} }

View File

@@ -1,90 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div
class="contextual-pane-out"
data-bind="
click: cancel,
clickBubble: false"
></div>
<div class="contextual-pane" id="renewadhocaccesspane">
<!-- Renew ad-hoc access form - Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Renew ad-hoc access header - Start -->
<div class="firstdivbg headerline">
<span role="heading" aria-level="2" data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="
click: cancel"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Renew ad-hoc access header - End -->
<!-- Renew ad-hoc access errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="
visible: formErrorsDetails() && formErrorsDetails() !== '',
click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Renew ad-hoc access errors - End -->
<!-- Renew ad-hoc access inputs - Start -->
<div class="paneMainContent">
<div class="renewUploadItemsHeader">Provide a valid account connection string</div>
<input
class="accessKeyInput"
type="text"
placeholder="Enter a connection string"
required
data-bind="value: accessKey"
/>
<div
class="renewAccessExpandCollapse"
data-bind="click: onShowHelperImageClick, event: { keypress: onShowHelperImageKeyPress }"
>
<img src="/Triangle-right.svg" alt="Show renew access image" data-bind="visible: !isHelperImageVisible()" />
<img src="/Triangle-down.svg" alt="Hide renew access image" data-bind="visible: isHelperImageVisible()" />
<span class="AccountNavigationText">Where do I find the Connection String?</span>
</div>
<div class="renewAccessImg" data-bind="visible: isHelperImageVisible()">
<span class="AccountNavigationText"
>To get the connection string, navigate to your Azure Cosmos DB account in Azure Portal, select Keys and
copy the connection string.</span
>
<img src="/ConnectionString_Artwork.png" />
</div>
</div>
<div class="paneFooter">
<div class="leftpanel-okbut"><input type="submit" value="Connect" class="btncreatecoll1" /></div>
</div>
<!-- Renew ad-hoc access - End -->
</form>
</div>
<!-- Renew ad-hoc access form - Start -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,101 +0,0 @@
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { parseConnectionString } from "../../Platform/Hosted/Helpers/ConnectionStringParser";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class RenewAdHocAccessPane extends ContextualPaneBase {
public accessKey: ko.Observable<string>;
public isHelperImageVisible: ko.Observable<boolean>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Connect to Azure Cosmos DB");
this.accessKey = ko.observable<string>();
this.isHelperImageVisible = ko.observable<boolean>(false);
}
public submit(): void {
this.formErrors("");
this.formErrorsDetails("");
if (this._shouldShowContextSwitchPrompt()) {
this.container.displayContextSwitchPromptForConnectionString(this.accessKey());
} else if (!!this.formErrors()) {
return;
} else {
this.isExecuting(true);
this._renewShareAccess();
}
}
public onShowHelperImageClick = (src: any, event: MouseEvent): void => {
this.isHelperImageVisible(!this.isHelperImageVisible());
};
public onShowHelperImageKeyPress = (src: any, event: KeyboardEvent): boolean => {
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
this.onShowHelperImageClick(src, null);
return false;
}
return true;
};
private _shouldShowContextSwitchPrompt(): boolean {
const inputMetadata: DataModels.AccessInputMetadata = parseConnectionString(this.accessKey());
const apiKind: DataModels.ApiKind =
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
const hasOpenedTabs: boolean =
(this.container && this.container.tabsManager && this.container.tabsManager.openedTabs().length > 0) || false;
if (!inputMetadata || inputMetadata.apiKind == null || !inputMetadata.accountName) {
this.formErrors("Invalid connection string input");
this.formErrorsDetails("Please enter a valid connection string");
}
if (
!inputMetadata ||
this.formErrors() ||
!this.container ||
apiKind == null ||
!this.container.databaseAccount ||
!this.container.defaultExperience ||
!hasOpenedTabs ||
(this.container.databaseAccount().name === inputMetadata.accountName &&
apiKind === inputMetadata.apiKind &&
!hasOpenedTabs)
) {
return false;
}
return true;
}
private _renewShareAccess = (): void => {
this.container
.renewShareAccess(this.accessKey())
.fail((error: any) => {
const errorMessage: string = getErrorMessage(error);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage);
})
.finally(() => {
this.isExecuting(false);
});
};
public close(): void {
super.close();
this.isHelperImageVisible(false);
this.formErrors("");
this.formErrorsDetails("");
this.accessKey("");
}
}

View File

@@ -63,8 +63,6 @@ export class SaveQueryPane extends ContextualPaneBase {
query: query, query: query,
}; };
const startKey: number = TelemetryProcessor.traceStart(Action.SaveQuery, { const startKey: number = TelemetryProcessor.traceStart(Action.SaveQuery, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -77,8 +75,6 @@ export class SaveQueryPane extends ContextualPaneBase {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.SaveQuery, Action.SaveQuery,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}, },
@@ -94,8 +90,6 @@ export class SaveQueryPane extends ContextualPaneBase {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.SaveQuery, Action.SaveQuery,
{ {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
error: errorMessage, error: errorMessage,
@@ -113,8 +107,6 @@ export class SaveQueryPane extends ContextualPaneBase {
} }
const startKey: number = TelemetryProcessor.traceStart(Action.SetupSavedQueries, { const startKey: number = TelemetryProcessor.traceStart(Action.SetupSavedQueries, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -125,8 +117,6 @@ export class SaveQueryPane extends ContextualPaneBase {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.SetupSavedQueries, Action.SetupSavedQueries,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}, },
@@ -137,8 +127,6 @@ export class SaveQueryPane extends ContextualPaneBase {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.SetupSavedQueries, Action.SetupSavedQueries,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane, dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
error: errorMessage, error: errorMessage,

View File

@@ -54,8 +54,6 @@ export class SetupNotebooksPane extends ContextualPaneBase {
} }
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, { const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}); });
@@ -74,8 +72,6 @@ export class SetupNotebooksPane extends ContextualPaneBase {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.CreateNotebookWorkspace, Action.CreateNotebookWorkspace,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
}, },
@@ -90,8 +86,6 @@ export class SetupNotebooksPane extends ContextualPaneBase {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateNotebookWorkspace, Action.CreateNotebookWorkspace,
{ {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane, dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(), paneTitle: this.title(),
error: errorMessage, error: errorMessage,

View File

@@ -1,6 +1,6 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil"; import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter"; import { SplashScreen } from "./SplashScreen";
import { TabsManager } from "../Tabs/TabsManager"; import { TabsManager } from "../Tabs/TabsManager";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
jest.mock("../Explorer"); jest.mock("../Explorer");
@@ -14,7 +14,7 @@ const createExplorer = () => {
return mock as jest.Mocked<Explorer>; return mock as jest.Mocked<Explorer>;
}; };
describe("SplashScreenComponentAdapter", () => { describe("SplashScreen", () => {
it("allows sample collection creation for supported api's", () => { it("allows sample collection creation for supported api's", () => {
const explorer = createExplorer(); const explorer = createExplorer();
const dataSampleUtil = new DataSamplesUtil(explorer); const dataSampleUtil = new DataSamplesUtil(explorer);
@@ -25,9 +25,9 @@ describe("SplashScreenComponentAdapter", () => {
// Sample is supported // Sample is supported
jest.spyOn(dataSampleUtil, "isSampleContainerCreationSupported").mockImplementation(() => true); jest.spyOn(dataSampleUtil, "isSampleContainerCreationSupported").mockImplementation(() => true);
const splashScreenAdapter = new SplashScreenComponentAdapter(explorer); const splashScreen = new SplashScreen({ explorer });
jest.spyOn(splashScreenAdapter, "createDataSampleUtil").mockImplementation(() => dataSampleUtil); jest.spyOn(splashScreen, "createDataSampleUtil").mockImplementation(() => dataSampleUtil);
const mainButtons = splashScreenAdapter.createMainItems(); const mainButtons = splashScreen.createMainItems();
// Press all buttons and make sure create gets called // Press all buttons and make sure create gets called
mainButtons.forEach((button) => { mainButtons.forEach((button) => {
@@ -50,9 +50,9 @@ describe("SplashScreenComponentAdapter", () => {
// Sample is not supported // Sample is not supported
jest.spyOn(dataSampleUtil, "isSampleContainerCreationSupported").mockImplementation(() => false); jest.spyOn(dataSampleUtil, "isSampleContainerCreationSupported").mockImplementation(() => false);
const splashScreenAdapter = new SplashScreenComponentAdapter(explorerStub); const splashScreen = new SplashScreen({ explorer: explorerStub });
jest.spyOn(splashScreenAdapter, "createDataSampleUtil").mockImplementation(() => dataSampleUtil); jest.spyOn(splashScreen, "createDataSampleUtil").mockImplementation(() => dataSampleUtil);
const mainButtons = splashScreenAdapter.createMainItems(); const mainButtons = splashScreen.createMainItems();
// Press all buttons and make sure create doesn't get called // Press all buttons and make sure create doesn't get called
mainButtons.forEach((button) => { mainButtons.forEach((button) => {

View File

@@ -0,0 +1,384 @@
/**
* Accordion top class
*/
import * as React from "react";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import { Link } from "office-ui-fabric-react/lib/Link";
import NewContainerIcon from "../../../images/Hero-new-container.svg";
import NewNotebookIcon from "../../../images/Hero-new-notebook.svg";
import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg";
import OpenQueryIcon from "../../../images/BrowseQuery.svg";
import NewStoredProcedureIcon from "../../../images/AddStoredProcedure.svg";
import ScaleAndSettingsIcon from "../../../images/Scale_15x15.svg";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import SampleIcon from "../../../images/Hero-sample.svg";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
import CollectionIcon from "../../../images/tree-collection.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
export interface SplashScreenItem {
iconSrc: string;
title: string;
info?: string;
description: string;
onClick: () => void;
}
export interface SplashScreenProps {
explorer: Explorer;
}
export class SplashScreen extends React.Component<SplashScreenProps> {
private static readonly seeMoreItemTitle: string = "See more Cosmos DB documentation";
private static readonly seeMoreItemUrl: string = "https://aka.ms/cosmosdbdocument";
private static readonly dataModelingUrl = "https://docs.microsoft.com/azure/cosmos-db/modeling-data";
private static readonly throughputEstimatorUrl = "https://cosmos.azure.com/capacitycalculator";
private static readonly failoverUrl = "https://docs.microsoft.com/azure/cosmos-db/high-availability";
private readonly container: Explorer;
private subscriptions: Array<{ dispose: () => void }>;
constructor(props: SplashScreenProps) {
super(props);
this.container = props.explorer;
this.subscriptions = [];
}
public shouldComponentUpdate() {
return this.container.tabsManager.openedTabs.length === 0;
}
public componentWillUnmount() {
while (this.subscriptions.length) {
this.subscriptions.pop().dispose();
}
}
public componentDidMount() {
this.subscriptions.push(
this.container.tabsManager.openedTabs.subscribe(() => this.setState({})),
this.container.selectedNode.subscribe(() => this.setState({})),
this.container.isNotebookEnabled.subscribe(() => this.setState({}))
);
}
private clearMostRecent = (): void => {
MostRecentActivity.mostRecentActivity.clear(userContext.databaseAccount?.id);
this.setState({});
};
public render(): JSX.Element {
const mainItems = this.createMainItems();
const commonTaskItems = this.createCommonTaskItems();
const recentItems = this.createRecentItems();
const tipsItems = this.createTipsItems();
const onClearRecent = this.clearMostRecent;
return (
<div className="splashScreenContainer">
<div className="splashScreen">
<div className="title">
Welcome to Cosmos DB
<FeaturePanelLauncher />
</div>
<div className="subtitle">Globally distributed, multi-model database service for any scale</div>
<div className="mainButtonsContainer">
{mainItems.map((item) => (
<div
className="mainButton focusable"
key={`${item.title}`}
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="button"
>
<img src={item.iconSrc} alt="" />
<div className="legendContainer">
<div className="legend">{item.title}</div>
<div className="description">{item.description}</div>
</div>
</div>
))}
</div>
<div className="moreStuffContainer">
<div className="moreStuffColumn commonTasks">
<div className="title">Common Tasks</div>
<ul>
{commonTaskItems.map((item) => (
<li
className="focusable"
key={`${item.title}${item.description}`}
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="button"
>
<img src={item.iconSrc} alt="" />
<span className="oneLineContent" title={item.info}>
{item.title}
</span>
</li>
))}
</ul>
</div>
<div className="moreStuffColumn">
<div className="title">Recents</div>
<ul>
{recentItems.map((item, index) => (
<li key={`${item.title}${item.description}${index}`}>
<img src={item.iconSrc} alt="" />
<span className="twoLineContent">
<Link onClick={item.onClick} title={item.info}>
{item.title}
</Link>
<div className="description">{item.description}</div>
</span>
</li>
))}
</ul>
{recentItems.length > 0 && <Link onClick={() => onClearRecent()}>Clear Recents</Link>}
</div>
<div className="moreStuffColumn tipsContainer">
<div className="title">Tips</div>
<ul>
{tipsItems.map((item) => (
<li
className="tipContainer focusable"
key={`${item.title}${item.description}`}
onClick={item.onClick}
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
tabIndex={0}
role="link"
>
<div className="title" title={item.info}>
{item.title}
</div>
<div className="description">{item.description}</div>
</li>
))}
<li>
<a role="link" href={SplashScreen.seeMoreItemUrl} target="_blank" tabIndex={0}>
{SplashScreen.seeMoreItemTitle}
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}
/**
* This exists to enable unit testing
*/
public createDataSampleUtil(): DataSamplesUtil {
return new DataSamplesUtil(this.container);
}
/**
* public for testing purposes
*/
public createMainItems(): SplashScreenItem[] {
const dataSampleUtil = this.createDataSampleUtil();
const heroes: SplashScreenItem[] = [
{
iconSrc: NewContainerIcon,
title: this.container.addCollectionText(),
description: "Create a new container for storage and throughput",
onClick: () => this.container.onNewCollectionClicked(),
},
];
if (dataSampleUtil.isSampleContainerCreationSupported()) {
// Insert at the front
heroes.unshift({
iconSrc: SampleIcon,
title: "Start with Sample",
description: "Get started with a sample provided by Cosmos DB",
onClick: () => dataSampleUtil.createSampleContainerAsync(),
});
}
if (this.container.isNotebookEnabled()) {
heroes.push({
iconSrc: NewNotebookIcon,
title: "New Notebook",
description: "Create a notebook to start querying, visualizing, and modeling your data",
onClick: () => this.container.onNewNotebookClicked(),
});
}
return heroes;
}
private getItemIcon(item: MostRecentActivity.Item): string {
switch (item.type) {
case MostRecentActivity.Type.OpenCollection:
return CollectionIcon;
case MostRecentActivity.Type.OpenNotebook:
return NotebookIcon;
default:
return null;
}
}
private onItemClicked(item: MostRecentActivity.Item) {
switch (item.type) {
case MostRecentActivity.Type.OpenCollection: {
const openCollectionitem = item.data as MostRecentActivity.OpenCollectionItem;
const collection = this.container.findCollection(
openCollectionitem.databaseId,
openCollectionitem.collectionId
);
if (collection) {
collection.openTab();
}
break;
}
case MostRecentActivity.Type.OpenNotebook: {
const openNotebookItem = item.data as MostRecentActivity.OpenNotebookItem;
const notebookItem = this.container.createNotebookContentItemFile(openNotebookItem.name, openNotebookItem.path);
notebookItem && this.container.openNotebook(notebookItem);
break;
}
default:
console.error("Unknown item type", item);
break;
}
}
private createCommonTaskItems(): SplashScreenItem[] {
const items: SplashScreenItem[] = [];
if (this.container.isAuthWithResourceToken()) {
return items;
}
if (!this.container.isDatabaseNodeOrNoneSelected()) {
if (this.container.isPreferredApiDocumentDB() || this.container.isPreferredApiGraph()) {
items.push({
iconSrc: NewQueryIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
},
title: "New SQL Query",
description: null,
});
} else if (this.container.isPreferredApiMongoDB()) {
items.push({
iconSrc: NewQueryIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
},
title: "New Query",
description: null,
});
}
items.push({
iconSrc: OpenQueryIcon,
title: "Open Query",
description: null,
onClick: () => this.container.browseQueriesPane.open(),
});
if (!this.container.isPreferredApiCassandra()) {
items.push({
iconSrc: NewStoredProcedureIcon,
title: "New Stored Procedure",
description: null,
onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
});
}
/* Scale & Settings */
let isShared = false;
if (this.container.isDatabaseNodeSelected()) {
isShared = this.container.findSelectedDatabase().isDatabaseShared();
} else if (this.container.isNodeKindSelected("Collection")) {
const database: ViewModels.Database = this.container.findSelectedCollection().getDatabase();
isShared = database && database.isDatabaseShared();
}
const label = isShared ? "Settings" : "Scale & Settings";
items.push({
iconSrc: ScaleAndSettingsIcon,
title: label,
description: null,
onClick: () => {
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
selectedCollection && selectedCollection.onSettingsClick();
},
});
} else {
items.push({
iconSrc: AddDatabaseIcon,
title: this.container.addDatabaseText(),
description: null,
onClick: () => this.container.addDatabasePane.open(),
});
}
return items;
}
private static getInfo(item: MostRecentActivity.Item): string {
if (item.type === MostRecentActivity.Type.OpenNotebook) {
const data = item.data as MostRecentActivity.OpenNotebookItem;
return data.path;
} else {
return undefined;
}
}
private createRecentItems(): SplashScreenItem[] {
return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((item) => ({
iconSrc: this.getItemIcon(item),
title: item.title,
description: item.description,
info: SplashScreen.getInfo(item),
onClick: () => this.onItemClicked(item),
}));
}
private createTipsItems(): SplashScreenItem[] {
return [
{
iconSrc: null,
title: "Data Modeling",
description: "Learn more about modeling",
onClick: () => window.open(SplashScreen.dataModelingUrl),
},
{
iconSrc: null,
title: "Cost & Throughput Calculation",
description: "Learn more about cost calculation",
onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
},
{
iconSrc: null,
title: "Configure automatic failover",
description: "Learn more about Cosmos DB high-availability",
onClick: () => window.open(SplashScreen.failoverUrl),
},
];
}
private onSplashScreenItemKeyPress(event: React.KeyboardEvent, callback: () => void) {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
callback();
event.stopPropagation();
}
}
}

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