Compare commits

...

64 Commits

Author SHA1 Message Date
Laurent Nguyen
8141799ffd Initial move of command bar to react 2021-04-14 11:44:22 +02:00
Steve Faulkner
732d7ce8fa Fix upload worker error display (#547) 2021-03-15 22:47:49 -05:00
Steve Faulkner
254c551999 Test Explorer Improvements (#541) 2021-03-14 22:53:16 -05:00
Jordi Bunster
f86883de6c MostRecentActivity changes (#463)
This changes the public API a bit, so that recording activity (the most common use) is less involved.
2021-03-15 03:10:48 +00:00
Steve Faulkner
62550f8d6a Deprecate Explorer Properties (#535) 2021-03-10 23:02:55 -06:00
hardiknai-techm
184910ee6c Resolve Lint errors in NotebookConfigurationUtils.ts (#490) 2021-03-10 21:47:08 -06:00
Steve Faulkner
9253ab1876 Run Emulator test only on master (#536) 2021-03-10 18:52:32 -06:00
Steve Faulkner
920c95b614 Fix cleanup databases task 2021-03-10 16:03:50 -06:00
Srinath Narayanan
1d98c83be5 Created selfServe landing page (#444)
* Portal changes for DedicatedGateway

Changes to support creation and deletion of DedicatedGateway resource.

Tested locally with various scenarios.

* Portal changes for DedicatedGateway. CR feedback

* Stylecop changes

* created selfServe.html landing page

* Removing TODO comments

* exposed baselineValues

* added getOnSaveNotification

* disable UI when onSave is taking place

* minro edits

* made polling optional

* added optional polling

* added default

* Added portal notifications

* merged more changes

* minor edits

* added label for description

* Added correlationids and polling of refresh

* Added correlationids and polling of refresh

* minor edit

* added label tooltip

* removed ClassInfo decorator

* Added dynamic decription

* added info and warninf types for description

* more changes to promise retry

* promise retry changes

* added spinner on selfserve load

* compile errors fixed

* New changes

* added operationstatus link

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* more retry changes

* more changes

* added polling on landing on the page

* edits for error display

* added keys blade link

* added link generation

* added link to blade

* Modified info and description

* fixed format errors

* added selfserve contract to output files

* addressed PR comments

Co-authored-by: Balaji Sridharan <fnbalaji@microsoft.com>
Co-authored-by: fnbalaji <75445927+fnbalaji@users.noreply.github.com>
2021-03-10 13:55:05 -08:00
Sunil Kumar Yadav
b85a20cbea Fix Lint errors in StringUtility.ts (#479) 2021-03-09 19:33:29 -06:00
hardiknai-techm
d0f6923d24 Configure onsave auto format eslint in vscode (#478) 2021-03-09 19:31:38 -06:00
Srinath Narayanan
ecdc41ada9 Added more selfserve changes (#443)
* exposed baselineValues

* added getOnSaveNotification

* disable UI when onSave is taking place

* added optional polling

* Added portal notifications

* minor edits

* added label for description

* Added correlationids and polling of refresh

* added label tooltip

* removed ClassInfo decorator

* Added dynamic decription

* added info and warninf types for description

* promise retry changes

* compile errors fixed

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* added polling on landing on the page

* edits for error display

* added link generation

* addressed PR comments

* modified test

* fixed compilation error
2021-03-09 16:07:23 -08:00
Tanuj Mittal
c1b74266eb Gallery fixes (#514)
- Fix COC overlay height
- Make standalone gallery usable on mobile devices

Before:
![image](https://user-images.githubusercontent.com/693092/110415215-81cd0680-80b7-11eb-8000-bd0b8536607a.png)

After:
![image](https://user-images.githubusercontent.com/693092/110415236-898cab00-80b7-11eb-8266-94a5718113fe.png)
2021-03-09 18:42:54 +00:00
hardiknai-techm
ef6ecf0a5f Resolve Lint errors in NavBar component (#528) 2021-03-09 12:34:01 -06:00
Sunil Kumar Yadav
b241771e69 fixed eslint of IteratorUtility (#469) 2021-03-09 12:27:24 -06:00
victor-meng
9d63a346e4 Fix issues in the add collection pane (#501) 2021-03-09 10:18:40 -08:00
hardiknai-techm
2e7c7440d3 Resolve Lint errors in CommandBarUtil.test.tsx (#529) 2021-03-09 10:20:14 -06:00
Sunil Kumar Yadav
641dae30a1 fix-eslint-NTeractUtil.ts (#493) 2021-03-09 10:17:02 -06:00
Sunil Kumar Yadav
f192310697 Fixed lint issue and remove unused code (#491) 2021-03-08 22:54:00 -06:00
Sunil Kumar Yadav
588c1d3ec3 fixed eslint GraphUtil and removed files from eslintignore (#482) 2021-03-08 22:53:08 -06:00
hardiknai-techm
7eb2817acc Resolve Lint errors in MessageValidation.ts (#489) 2021-03-08 22:45:56 -06:00
Sunil Kumar Yadav
9c28b7f9c5 Fixed lint issue of AuthorizationUtils (#496) 2021-03-08 19:10:26 -06:00
Sunil Kumar Yadav
4807169b0c Eslint/fix lint date time utility (#471)
* fixed lint issue of DateTimeUtility

* remove commented code

* Do change request.
2021-03-08 17:20:45 -06:00
Sunil Kumar Yadav
d85b6285ac fixed lint issue of StringUtils file (#480)
* fixed lint issue of StringUtils file

* Fixed null lint issue
2021-03-08 17:20:11 -06:00
hardiknai-techm
9617b80b56 Fix Lint errors in ThemeUtility (#477)
* Fix Lint errors in ThemeUtility

* format ThemeUtility.ts file

Co-authored-by: hardiknai <hardiknai92@gmail.com>
Co-authored-by: zen3-hardik <hardikkumar.n@zen3tech.com>
2021-03-08 17:19:22 -06:00
hardiknai-techm
1af44fb207 Fix Lint errors in JunoUtil (#484) 2021-03-08 17:10:24 -06:00
hardiknai-techm
9d30dd5d0a Resolve Lint errors in AutoPilotUtils.ts (#488) 2021-03-08 17:09:46 -06:00
hardiknai-techm
4eb0dedddb Resolve Lint errors in quickstart.ts (#492) 2021-03-08 17:08:24 -06:00
Steve Faulkner
c844986c34 Move resourceToken test to portal runner sub (#499)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2021-03-08 16:35:20 -06:00
Steve Faulkner
4e702716bd More Flaky Test Improvements (#498) 2021-03-08 14:41:12 -06:00
Jordi Bunster
1d2995ef32 Fix eslint warnings (#456) 2021-03-08 10:29:41 -08:00
hardiknai-techm
ee919a68a5 Resolve Lint errors in definitions.ts (#494) 2021-03-08 11:32:45 -06:00
Steve Faulkner
2ec9df52aa Increase cleanup window to 60 minutes 2021-03-08 11:16:40 -06:00
Steve Faulkner
0ed9fe029d Fix Test DB cleanup conditional 2021-03-08 10:45:26 -06:00
Steve Faulkner
45af1d7cf9 Fix Test Database Cleanup Script (#497) 2021-03-08 10:14:31 -06:00
Srinath Narayanan
69975cd0e8 made public gallery the first tab (#483) 2021-03-05 09:03:48 -08:00
Steve Faulkner
c1141406ff End to End Test Improvements Round 2 (#475) 2021-03-04 22:49:52 -06:00
sunilyadav840
acb284eac7 Eslint/fix lint headers utility (#470) 2021-03-04 20:46:33 -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
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
222 changed files with 4554 additions and 5337 deletions

View File

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

View File

@@ -11,15 +11,9 @@ src/Common/CosmosClient.test.ts
src/Common/CosmosClient.ts
src/Common/DataAccessUtilityBase.test.ts
src/Common/DataAccessUtilityBase.ts
src/Common/DeleteFeedback.ts
src/Common/DocumentClientUtilityBase.ts
src/Common/EditableUtility.ts
src/Common/HashMap.test.ts
src/Common/HashMap.ts
src/Common/HeadersUtility.test.ts
src/Common/HeadersUtility.ts
src/Common/IteratorUtilities.test.ts
src/Common/IteratorUtilities.ts
src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts
@@ -30,7 +24,6 @@ src/Common/ObjectCache.test.ts
src/Common/ObjectCache.ts
src/Common/QueriesClient.ts
src/Common/Splitter.ts
src/Common/ThemeUtility.ts
src/Common/UrlUtility.ts
src/Config.ts
src/Contracts/ActionContracts.ts
@@ -58,8 +51,6 @@ src/Explorer/ComponentRegisterer.test.ts
src/Explorer/ComponentRegisterer.ts
src/Explorer/ContextMenuButtonFactory.ts
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts
src/Explorer/Controls/CommandButton/CommandButton.test.ts
src/Explorer/Controls/CommandButton/CommandButton.ts
src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts
src/Explorer/Controls/DynamicList/DynamicList.test.ts
src/Explorer/Controls/DynamicList/DynamicListComponent.ts
@@ -95,8 +86,6 @@ src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.ts
src/Explorer/Graph/GraphExplorerComponent/EdgeInfoCache.ts
src/Explorer/Graph/GraphExplorerComponent/GraphData.test.ts
src/Explorer/Graph/GraphExplorerComponent/GraphData.ts
src/Explorer/Graph/GraphExplorerComponent/GraphUtil.test.ts
src/Explorer/Graph/GraphExplorerComponent/GraphUtil.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
@@ -110,7 +99,6 @@ src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
src/Explorer/Menus/ContextMenu.ts
src/Explorer/MostRecentActivity/MostRecentActivity.ts
src/Explorer/Notebook/FileSystemUtil.ts
src/Explorer/Notebook/NTeractUtil.ts
src/Explorer/Notebook/NotebookClientV2.ts
src/Explorer/Notebook/NotebookComponent/NotebookContentProvider.ts
src/Explorer/Notebook/NotebookComponent/__mocks__/rx-jupyter.ts
@@ -162,7 +150,7 @@ src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
src/Explorer/Panes/UploadFilePane.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/DataTable/CacheBase.ts
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
@@ -170,7 +158,6 @@ src/Explorer/Tables/DataTable/DataTableBuilder.ts
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
src/Explorer/Tables/DataTable/DataTableOperations.ts
src/Explorer/Tables/DataTable/DataTableUtilities.ts
src/Explorer/Tables/DataTable/DataTableViewModel.ts
src/Explorer/Tables/DataTable/TableCommands.ts
src/Explorer/Tables/DataTable/TableEntityCache.ts
@@ -179,8 +166,6 @@ src/Explorer/Tables/Entities.ts
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/QueryBuilder/DateTimeUtilities.test.ts
src/Explorer/Tables/QueryBuilder/DateTimeUtilities.ts
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
src/Explorer/Tables/QueryBuilder/QueryViewModel.ts
@@ -200,7 +185,6 @@ src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/SparkMasterTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts
@@ -264,8 +248,6 @@ src/Shared/ExplorerSettings.ts
src/Shared/PriceEstimateCalculator.ts
src/Shared/StorageUtility.test.ts
src/Shared/StorageUtility.ts
src/Shared/StringUtility.test.ts
src/Shared/StringUtility.ts
src/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts
@@ -274,25 +256,14 @@ src/Terminal/NotebookAppContracts.d.ts
src/Terminal/index.ts
src/TokenProviders/PortalTokenProvider.ts
src/TokenProviders/TokenProviderFactory.ts
src/Utils/AuthorizationUtils.test.ts
src/Utils/AuthorizationUtils.ts
src/Utils/AutoPilotUtils.test.ts
src/Utils/AutoPilotUtils.ts
src/Utils/DatabaseAccountUtils.test.ts
src/Utils/DatabaseAccountUtils.ts
src/Utils/JunoUtils.ts
src/Utils/MessageValidation.ts
src/Utils/NotebookConfigurationUtils.ts
src/Utils/PricingUtils.test.ts
src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts
src/Utils/StringUtils.test.ts
src/Utils/StringUtils.ts
src/applyExplorerBindings.ts
src/global.d.ts
src/quickstart.ts
src/setupTests.ts
src/workers/upload/definitions.ts
src/workers/upload/index.ts
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
src/Explorer/Controls/Accordion/AccordionComponent.tsx
@@ -339,15 +310,7 @@ src/Explorer/Graph/GraphExplorerComponent/QueryContainerComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.test.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Menus/CommandBar/MemoryTrackerComponent.tsx
src/Explorer/Menus/NavBar/ControlBarComponent.tsx
src/Explorer/Menus/NavBar/ControlBarComponentAdapter.tsx
src/Explorer/Menus/NavBar/MeControlComponent.test.tsx
src/Explorer/Menus/NavBar/MeControlComponent.tsx
src/Explorer/Menus/NavBar/MeControlComponentAdapter.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponentAdapter.tsx
@@ -377,8 +340,7 @@ src/Explorer/Notebook/temp/inputs/editor.tsx
src/Explorer/Notebook/temp/markdown-cell.tsx
src/Explorer/Notebook/temp/source.tsx
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
src/Explorer/SplashScreen/SplashScreenComponent.tsx
src/Explorer/SplashScreen/SplashScreenComponentApdapter.tsx
src/Explorer/SplashScreen/SplashScreen.tsx
src/Explorer/Tabs/GalleryTab.tsx
src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx

View File

@@ -15,10 +15,10 @@ jobs:
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: node utils/codeMetrics.js
env:
@@ -28,10 +28,10 @@ jobs:
name: "Compile TypeScript"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: npm run compile
- run: npm run compile:strict
@@ -40,10 +40,10 @@ jobs:
name: "Check Format"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: npm run format:check
lint:
@@ -51,10 +51,10 @@ jobs:
name: "Lint"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: npm run lint
unittest:
@@ -62,10 +62,10 @@ jobs:
name: "Unit Tests"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: npm run test
build:
@@ -74,10 +74,10 @@ jobs:
name: "Build"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- run: npm run build:contracts
- name: Restore Build Cache
@@ -94,14 +94,14 @@ jobs:
path: dist/
endtoendemulator:
name: "End To End Emulator Tests"
needs: [lint, format, compile, unittest]
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- uses: southpolesteve/cosmos-emulator-github-action@v1
- name: End to End Tests
run: |
@@ -125,10 +125,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
@@ -143,48 +143,72 @@ jobs:
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendhosted:
name: "End to End Hosted Tests"
needs: [lint, format, compile, unittest]
name: "End to End Tests"
needs: [cleanupaccounts]
runs-on: ubuntu-latest
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
strategy:
fail-fast: false
matrix:
test-file:
- ./test/cassandra/container.spec.ts
- ./test/mongo/mongoIndexPolicy.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 12.x
- name: Use Node.js 14.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
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
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshots
path: failed-*
cleanupaccounts:
name: "Cleanup Test Database Accounts"
runs-on: ubuntu-latest
env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
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:
name: Publish Nuget
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
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -200,7 +224,7 @@ jobs:
- run: cp ./configs/prod.json config.json
- 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 push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
@@ -208,7 +232,7 @@ jobs:
nugetmpac:
name: Publish Nuget MPAC
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
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -225,7 +249,7 @@ jobs:
- 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 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
name: packages
with:

43
.vscode/settings.json vendored
View File

@@ -1,21 +1,26 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
".vs": true,
".vscode/**": true,
"*.trx": true,
"**/.DS_Store": true,
"**/.git": true,
"**/.hg": true,
"**/.svn": true,
"built/**": true,
"coverage/**": true,
"libs/**": true,
"node_modules/**": true,
"package-lock.json": true,
"quickstart/**": true,
"test/out/**": true,
"workers/libs/**": true
},
"typescript.tsdk": "node_modules/typescript/lib"
}
"files.exclude": {
".vs": true,
".vscode/**": true,
"*.trx": true,
"**/.DS_Store": true,
"**/.git": true,
"**/.hg": true,
"**/.svn": true,
"built/**": true,
"coverage/**": true,
"libs/**": true,
"node_modules/**": true,
"package-lock.json": true,
"quickstart/**": true,
"test/out/**": true,
"workers/libs/**": true
},
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
}
}

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

@@ -1,6 +1,6 @@
# Contribution guidelines to Data Explorer
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
@@ -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.
## Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
@@ -20,33 +21,3 @@ Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- 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)

4
config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"GITHUB_CLIENT_ID": "167ea4b09801db1de03d",
"GITHUB_CLIENT_SECRET": "e7bb10a3a8da428815805c6fc483560a035a73c1"
}

View File

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

133
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": {
"version": "2.1.0",
"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"
}
},
"@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": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.7.2.tgz",
@@ -698,12 +713,20 @@
}
},
"@babel/plugin-syntax-class-properties": {
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz",
"integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==",
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.4"
"@babel/helper-plugin-utils": "^7.12.13"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
}
}
},
"@babel/plugin-syntax-decorators": {
@@ -1672,9 +1695,9 @@
}
},
"@istanbuljs/schema": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true
},
"@jest/console": {
@@ -1814,9 +1837,9 @@
}
},
"@types/yargs": {
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
"version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -4431,9 +4454,9 @@
}
},
"@types/graceful-fs": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
"integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -5641,6 +5664,38 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
"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": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -6059,7 +6114,6 @@
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
}
@@ -7288,6 +7342,11 @@
"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": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -8520,6 +8579,11 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
"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": {
"version": "1.8.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.19.tgz",
@@ -8536,6 +8600,13 @@
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"decamelize": {
@@ -12843,9 +12914,9 @@
}
},
"@types/yargs": {
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
"version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -13098,9 +13169,9 @@
}
},
"fsevents": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
@@ -13886,9 +13957,9 @@
}
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
@@ -16025,9 +16096,9 @@
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"msal": {
"version": "1.4.4",
@@ -22579,6 +22650,16 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"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": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz",

View File

@@ -8,6 +8,7 @@
"@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@jupyterlab/services": "6.0.2",
@@ -49,6 +50,7 @@
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2",
"css-element-queries": "1.1.1",
@@ -75,6 +77,7 @@
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.18.1",
"ms": "2.1.3",
"msal": "1.4.4",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.134.1",

View File

@@ -3,6 +3,7 @@
"offerThroughput": 400,
"databaseLevelThroughput": false,
"collectionId": "Persons",
"createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [
"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 enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl";
@@ -130,7 +128,6 @@ export class Flights {
public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest";
public static readonly GalleryPublish = "gallerypublish";
}
export class AfecFeatures {

View File

@@ -1,10 +1,10 @@
import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext";
import { getErrorMessage } from "./ErrorHandlingUtils";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { userContext } from "../UserContext";
import { getErrorMessage } from "./ErrorHandlingUtils";
const _global = typeof self === "undefined" ? window : self;

View File

@@ -1,8 +1,9 @@
import { ARMError } from "../Utils/arm/request";
import { HttpStatusCodes } from "./Constants";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { SubscriptionType } from "../Contracts/SubscriptionType";
import { userContext } from "../UserContext";
import { ARMError } from "../Utils/arm/request";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { HttpStatusCodes } from "./Constants";
import { logError } from "./Logger";
import { sendMessage } from "./MessageHandler";
@@ -44,7 +45,7 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
const replaceKnownError = (errorMessage: string): string => {
if (
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
userContext.subscriptionType === SubscriptionType.Internal &&
errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";

View File

@@ -1,28 +1,5 @@
import * as Constants from "./Constants";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
// x-ms-resource-quota: databases = 100; collections = 5000; users = 500000; permissions = 2000000;
export function getQuota(responseHeaders: any): any {
return responseHeaders && responseHeaders[Constants.HttpHeaders.resourceQuota]
? parseStringIntoObject(responseHeaders[Constants.HttpHeaders.resourceQuota])
: null;
}
export function shouldEnableCrossPartitionKey(): boolean {
return LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true";
}
function parseStringIntoObject(resourceString: string) {
var entityObject: any = {};
if (resourceString) {
var entitiesArray: string[] = resourceString.split(";");
for (var i: any = 0; i < entitiesArray.length; i++) {
var entity: string[] = entitiesArray[i].split("=");
entityObject[entity[0]] = entity[1];
}
}
return entityObject;
}

View File

@@ -1,6 +1,8 @@
import { QueryResults } from "../Contracts/ViewModels";
interface QueryResponse {
// [Todo] remove any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resources: any[];
hasMoreResults: boolean;
activityId: string;
@@ -16,6 +18,7 @@ export interface MinimalQueryIterator {
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
return documentsIterator.fetchNext().then((response) => {
const documents = response.resources;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any
const itemCount = (documents && documents.length) || 0;
return {

View File

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

View File

@@ -20,7 +20,7 @@ const defaultHeaders = {
};
function authHeaders() {
if (window.authType === AuthType.EncryptedToken) {
if (userContext.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: userContext.accessToken };
} else {
return { [HttpHeaders.authorization]: userContext.authorizationToken };
@@ -337,7 +337,7 @@ export function createMongoCollectionWithProxy(
export function getEndpoint(): string {
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");
}
return url;

View File

@@ -2,18 +2,16 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
export default class ThemeUtility {
public static getMonacoTheme(theme: string): string {
switch (theme) {
case "default":
case "hc-white":
return "vs";
case "dark":
return "vs-dark";
case "hc-black":
return "hc-black";
default:
return "vs";
}
export function getMonacoTheme(theme: string): string {
switch (theme) {
case "default":
case "hc-white":
return "vs";
case "dark":
return "vs-dark";
case "hc-black":
return "hc-black";
default:
return "vs";
}
}

View File

@@ -27,13 +27,17 @@ describe("createCollection", () => {
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
updateUserContext({
authType: AuthType.AAD,
});
await createCollection(createCollectionParams);
expect(armRequest).toHaveBeenCalled();
});
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({
databases: {
createIfNotExists: () => {

View File

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

View File

@@ -34,7 +34,7 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
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)
: createDatabaseWithSDK(params));

View File

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

View File

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

View File

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

View File

@@ -20,13 +20,17 @@ describe("deleteCollection", () => {
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
updateUserContext({
authType: AuthType.AAD,
});
await deleteCollection("database", "collection");
expect(armRequest).toHaveBeenCalled();
});
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({
database: () => {
return {

View File

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

View File

@@ -16,7 +16,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
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);
} else {
await client().database(databaseId).delete();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,13 +19,17 @@ describe("readCollections", () => {
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
updateUserContext({
authType: AuthType.AAD,
});
await readCollections("database");
expect(armRequest).toHaveBeenCalled();
});
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({
database: () => {
return {

View File

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

View File

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

View File

@@ -19,13 +19,17 @@ describe("readDatabases", () => {
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
updateUserContext({
authType: AuthType.AAD,
});
await readDatabases();
expect(armRequest).toHaveBeenCalled();
});
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({
databases: {
readAll: () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ export async function updateCollection(
try {
if (
window.authType === AuthType.AAD &&
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
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}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ export async function updateUserDefinedFunction(
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try {
if (
window.authType === AuthType.AAD &&
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
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.
hostedExplorerURL: string;
armAPIVersion?: string;
ENABLE_GALLERY_PUBLISH?: boolean;
}
// Default configuration

View File

@@ -9,10 +9,10 @@ export interface DatabaseAccount {
}
export interface DatabaseAccountExtendedProperties {
documentEndpoint: string;
tableEndpoint: string;
gremlinEndpoint: string;
cassandraEndpoint: string;
documentEndpoint?: string;
tableEndpoint?: string;
gremlinEndpoint?: string;
cassandraEndpoint?: string;
configurationOverrides?: ConfigurationOverrides;
capabilities?: Capability[];
enableMultipleWriteLocations?: boolean;

View File

@@ -0,0 +1,9 @@
/**
* Messaging types used with SelfServe Component <-> Portal communication
* and Hosted <-> SelfServe Component communication
*/
export enum SelfServeMessageTypes {
TelemetryInfo = "TelemetryInfo",
Notification = "Notification",
}

View File

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

View File

@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
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", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
@@ -69,10 +65,6 @@ describe("Component Registerer", () => {
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", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});

View File

@@ -1,7 +1,6 @@
import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
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("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent());
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("notebookv2-tab", new TabComponents.NotebookV2Tab());
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("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("settings-pane", new PaneComponents.SettingsPaneComponent());
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("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
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,4 +1,4 @@
import { StringUtils } from "../../../Utils/StringUtils";
import * as StringUtils from "../../../Utils/StringUtils";
import { KeyCodes } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";

View File

@@ -1,5 +1,5 @@
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 { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
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_SUBTEXT_FONT_SIZE = "15px";
export class DialogComponent extends React.Component<DialogProps, {}> {
export class Dialog extends React.Component<DialogProps> {
constructor(props: DialogProps) {
super(props);
}
@@ -91,7 +91,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
: undefined;
return (
<Dialog {...dialogProps}>
<FluentDialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
@@ -104,7 +104,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
<PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</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.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", 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.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.enablefixedcollectionwithsharedthroughput",

View File

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

View File

@@ -74,8 +74,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
private onAddRepoButtonClick = async (): Promise<void> => {
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,
});
let enteredUrl = this.state.textFieldValue;
@@ -105,8 +103,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
TelemetryProcessor.traceSuccess(
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,
},
startKey
@@ -121,8 +117,6 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
TelemetryProcessor.traceFailure(
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,
error: AddRepoComponent.TextFieldErrorMessage,
},

View File

@@ -4,7 +4,7 @@
import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels";
import { StringUtils } from "../../../Utils/StringUtils";
import * as StringUtils from "../../../Utils/StringUtils";
import { userContext } from "../../../UserContext";
import { TerminalQueryParams } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";

View File

@@ -13,6 +13,8 @@ import {
LinkBase,
Separator,
TooltipHost,
Spinner,
SpinnerSize,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient";
@@ -29,10 +31,14 @@ export interface GalleryCardComponentProps {
onFavoriteClick: () => void;
onUnfavoriteClick: () => 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;
private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
@@ -40,6 +46,14 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private static readonly cardDescriptionMaxChars = 80;
private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
constructor(props: GalleryCardComponentProps) {
super(props);
this.state = {
isDeletingPublishedNotebook: false,
};
}
public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
@@ -59,91 +73,110 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)}
>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
{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 }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Item>
<Image
src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Item>
<Image
src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap>
{this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: 36 } }}>
{this.renderTruncatedDescription()}
</Text>
<span>
{this.props.data.views !== undefined && this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap>
{this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
</span>
</Card.Section>
<Text variant="small" styles={{ root: { height: 36 } }}>
{this.renderTruncatedDescription()}
</Text>
<span>
{this.props.data.views !== undefined &&
this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
this.props.onDeleteClick(
() => this.setState({ isDeletingPublishedNotebook: true }),
() => this.setState({ isDeletingPublishedNotebook: false })
)
)}
</span>
</Card.Section>
)}
</>
)}
</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`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, startKey);
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
this.props.onAcceptCodeOfConduct(response.data);
} catch (error) {

View File

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

View File

@@ -11,7 +11,7 @@
.publicGalleryTabContainer {
position: relative;
height: 100vh;
min-height: 100vh;
}
.publicGalleryTabOverlayContent {

View File

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

View File

@@ -23,7 +23,7 @@ import {
import * as React from "react";
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { Dialog, DialogProps } from "../Dialog";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import "./GalleryViewerComponent.less";
import { HttpStatusCodes } from "../../../Common/Constants";
@@ -36,7 +36,6 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
export interface GalleryViewerComponentProps {
container?: Explorer;
isGalleryPublishEnabled: boolean;
junoClient: JunoClient;
selectedTab: GalleryTab;
sortBy: SortBy;
@@ -48,8 +47,8 @@ export interface GalleryViewerComponentProps {
}
export enum GalleryTab {
OfficialSamples,
PublicGallery,
OfficialSamples,
Favorites,
Published,
}
@@ -140,35 +139,28 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
text: GalleryViewerComponent.mostRecentText,
},
];
if (this.props.container?.isGalleryPublishEnabled()) {
this.sortingOptions.push({
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText,
});
}
this.sortingOptions.push({
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText,
});
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 {
this.traceViewGallery();
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
const tabs: GalleryTabInfo[] = [
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
),
this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks),
];
if (this.props.isGalleryPublishEnabled) {
tabs.push(
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
)
);
}
if (this.props.container?.isGalleryPublishEnabled()) {
if (this.props.container) {
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
}
@@ -196,7 +188,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<div className="galleryContainer">
<Pivot {...pivotProps}>{pivotItems}</Pivot>
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
{this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</div>
);
}
@@ -208,13 +200,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}
switch (this.state.selectedTab) {
case GalleryTab.OfficialSamples:
if (!this.viewOfficialSamplesTraced) {
this.resetViewGalleryTabTracedFlags();
this.viewOfficialSamplesTraced = true;
trace(Action.NotebooksGalleryViewOfficialSamples);
}
break;
case GalleryTab.PublicGallery:
if (!this.viewPublicGalleryTraced) {
this.resetViewGalleryTabTracedFlags();
@@ -222,6 +207,13 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
trace(Action.NotebooksGalleryViewPublicGallery);
}
break;
case GalleryTab.OfficialSamples:
if (!this.viewOfficialSamplesTraced) {
this.resetViewGalleryTabTracedFlags();
this.viewOfficialSamplesTraced = true;
trace(Action.NotebooksGalleryViewOfficialSamples);
}
break;
case GalleryTab.Favorites:
if (!this.viewFavoritesTraced) {
this.resetViewGalleryTabTracedFlags();
@@ -396,7 +388,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createSearchBarHeader(content: JSX.Element): JSX.Element {
return (
<Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
<Stack horizontal wrap tokens={{ childrenGap: 20, padding: 10 }}>
<Stack.Item grow>
<SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} />
</Stack.Item>
@@ -406,11 +398,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item>
{this.props.isGalleryPublishEnabled && (
<Stack.Item>
<InfoComponent />
</Stack.Item>
)}
<Stack.Item>
<InfoComponent />
</Stack.Item>
</Stack>
<Stack.Item>{content}</Stack.Item>
</Stack>
@@ -453,14 +443,14 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
switch (tab) {
case GalleryTab.OfficialSamples:
this.loadSampleNotebooks(searchText, sortBy, offline);
break;
case GalleryTab.PublicGallery:
this.loadPublicNotebooks(searchText, sortBy, offline);
break;
case GalleryTab.OfficialSamples:
this.loadSampleNotebooks(searchText, sortBy, offline);
break;
case GalleryTab.Favorites:
this.loadFavoriteNotebooks(searchText, sortBy, offline);
break;
@@ -664,10 +654,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
};
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
let isFavorite: boolean;
if (this.props.container?.isGalleryPublishEnabled()) {
isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
}
const isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
const props: GalleryCardComponentProps = {
data,
isFavorite,
@@ -678,7 +665,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data),
onDownloadClick: () => this.downloadItem(data),
onDeleteClick: () => this.deleteItem(data),
onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) =>
this.deleteItem(data, beforeDelete, afterDelete),
};
return (
@@ -722,11 +710,18 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
);
};
private deleteItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => {
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
this.refreshSelectedTab(item);
});
private deleteItem = async (data: IGalleryItem, beforeDelete: () => void, afterDelete: () => void): Promise<void> => {
GalleryUtils.deleteItem(
this.props.container,
this.props.junoClient,
data,
(item) => {
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
this.refreshSelectedTab(item);
},
beforeDelete,
afterDelete
);
};
private onPivotChange = (item: PivotItem): void => {

View File

@@ -8,6 +8,95 @@ exports[`GalleryViewerComponent renders 1`] = `
onLinkClick={[Function]}
selectedKey="OfficialSamples"
>
<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,
}
}
wrap={true}
>
<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>
<PivotItem
headerText="Official samples"
itemKey="OfficialSamples"
@@ -33,6 +122,7 @@ exports[`GalleryViewerComponent renders 1`] = `
"padding": 10,
}
}
wrap={true}
>
<StackItem
grow={true}
@@ -72,11 +162,18 @@ exports[`GalleryViewerComponent renders 1`] = `
"key": 3,
"text": "Most recent",
},
Object {
"key": 2,
"text": "Most favorited",
},
]
}
selectedKey={0}
/>
</StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack>
<StackItem>
<StyledSpinnerBase

View File

@@ -11,11 +11,10 @@ import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent";
import { Dialog, DialogProps, TextFieldProps } from "../Dialog";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer";
import { NotebookV4 } from "@nteract/commutable/lib/v4";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { DialogHost } from "../../../Utils/GalleryUtils";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
@@ -103,7 +102,7 @@ export class NotebookViewerComponent
);
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.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 {
return (
<div className="notebookViewerContainer">
@@ -179,7 +167,7 @@ export class NotebookViewerComponent
hidePrompts: this.props.hidePrompts,
})}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
{this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
</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?")) {
const container = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(),
});
@@ -231,8 +229,6 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
TelemetryProcessor.traceSuccess(
Action.DeleteSavedQuery,
{
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(),
},
@@ -242,8 +238,6 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
TelemetryProcessor.traceFailure(
Action.DeleteSavedQuery,
{
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title(),
error: getErrorMessage(error),

View File

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

View File

@@ -28,16 +28,11 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function],
"container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -440,22 +435,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [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 {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -695,9 +674,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -804,6 +780,7 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -859,9 +836,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -951,11 +925,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -965,8 +937,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
@@ -992,13 +962,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -1021,7 +984,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -1051,24 +1014,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [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],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -1115,14 +1060,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"selfServeComponentAdapter": SelfServeComponentAdapter {
"container": [Circular],
"parameters": [Function],
},
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
"parameters": [Function],
},
"selfServeType": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
@@ -1164,22 +1101,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -1237,9 +1160,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -1309,16 +1229,11 @@ exports[`SettingsComponent renders 1`] = `
}
container={
Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -1721,22 +1636,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [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 {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -1976,9 +1875,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -2085,6 +1981,7 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -2140,9 +2037,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -2232,11 +2126,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -2246,8 +2138,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
@@ -2273,13 +2163,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -2302,7 +2185,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -2332,24 +2215,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [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],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -2396,14 +2261,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"selfServeComponentAdapter": SelfServeComponentAdapter {
"container": [Circular],
"parameters": [Function],
},
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
"parameters": [Function],
},
"selfServeType": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
@@ -2445,22 +2302,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -2518,9 +2361,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -2603,16 +2443,11 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function],
"container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -3015,22 +2850,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [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 {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -3270,9 +3089,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -3379,6 +3195,7 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -3434,9 +3251,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -3526,11 +3340,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -3540,8 +3352,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
@@ -3567,13 +3377,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -3596,7 +3399,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -3626,24 +3429,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [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],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -3690,14 +3475,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"selfServeComponentAdapter": SelfServeComponentAdapter {
"container": [Circular],
"parameters": [Function],
},
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
"parameters": [Function],
},
"selfServeType": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
@@ -3739,22 +3516,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -3812,9 +3575,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -3884,16 +3644,11 @@ exports[`SettingsComponent renders 1`] = `
}
container={
Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -4296,22 +4051,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [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 {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -4551,9 +4290,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -4660,6 +4396,7 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -4715,9 +4452,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -4807,11 +4541,9 @@ exports[`SettingsComponent renders 1`] = `
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -4821,8 +4553,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
@@ -4848,13 +4578,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -4877,7 +4600,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"onToggleKeyDown": [Function],
"openDialog": undefined,
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -4907,24 +4630,6 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [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],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -4971,14 +4676,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"selfServeComponentAdapter": SelfServeComponentAdapter {
"container": [Circular],
"parameters": [Function],
},
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
"parameters": [Function],
},
"selfServeType": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
@@ -5020,22 +4717,8 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -5093,9 +4776,6 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],

View File

@@ -1,7 +1,7 @@
import React from "react";
import { shallow } from "enzyme";
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
import { NumberUiType, SmartUiInput } from "../../../SelfServe/SelfServeTypes";
import { NumberUiType, SmartUiInput, DescriptionType } from "../../../SelfServe/SelfServeTypes";
describe("SmartUiComponent", () => {
const exampleData: SmartUiDescriptor = {
@@ -18,10 +18,12 @@ describe("SmartUiComponent", () => {
{
id: "description",
input: {
labelTKey: undefined,
dataFieldName: "description",
type: "string",
description: {
textTKey: "this is an example description text.",
type: DescriptionType.Text,
link: {
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction",
textTKey: "Click here for more information.",

View File

@@ -6,12 +6,13 @@ import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Text } from "office-ui-fabric-react/lib/Text";
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
import { Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import { Label, Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
import {
ChoiceItem,
Description,
DescriptionType,
Info,
InputType,
InputTypeValue,
@@ -19,6 +20,7 @@ import {
SmartUiInput,
} from "../../../SelfServe/SelfServeTypes";
import { TFunction } from "i18next";
import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent";
/**
* Generic UX renderer
@@ -29,15 +31,14 @@ import { TFunction } from "i18next";
*/
interface BaseDisplay {
labelTKey: string;
dataFieldName: string;
errorMessage?: string;
type: InputTypeValue;
}
interface BaseInput extends BaseDisplay {
labelTKey: string;
placeholderTKey?: string;
errorMessage?: string;
}
/**
@@ -67,7 +68,8 @@ interface ChoiceInput extends BaseInput {
}
interface DescriptionDisplay extends BaseDisplay {
description: Description;
description?: Description;
isDynamicDescription?: boolean;
}
type AnyDisplay = NumberInput | BooleanInput | StringInput | ChoiceInput | DescriptionDisplay;
@@ -123,25 +125,27 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private renderInfo(info: Info): JSX.Element {
return (
<MessageBar styles={{ root: { width: 400 } }}>
{this.props.getTranslation(info.messageTKey)}
{info.link && (
<Link href={info.link.href} target="_blank">
{this.props.getTranslation(info.link.textTKey)}
</Link>
)}
</MessageBar>
info && (
<Text>
{this.props.getTranslation(info.messageTKey)}{" "}
{info.link && (
<Link href={info.link.href} target="_blank">
{this.props.getTranslation(info.link.textTKey)}
</Link>
)}
</Text>
)
);
}
private renderTextInput(input: StringInput): JSX.Element {
private renderTextInput(input: StringInput, labelId: string): JSX.Element {
const value = this.props.currentValues.get(input.dataFieldName)?.value as string;
const disabled = this.props.disabled || this.props.currentValues.get(input.dataFieldName)?.disabled;
return (
<div className="stringInputContainer">
<TextField
id={`${input.dataFieldName}-textField-input`}
label={this.props.getTranslation(input.labelTKey)}
aria-labelledby={labelId}
type="text"
value={value || ""}
placeholder={this.props.getTranslation(input.placeholderTKey)}
@@ -149,32 +153,35 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
onChange={(_, newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
subComponentStyles: {
label: {
root: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
},
},
}}
/>
</div>
);
}
private renderDescription(input: DescriptionDisplay): JSX.Element {
const description = input.description;
return (
<Text id={`${input.dataFieldName}-text-display`}>
{this.props.getTranslation(input.description.textTKey)}{" "}
private renderDescription(input: DescriptionDisplay, labelId: string): JSX.Element {
const dataFieldName = input.dataFieldName;
const description = input.description || (this.props.currentValues.get(dataFieldName)?.value as Description);
if (!description) {
return this.renderError("Description is not provided.");
}
const descriptionElement = (
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
{this.props.getTranslation(description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={input.description.link.href}>
{this.props.getTranslation(input.description.link.textTKey)}
<Link target="_blank" href={description.link.href}>
{this.props.getTranslation(description.link.textTKey)}
</Link>
)}
</Text>
);
if (description.type === DescriptionType.Text) {
return descriptionElement;
}
const messageBarType =
description.type === DescriptionType.InfoMessageBar ? MessageBarType.info : MessageBarType.warning;
return <MessageBar messageBarType={messageBarType}>{descriptionElement}</MessageBar>;
}
private clearError(dataFieldName: string): void {
@@ -220,13 +227,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return undefined;
};
private renderNumberInput(input: NumberInput): JSX.Element {
private renderNumberInput(input: NumberInput, labelId: string): JSX.Element {
const { labelTKey, min, max, dataFieldName, step } = input;
const props = {
label: this.props.getTranslation(labelTKey),
min: min,
max: max,
ariaLabel: labelTKey,
ariaLabel: this.props.getTranslation(labelTKey),
step: step,
};
@@ -243,13 +249,8 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top}
aria-labelledby={labelId}
disabled={disabled}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
}}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
@@ -266,10 +267,6 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
titleLabel: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
valueLabel: SmartUiComponent.labelStyle,
}}
/>
@@ -280,13 +277,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
}
}
private renderBooleanInput(input: BooleanInput): JSX.Element {
private renderBooleanInput(input: BooleanInput, labelId: string): JSX.Element {
const value = this.props.currentValues.get(input.dataFieldName)?.value as boolean;
const disabled = this.props.disabled || this.props.currentValues.get(input.dataFieldName)?.disabled;
return (
<Toggle
id={`${input.dataFieldName}-toggle-input`}
label={this.props.getTranslation(input.labelTKey)}
aria-labelledby={labelId}
checked={value || false}
onText={this.props.getTranslation(input.trueLabelTKey)}
offText={this.props.getTranslation(input.falseLabelTKey)}
@@ -297,8 +294,8 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
);
}
private renderChoiceInput(input: ChoiceInput): JSX.Element {
const { labelTKey, defaultKey, dataFieldName, choices, placeholderTKey } = input;
private renderChoiceInput(input: ChoiceInput, labelId: string): JSX.Element {
const { defaultKey, dataFieldName, choices, placeholderTKey } = input;
const value = this.props.currentValues.get(dataFieldName)?.value as string;
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
let selectedKey = value ? value : defaultKey;
@@ -308,7 +305,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return (
<Dropdown
id={`${input.dataFieldName}-dropdown-input`}
label={this.props.getTranslation(labelTKey)}
aria-labelledby={labelId}
selectedKey={selectedKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={this.props.getTranslation(placeholderTKey)}
@@ -319,40 +316,53 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
}))}
styles={{
root: { width: 400 },
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
dropdown: SmartUiComponent.labelStyle,
}}
/>
);
}
private renderError(input: AnyDisplay): JSX.Element {
return <MessageBar messageBarType={MessageBarType.error}>Error: {input.errorMessage}</MessageBar>;
private renderError(errorMessage: string): JSX.Element {
return <MessageBar messageBarType={MessageBarType.error}>Error: {errorMessage}</MessageBar>;
}
private renderDisplay(input: AnyDisplay): JSX.Element {
private renderDisplayWithInfoBubble(input: AnyDisplay, info: Info): JSX.Element {
if (input.errorMessage) {
return this.renderError(input);
return this.renderError(input.errorMessage);
}
const inputHidden = this.props.currentValues.get(input.dataFieldName)?.hidden;
if (inputHidden) {
return <></>;
}
const labelId = `${input.dataFieldName}-label`;
return (
<Stack>
{input.labelTKey && (
<Label id={labelId}>
<ToolTipLabelComponent
label={this.props.getTranslation(input.labelTKey)}
toolTipElement={this.renderInfo(info)}
/>
</Label>
)}
{this.renderDisplay(input, labelId)}
</Stack>
);
}
private renderDisplay(input: AnyDisplay, labelId: string): JSX.Element {
switch (input.type) {
case "string":
if ("description" in input) {
return this.renderDescription(input as DescriptionDisplay);
if ("description" in input || "isDynamicDescription" in input) {
return this.renderDescription(input as DescriptionDisplay, labelId);
}
return this.renderTextInput(input as StringInput);
return this.renderTextInput(input as StringInput, labelId);
case "number":
return this.renderNumberInput(input as NumberInput);
return this.renderNumberInput(input as NumberInput, labelId);
case "boolean":
return this.renderBooleanInput(input as BooleanInput);
return this.renderBooleanInput(input as BooleanInput, labelId);
case "object":
return this.renderChoiceInput(input as ChoiceInput);
return this.renderChoiceInput(input as ChoiceInput, labelId);
default:
throw new Error(`Unknown input type: ${input.type}`);
}
@@ -363,10 +373,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
<Stack.Item>
{node.info && this.renderInfo(node.info as Info)}
{node.input && this.renderDisplay(node.input)}
</Stack.Item>
<Stack.Item>{node.input && this.renderDisplayWithInfoBubble(node.input, node.info as Info)}</Stack.Item>
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack>
);

View File

@@ -9,25 +9,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
}
>
<StackItem>
<StyledMessageBarBase
styles={
Object {
"root": Object {
"width": 400,
},
}
}
>
Start at $24/mo per database
<StyledLinkBase
href="https://aka.ms/azure-cosmos-db-pricing"
target="_blank"
>
More Details
</StyledLinkBase>
</StyledMessageBarBase>
</StackItem>
<StackItem />
<div
key="description"
>
@@ -40,18 +22,21 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<Text
id="description-text-display"
>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
Click here for more information.
</StyledLinkBase>
</Text>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
>
Click here for more information.
</StyledLinkBase>
</Text>
</Stack>
</StackItem>
</Stack>
</div>
@@ -67,53 +52,53 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label="Throughput (input)"
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
"root": Object {
"width": 400,
},
}
}
/>
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -130,37 +115,39 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
disabled={true}
label="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"titleLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
disabled={true}
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
}
}
}
/>
</div>
/>
</div>
</Stack>
</StackItem>
</Stack>
</div>
@@ -197,35 +184,34 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<div
className="stringInputContainer"
>
<StyledTextFieldBase
disabled={true}
id="containerId-textField-input"
label="Container id"
onChange={[Function]}
styles={
Object {
"root": Object {
"width": 400,
},
"subComponentStyles": Object {
"label": Object {
"root": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<div
className="stringInputContainer"
>
<StyledTextFieldBase
aria-labelledby="containerId-label"
disabled={true}
id="containerId-textField-input"
onChange={[Function]}
styles={
Object {
"root": Object {
"width": 400,
},
},
}
}
}
type="text"
value=""
/>
</div>
type="text"
value=""
/>
</div>
</Stack>
</StackItem>
</Stack>
</div>
@@ -241,22 +227,31 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<StyledToggleBase
checked={false}
disabled={true}
id="analyticalStore-toggle-input"
label="Analytical Store"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
disabled={true}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
/>
/>
</Stack>
</StackItem>
</Stack>
</div>
@@ -272,47 +267,50 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<StyledWithResponsiveMode
disabled={true}
id="database-dropdown-input"
label="Database"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
disabled={true}
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
}
/>
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
}
}
/>
</Stack>
</StackItem>
</Stack>
</div>
@@ -328,25 +326,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
}
>
<StackItem>
<StyledMessageBarBase
styles={
Object {
"root": Object {
"width": 400,
},
}
}
>
Start at $24/mo per database
<StyledLinkBase
href="https://aka.ms/azure-cosmos-db-pricing"
target="_blank"
>
More Details
</StyledLinkBase>
</StyledMessageBarBase>
</StackItem>
<StackItem />
<div
key="description"
>
@@ -359,18 +339,21 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<Text
id="description-text-display"
>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
Click here for more information.
</StyledLinkBase>
</Text>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
>
Click here for more information.
</StyledLinkBase>
</Text>
</Stack>
</StackItem>
</Stack>
</div>
@@ -386,53 +369,53 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label="Throughput (input)"
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
"root": Object {
"width": 400,
},
}
}
/>
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -449,36 +432,38 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
label="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"titleLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
}
}
}
/>
</div>
/>
</div>
</Stack>
</StackItem>
</Stack>
</div>
@@ -515,34 +500,33 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<div
className="stringInputContainer"
>
<StyledTextFieldBase
id="containerId-textField-input"
label="Container id"
onChange={[Function]}
styles={
Object {
"root": Object {
"width": 400,
},
"subComponentStyles": Object {
"label": Object {
"root": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<div
className="stringInputContainer"
>
<StyledTextFieldBase
aria-labelledby="containerId-label"
id="containerId-textField-input"
onChange={[Function]}
styles={
Object {
"root": Object {
"width": 400,
},
},
}
}
}
type="text"
value=""
/>
</div>
type="text"
value=""
/>
</div>
</Stack>
</StackItem>
</Stack>
</div>
@@ -558,21 +542,30 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<StyledToggleBase
checked={false}
id="analyticalStore-toggle-input"
label="Analytical Store"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
/>
/>
</Stack>
</StackItem>
</Stack>
</div>
@@ -588,46 +581,49 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<StyledWithResponsiveMode
id="database-dropdown-input"
label="Database"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
}
/>
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
}
}
/>
</Stack>
</StackItem>
</Stack>
</div>

View File

@@ -129,7 +129,6 @@ export interface ThroughputInputParams {
throughputModeRadioName: string;
maxAutoPilotThroughputSet: ViewModels.Editable<number>;
autoPilotUsageCost: ko.Computed<string>;
showAutoPilot?: ko.Observable<boolean>;
overrideWithAutoPilotSettings: ko.Observable<boolean>;
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
freeTierExceedThroughputTooltip?: ko.Observable<string>;
@@ -158,7 +157,6 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
public infoBubbleText: string | ko.Observable<string>;
public label: ko.Observable<string>;
public isFixed: boolean;
public showAutoPilot: ko.Observable<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>;
public throughputAutoPilotRadioId: string;
public throughputProvisionedRadioId: string;
@@ -202,14 +200,10 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
this.isFixed = !!options.isFixed;
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
this.label = options.label || ko.observable<string>();
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
this.isAutoPilotSelected.subscribe((value) => {
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
databaseAccountName: userContext.databaseAccount?.name,
subscriptionId: userContext.subscriptionId,
apiKind: userContext.defaultExperience,
dataExplorerArea: "Scale Tab V1",
});
});

View File

@@ -17,7 +17,7 @@
</div>
<!-- ko if: !isFixed -->
<div data-bind="visible: showAutoPilot" class="throughputModeContainer">
<div class="throughputModeContainer">
<input
class="throughputModeRadio"
aria-label="Autopilot mode"

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
import * as React from "react";
import { NeighborVertexBasicInfo, EditedEdges, GraphNewEdgeData, PossibleVertex } from "./GraphExplorer";
import { GraphUtil } from "./GraphUtil";
import * as GraphUtil from "./GraphUtil";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import DeleteIcon from "../../../../images/delete.svg";
import AddPropertyIcon from "../../../../images/Add-property.svg";

View File

@@ -9,7 +9,7 @@ import { GraphVizComponentProps } from "./GraphVizComponent";
import * as GraphData from "./GraphData";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { GraphUtil } from "./GraphUtil";
import * as GraphUtil from "./GraphUtil";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as GremlinClient from "./GremlinClient";
@@ -1031,10 +1031,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
TelemetryProcessor.traceSuccess(
Action.Tab,
{
databaseAccountName: this.props.resourceId,
databaseName: this.props.databaseId,
collectionName: this.props.collectionId,
defaultExperience: Constants.DefaultAccountExperience.Graph,
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Graph",
},

View File

@@ -1,4 +1,4 @@
import { GraphUtil } from "./GraphUtil";
import * as GraphUtil from "./GraphUtil";
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData";
import * as sinon from "sinon";
import { GraphExplorer } from "./GraphExplorer";
@@ -69,7 +69,7 @@ describe("Process Gremlin vertex", () => {
describe("getLimitedArrayString()", () => {
const expectedEmptyResult = { result: "", consumedCount: 0 };
it("should handle null array", () => {
expect(GraphUtil.getLimitedArrayString(null, 10)).toEqual(expectedEmptyResult);
expect(GraphUtil.getLimitedArrayString(undefined, 10)).toEqual(expectedEmptyResult);
});
it("should handle empty array", () => {

View File

@@ -7,180 +7,184 @@ interface JoinArrayMaxCharOutput {
consumedCount: number; // Number of items consumed
}
export class GraphUtil {
public static getNeighborTitle(neighbor: NeighborVertexBasicInfo): string {
return `edge id: ${neighbor.edgeId}, vertex id: ${neighbor.id}`;
}
interface EdgePropertyType {
id: string;
outV?: string;
inV?: string;
}
/**
* Collect all edges from this node
* @param vertex
* @param graphData
* @param newNodes (optional) object describing new nodes encountered
*/
public static createEdgesfromNode(
vertex: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>,
newNodes?: { [id: string]: boolean }
): void {
if (vertex.hasOwnProperty("outE")) {
let outE = vertex.outE;
for (var label in outE) {
$.each(outE[label], (index: number, edge: any) => {
// We create our own edge. No need to fetch
let e = {
id: edge.id,
label: label,
inV: edge.inV,
outV: vertex.id,
};
export function getNeighborTitle(neighbor: NeighborVertexBasicInfo): string {
return `edge id: ${neighbor.edgeId}, vertex id: ${neighbor.id}`;
}
graphData.addEdge(e);
if (newNodes) {
newNodes[edge.inV] = true;
}
});
}
}
if (vertex.hasOwnProperty("inE")) {
let inE = vertex.inE;
for (var label in inE) {
$.each(inE[label], (index: number, edge: any) => {
// We create our own edge. No need to fetch
let e = {
id: edge.id,
label: label,
inV: vertex.id,
outV: edge.outV,
};
/**
* Collect all edges from this node
* @param vertex
* @param graphData
* @param newNodes (optional) object describing new nodes encountered
*/
export function createEdgesfromNode(
vertex: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>,
newNodes?: { [id: string]: boolean }
): void {
if (Object.prototype.hasOwnProperty.call(vertex, "outE")) {
const outE = vertex.outE;
for (const label in outE) {
$.each(outE[label], (index: number, edge: EdgePropertyType) => {
// We create our own edge. No need to fetch
const e = {
id: edge.id,
label: label,
inV: edge.inV,
outV: vertex.id,
};
graphData.addEdge(e);
if (newNodes) {
newNodes[edge.outV] = true;
}
});
}
graphData.addEdge(e);
if (newNodes) {
newNodes[edge.inV] = true;
}
});
}
}
if (Object.prototype.hasOwnProperty.call(vertex, "inE")) {
const inE = vertex.inE;
for (const label in inE) {
$.each(inE[label], (index: number, edge: EdgePropertyType) => {
// We create our own edge. No need to fetch
const e = {
id: edge.id,
label: label,
inV: vertex.id,
outV: edge.outV,
};
/**
* From ['id1', 'id2', 'idn'] build the following string "'id1','id2','idn'".
* The string length cannot exceed maxSize.
* @param array
* @param maxSize
* @return
*/
public static getLimitedArrayString(array: string[], maxSize: number): JoinArrayMaxCharOutput {
if (!array || array.length === 0 || array[0].length + 2 > maxSize) {
return { result: "", consumedCount: 0 };
graphData.addEdge(e);
if (newNodes) {
newNodes[edge.outV] = true;
}
});
}
const end = array.length - 1;
let output = `'${array[0]}'`;
let i = 0;
for (; i < end; i++) {
const candidate = `${output},'${array[i + 1]}'`;
if (candidate.length <= maxSize) {
output = candidate;
} else {
break;
}
}
return {
result: output,
consumedCount: i + 1,
};
}
public static createFetchEdgePairQuery(
outE: boolean,
pkid: string,
excludedEdgeIds: string[],
startIndex: number,
pageSize: number,
withoutStepArgMaxLenght: number
): string {
let gremlinQuery: string;
if (excludedEdgeIds.length > 0) {
// build a string up to max char
const joined = GraphUtil.getLimitedArrayString(excludedEdgeIds, withoutStepArgMaxLenght);
const hasWithoutStep = !!joined.result ? `.has(id, without(${joined.result}))` : "";
if (joined.consumedCount === excludedEdgeIds.length) {
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}()${hasWithoutStep}.limit(${pageSize}).as('e').${
outE ? "inV" : "outV"
}().as('v').select('e', 'v')`;
} else {
const start = startIndex - joined.consumedCount;
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}()${hasWithoutStep}.range(${start},${
start + pageSize
}).as('e').${outE ? "inV" : "outV"}().as('v').select('e', 'v')`;
}
} else {
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}().limit(${pageSize}).as('e').${
outE ? "inV" : "outV"
}().as('v').select('e', 'v')`;
}
return gremlinQuery;
}
/**
* Trim graph
*/
public static trimGraph(
currentRoot: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
) {
const importantNodes = [currentRoot.id].concat(currentRoot._ancestorsId);
graphData.unloadAllVertices(importantNodes);
// Keep only ancestors node in fixed position
$.each(graphData.ids, (index: number, id: string) => {
graphData.getVertexById(id)._isFixedPosition = importantNodes.indexOf(id) !== -1;
});
}
public static addRootChildToGraph(
root: GraphData.GremlinVertex,
child: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
) {
child._ancestorsId = (root._ancestorsId || []).concat([root.id]);
graphData.addVertex(child);
GraphUtil.createEdgesfromNode(child, graphData);
graphData.addNeighborInfo(child);
}
/**
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \"" for now.
* @param value
*/
public static escapeDoubleQuotes(value: string): string {
return value == null ? value : value.replace(/"/g, '\\"');
}
/**
* Surround with double-quotes if val is a string.
* @param val
*/
public static getQuotedPropValue(ip: ViewModels.InputPropertyValue): string {
switch (ip.type) {
case "number":
case "boolean":
return `${ip.value}`;
case "null":
return null;
default:
return `"${GraphUtil.escapeDoubleQuotes(ip.value as string)}"`;
}
}
/**
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \' for now.
* @param value
*/
public static escapeSingleQuotes(value: string): string {
return value == null ? value : value.replace(/'/g, "\\'");
}
}
/**
* From ['id1', 'id2', 'idn'] build the following string "'id1','id2','idn'".
* The string length cannot exceed maxSize.
* @param array
* @param maxSize
* @return
*/
export function getLimitedArrayString(array: string[], maxSize: number): JoinArrayMaxCharOutput {
if (!array || array.length === 0 || array[0].length + 2 > maxSize) {
return { result: "", consumedCount: 0 };
}
const end = array.length - 1;
let output = `'${array[0]}'`;
let i = 0;
for (; i < end; i++) {
const candidate = `${output},'${array[i + 1]}'`;
if (candidate.length <= maxSize) {
output = candidate;
} else {
break;
}
}
return {
result: output,
consumedCount: i + 1,
};
}
export function createFetchEdgePairQuery(
outE: boolean,
pkid: string,
excludedEdgeIds: string[],
startIndex: number,
pageSize: number,
withoutStepArgMaxLenght: number
): string {
let gremlinQuery: string;
if (excludedEdgeIds.length > 0) {
// build a string up to max char
const joined = getLimitedArrayString(excludedEdgeIds, withoutStepArgMaxLenght);
const hasWithoutStep = joined.result ? `.has(id, without(${joined.result}))` : "";
if (joined.consumedCount === excludedEdgeIds.length) {
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}()${hasWithoutStep}.limit(${pageSize}).as('e').${
outE ? "inV" : "outV"
}().as('v').select('e', 'v')`;
} else {
const start = startIndex - joined.consumedCount;
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}()${hasWithoutStep}.range(${start},${
start + pageSize
}).as('e').${outE ? "inV" : "outV"}().as('v').select('e', 'v')`;
}
} else {
gremlinQuery = `g.V(${pkid}).${outE ? "outE" : "inE"}().limit(${pageSize}).as('e').${
outE ? "inV" : "outV"
}().as('v').select('e', 'v')`;
}
return gremlinQuery;
}
/**
* Trim graph
*/
export function trimGraph(
currentRoot: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
) {
const importantNodes = [currentRoot.id].concat(currentRoot._ancestorsId);
graphData.unloadAllVertices(importantNodes);
// Keep only ancestors node in fixed position
$.each(graphData.ids, (index: number, id: string) => {
graphData.getVertexById(id)._isFixedPosition = importantNodes.indexOf(id) !== -1;
});
}
export function addRootChildToGraph(
root: GraphData.GremlinVertex,
child: GraphData.GremlinVertex,
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
) {
child._ancestorsId = (root._ancestorsId || []).concat([root.id]);
graphData.addVertex(child);
createEdgesfromNode(child, graphData);
graphData.addNeighborInfo(child);
}
/**
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \"" for now.
* @param value
*/
export function escapeDoubleQuotes(value: string): string {
return value === undefined ? value : value.replace(/"/g, '\\"');
}
/**
* Surround with double-quotes if val is a string.
* @param val
*/
export function getQuotedPropValue(ip: ViewModels.InputPropertyValue): string {
switch (ip.type) {
case "number":
case "boolean":
return `${ip.value}`;
case "null":
return undefined;
default:
return `"${escapeDoubleQuotes(ip.value as string)}"`;
}
}
/**
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \' for now.
* @param value
*/
export function escapeSingleQuotes(value: string): string {
return value === undefined ? value : value.replace(/'/g, "\\'");
}

View File

@@ -5,7 +5,7 @@
import * as React from "react";
import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer";
import { GraphUtil } from "./GraphUtil";
import * as GraphUtil from "./GraphUtil";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
export interface ReadOnlyNeighborsComponentProps {

View File

@@ -0,0 +1,107 @@
/**
* This adapter is responsible to render the 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 ko from "knockout";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import * as React from "react";
import { StyleConstants } from "../../../Common/Constants";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil";
export interface CommandBarComponentProps {
isNotebookTabActive: boolean;
tabsButtons: CommandButtonComponentProps[];
}
export const CommandBarComponent: React.FunctionComponent = ({ isNotebookTabActive, tabsButtons }: CommandBarComponentProps) {
constructor(props: CommandBarComponentProps) {
super(props);
this.state = {
isNotebookTabActive: false
}
this.container = container;
this.tabsButtons = [];
// this.isNotebookTabActive = ko.computed(() =>
// container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
// );
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isPreferredApiTable,
container.isPreferredApiMongoDB,
container.isPreferredApiDocumentDB,
container.isPreferredApiCassandra,
container.isPreferredApiGraph,
container.deleteCollectionText,
container.deleteDatabaseText,
container.addCollectionText,
container.addDatabaseText,
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
container.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (props.isNotebookTabActive) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}

View File

@@ -1,110 +0,0 @@
/**
* This adapter is responsible to render the 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 ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { StyleConstants } from "../../../Common/Constants";
import * as CommandBarUtil from "./CommandBarUtil";
import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: Explorer;
private tabsButtons: CommandButtonComponentProps[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(() =>
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isPreferredApiTable,
container.isPreferredApiMongoDB,
container.isPreferredApiDocumentDB,
container.isPreferredApiCassandra,
container.isPreferredApiGraph,
container.deleteCollectionText,
container.deleteDatabaseText,
container.addCollectionText,
container.addDatabaseText,
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
container.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
public renderComponent(): JSX.Element {
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}
private triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,5 +1,5 @@
import * as ko from "knockout";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager";
import Explorer from "../../Explorer";
@@ -19,7 +19,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false);
@@ -61,7 +60,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -125,7 +123,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -207,7 +204,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
@@ -295,7 +291,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
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,5 +1,4 @@
import * as CommandBarUtil from "./CommandBarUtil";
import * as ViewModels from "../../../Contracts/ViewModels";
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@@ -26,7 +25,7 @@ describe("CommandBarUtil tests", () => {
const converteds = CommandBarUtil.convertButton([btn], backgroundColor);
expect(converteds.length).toBe(1);
const converted = converteds[0];
expect(!converted.split);
expect(converted.split).toBe(undefined);
expect(converted.iconProps.imageProps.src).toEqual(btn.iconSrc);
expect(converted.iconProps.imageProps.alt).toEqual(btn.iconAlt);
expect(converted.text).toEqual(btn.commandButtonLabel);
@@ -50,7 +49,7 @@ describe("CommandBarUtil tests", () => {
const converteds = CommandBarUtil.convertButton([btn], "backgroundColor");
expect(converteds.length).toBe(1);
const converted = converteds[0];
expect(converted.split);
expect(converted.split).toBe(true);
expect(converted.subMenuProps.items.length).toBe(btn.children.length);
for (let i = 0; i < converted.subMenuProps.items.length; i++) {
expect(converted.subMenuProps.items[i].text).toEqual(btn.children[i].commandButtonLabel);
@@ -64,7 +63,6 @@ describe("CommandBarUtil tests", () => {
}
const converteds = CommandBarUtil.convertButton(btns, "backgroundColor");
const keys = converteds.map((btn: ICommandBarItemProps) => btn.key);
const uniqueKeys = converteds
.map((btn: ICommandBarItemProps) => btn.key)
.filter((value: string, index: number, self: string[]) => self.indexOf(value) === index);
@@ -75,7 +73,7 @@ describe("CommandBarUtil tests", () => {
const btn = createButton();
const backgroundColor = "backgroundColor";
btn.commandButtonLabel = null;
btn.commandButtonLabel = undefined;
let converted = CommandBarUtil.convertButton([btn], backgroundColor)[0];
expect(converted.text).toEqual(btn.tooltipText);

View File

@@ -17,7 +17,7 @@ export class ControlBarComponent extends React.Component<ControlBarComponentProp
return commandButtonOptions.map(
(btn: CommandButtonComponentProps, index: number): JSX.Element => {
// Remove label
btn.commandButtonLabel = null;
btn.commandButtonLabel = undefined;
return CommandButtonComponent.renderButton(btn, `${index}`);
}
);

View File

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

View File

@@ -1,9 +1,6 @@
import * as ViewModels from "../../Contracts/ViewModels";
import { CollectionBase } from "../../Contracts/ViewModels";
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";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
export enum Type {
OpenCollection,
@@ -11,21 +8,18 @@ export enum Type {
}
export interface OpenNotebookItem {
type: Type.OpenNotebook;
name: string;
path: string;
}
export interface OpenCollectionItem {
type: Type.OpenCollection;
databaseId: string;
collectionId: string;
}
export interface Item {
type: Type;
title: string;
description: string;
data: OpenNotebookItem | OpenCollectionItem;
}
type Item = OpenNotebookItem | OpenCollectionItem;
// Update schemaVersion if you are going to change this interface
interface StoredData {
@@ -36,11 +30,11 @@ interface StoredData {
/**
* Stores most recent activity
*/
export class MostRecentActivity {
private static readonly schemaVersion: string = "1";
class MostRecentActivity {
private static readonly schemaVersion: string = "2";
private static itemsMaxNumber: number = 5;
private storedData: StoredData;
constructor(private container: Explorer) {
constructor() {
// Retrieve from local storage
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity);
@@ -97,7 +91,7 @@ export class MostRecentActivity {
LocalStorageUtility.setEntryString(StorageKey.MostRecentActivity, JSON.stringify(this.storedData));
}
public addItem(accountId: string, newItem: Item): void {
private addItem(accountId: string, newItem: Item): void {
// When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable.
// if (!accountId) {
// return;
@@ -116,47 +110,28 @@ export class MostRecentActivity {
return this.storedData.itemsMap[accountId] || [];
}
public collectionWasOpened(accountId: string, { id, databaseId }: Pick<CollectionBase, "id" | "databaseId">) {
const collectionId = id();
this.addItem(accountId, {
type: Type.OpenCollection,
databaseId,
collectionId,
});
}
public notebookWasItemOpened(accountId: string, { name, path }: Pick<NotebookContentItem, "name" | "path">) {
this.addItem(accountId, {
type: Type.OpenNotebook,
name,
path,
});
}
public clear(accountId: string): void {
delete this.storedData.itemsMap[accountId];
this.saveToLocalStorage();
}
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
* @param item
@@ -169,11 +144,7 @@ export class MostRecentActivity {
let index = -1;
for (let i = 0; i < itemsArray.length; i++) {
const currentItem = itemsArray[i];
if (
currentItem.title === item.title &&
currentItem.description === item.description &&
JSON.stringify(currentItem.data) === JSON.stringify(item.data)
) {
if (JSON.stringify(currentItem) === JSON.stringify(item)) {
index = i;
break;
}
@@ -203,3 +174,5 @@ export class MostRecentActivity {
}
}
}
export const mostRecentActivity = new MostRecentActivity();

View File

@@ -3,20 +3,18 @@ import { NotebookContentRecordProps, selectors } from "@nteract/core";
/**
* A bunch of utilities to interact with nteract
*/
export default class NTeractUtil {
public static getCurrentCellType(content: NotebookContentRecordProps): "markdown" | "code" | "raw" | undefined {
if (!content) {
return undefined;
}
const cellFocusedId = selectors.notebook.cellFocused(content.model);
if (cellFocusedId) {
const cell = selectors.notebook.cellById(content.model, { id: cellFocusedId });
if (cell) {
return cell.cell_type;
}
}
export function getCurrentCellType(content: NotebookContentRecordProps): "markdown" | "code" | "raw" | undefined {
if (!content) {
return undefined;
}
const cellFocusedId = selectors.notebook.cellFocused(content.model);
if (cellFocusedId) {
const cell = selectors.notebook.cellById(content.model, { id: cellFocusedId });
if (cell) {
return cell.cell_type;
}
}
return undefined;
}

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