Compare commits

..

72 Commits

Author SHA1 Message Date
Steve Faulkner
95f4653647 Pass masterkey in connection string mode (#572) 2021-03-19 18:38:44 -05:00
Steve Faulkner
c7ba5de90d Fix Test Explorer AAD Authority (#571) 2021-03-19 14:45:58 -05:00
Steve Faulkner
ddf59d6b24 Fix SplashScreen/Tabs Visible bug (#570) 2021-03-19 12:53:34 -05:00
Tanuj Mittal
316fe7e8bb Reset cell status after execution is canceled (#564) 2021-03-19 22:55:54 +05:30
Steve Faulkner
ee8d2070bf Remove Explorer.isRefreshing (#539) 2021-03-19 12:13:52 -05:00
Armando Trejo Oliver
e97a1643fb Make ready message backwards compatible (#569) 2021-03-19 08:38:57 -07:00
Srinath Narayanan
049e3c36d8 Dedicated gateway portal changes (#568)
* 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

* 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

* compile errors fixed

* New changes

* added operationstatus link

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* 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

* Second cut of the Portal

* OnChange for Number of instances

* added keys for texts

* fixed lint errors

* Added support for undefined dynamic description

* fixed failing test

* disable save/discard buttons

* fixed sqlx errors

* Dedicated Gateway changes to add the keys blade

* Change connectionStringText

* Change connectionStringText

* Text changes

* Added UI improvements

* Code review feedback

* undid package lock changes

* updated package.json

* undid package reverts

Co-authored-by: Balaji Sridharan <fnbalaji@microsoft.com>
Co-authored-by: fnbalaji <75445927+fnbalaji@users.noreply.github.com>
2021-03-19 00:54:13 -07:00
Srinath Narayanan
159c297e8d removed change to package.json (#567) 2021-03-19 00:05:15 -07:00
Srinath Narayanan
4e09e4c7fa Revert "Dedicated Gateway Portal Changes (#540)" (#566)
This reverts commit 909a9fa522.
2021-03-19 00:01:15 -07:00
Armando Trejo Oliver
19880203ec Fix ready message (format) (#565)
* Fix ready message sent to parent frame

* format
2021-03-18 21:40:31 -07:00
artrejo
f929a638d6 Fix ready message sent to parent frame 2021-03-18 20:41:43 -07:00
Steve Faulkner
3cccbdfe81 Fix Lint errors in URLUtility (#462) 2021-03-18 22:19:35 -05:00
victor-meng
65c859c835 Move add collection pane to React (#486)
* Move add collection pane to React

* Add feature flag

* fix unit tests

* FIx merge conflicts and address comments

* Resolve merge conflicts

* Address comments

* Fix e2e test failure

* Update test snapshots

* Update test snapshots
2021-03-18 20:06:13 -05:00
Steve Faulkner
c6090e2663 Update Puppeteer (#562) 2021-03-18 17:53:15 -05:00
Steve Faulkner
c43e24061c [Tables] Check for undefined before compare (#561) 2021-03-18 16:16:10 -05:00
fnbalaji
909a9fa522 Dedicated Gateway Portal Changes (#540)
* 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

* 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

* compile errors fixed

* New changes

* added operationstatus link

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* 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

* Second cut of the Portal

* OnChange for Number of instances

* added keys for texts

* fixed lint errors

* Added support for undefined dynamic description

* fixed failing test

* disable save/discard buttons

* fixed sqlx errors

* Dedicated Gateway changes to add the keys blade

* Change connectionStringText

* Change connectionStringText

* Text changes

* Added UI improvements

* Code review feedback

* undid package lock changes

Co-authored-by: Srinath Narayanan <srnara@microsoft.com>
2021-03-18 14:00:28 -07:00
Srinath Narayanan
be4e490a64 Added SelfServe UI updates (#559)
* Added SelfServe UI modifications

* fixed lint error

* addressed PR comments
2021-03-18 13:40:48 -07:00
Steve Faulkner
9db0975f7f Remove Explorer.isTryCosmos (#556) 2021-03-17 16:02:20 -05:00
Steve Faulkner
a2e3be9680 Remove Explorer.serverId (#555) 2021-03-17 15:24:21 -05:00
Srinath Narayanan
eab9b0ce9c mongo index policy editor bug fix (#550) 2021-03-17 11:52:16 -07:00
Steve Faulkner
d9d88c1517 Remove isAuthWithResourceToken from Explorer (#553)
* Remove isAuthWithResourceToken from Explorer

* Update test

* Remove ifs
2021-03-17 10:41:15 -05:00
Ken Dale
e10ab08d5c Small README.md update (#554) 2021-03-17 10:14:32 -05:00
Tanuj Mittal
3eda8029ba Change default Sort order for Gallery to MostRecent & fix gallery card height for empty tags (#545) 2021-03-17 01:40:38 +00:00
Steve Faulkner
6582d3be37 Fix Mongo AAD bug where collections are not load (#552) 2021-03-16 18:04:26 -05:00
Jordi Bunster
3530633fa2 Test coverage: include all source files (#551)
In addition this changes the thresholds to meet the existing level
of test coverage. This was motivated by work that imported a lot
of existing yet untested (and unexercised) code, which brought down
the total % without adding any more code.
2021-03-16 14:13:36 -07: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
188 changed files with 6102 additions and 4265 deletions

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,8 +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
src/Contracts/DataModels.ts
@@ -58,8 +50,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 +85,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 +98,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
@@ -170,7 +157,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 +165,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
@@ -263,8 +247,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
@@ -273,25 +255,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
@@ -338,15 +309,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

View File

@@ -1,35 +0,0 @@
module.exports = {
env: {
browser: true,
es6: true,
},
plugins: ["@typescript-eslint"],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly",
},
overrides: [
{
files: ["**/*.tsx"],
plugins: ["react"],
},
{
files: ["**/*.{test,spec}.{ts,tsx}"],
env: {
jest: true,
},
plugins: ["jest"],
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: "module",
},
rules: {
"@typescript-eslint/no-unused-vars-experimental": "error",
},
};

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
}
}

View File

@@ -69,7 +69,7 @@ Jest and Puppeteer are used for end to end browser based tests and are contained
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
### Architechture
### Architecture
[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)

View File

@@ -7,5 +7,6 @@ module.exports = {
defaultViewport: null,
ignoreHTTPSErrors: true,
args: ["--disable-web-security"],
exitOnPageError: false,
},
};

View File

@@ -21,17 +21,13 @@ module.exports = {
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: [
// "src/Common/Headers*"
// ],
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
coveragePathIgnorePatterns: ["/node_modules/"],
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ["json", "text", "cobertura"],
@@ -39,10 +35,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 22,
functions: 28,
lines: 33,
statements: 31,
branches: 25,
functions: 25,
lines: 30,
statements: 30,
},
},

722
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": {
@@ -1529,17 +1552,6 @@
}
}
},
"@fluentui/react-icons": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-0.3.9.tgz",
"integrity": "sha512-xGisio0Ds8/TWkbERtg6akoegp68/Vop3n6eD7X+0HqVL0rOl44iW+cmQrnOh1xIWiz7EqLVQU0w70bL2oLCGw==",
"requires": {
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/set-version": "^7.0.23",
"@uifabric/utilities": "^7.33.2",
"tslib": "^1.10.0"
}
},
"@fluentui/react-window-provider": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.1.tgz",
@@ -1611,15 +1623,6 @@
"@hapi/hoek": "^8.3.0"
}
},
"@ianvs/eslint-stats": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ianvs/eslint-stats/-/eslint-stats-2.0.0.tgz",
"integrity": "sha512-DnIVVAiXR4tfWERTiQxr1Prrs/uFEbC1C4gTGORMvbF4k7ENyVQeLcoUfNyhlAj2MB/OeorCrN3wSnYuDOUS6Q==",
"requires": {
"chalk": "^2.4.2",
"lodash": "^4.17.15"
}
},
"@icons/material": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
@@ -1681,9 +1684,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": {
@@ -1823,9 +1826,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": "*"
@@ -4414,9 +4417,9 @@
}
},
"@types/expect-puppeteer": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.3.tgz",
"integrity": "sha512-jWZOO9d8ST2vutV5yxZ1OYxxtYD0lOufIgOUlDjyTNBGo8um67shJs2NQDLVDG06wWrabpPPOUQlI8GSvsdKVQ==",
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz",
"integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==",
"dev": true,
"requires": {
"@types/jest": "*",
@@ -4440,9 +4443,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": "*"
@@ -4623,16 +4626,14 @@
}
},
"@types/jest-environment-puppeteer": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.3.2.tgz",
"integrity": "sha512-QVR49cGaQMOrWRN7CXlvtPMuVAxa3Z+W3APxhWoSQLG/lvz1y03ECPvS7Y9eK+hgfndK+39400rO6IifDJV9YA==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.1.tgz",
"integrity": "sha512-LiZTD6i63le6QMnxi7pJB0SFv/fWtss6VVEEDm/UaeowBgjduf8txyE//j3WEeDPxngTvioUjbzA7Rc6Wc3cBA==",
"dev": true,
"requires": {
"@jest/environment": "^24",
"@jest/fake-timers": "^24",
"@jest/types": "^24",
"@jest/types": ">=24 <=26",
"@types/puppeteer": "*",
"jest-mock": "^24"
"jest-environment-node": ">=24 <=26"
}
},
"@types/json-schema": {
@@ -4724,9 +4725,9 @@
"dev": true
},
"@types/puppeteer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
"integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz",
"integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -5650,6 +5651,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",
@@ -6068,7 +6101,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"
}
@@ -7030,11 +7062,6 @@
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"check-types": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
@@ -7291,19 +7318,6 @@
}
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"requires": {
"restore-cursor": "^3.1.0"
}
},
"cli-width": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw=="
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
@@ -8552,6 +8566,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",
@@ -8568,6 +8587,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": {
@@ -8865,6 +8891,12 @@
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true
},
"devtools-protocol": {
"version": "0.0.854822",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz",
"integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==",
"dev": true
},
"diagnostic-channel": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.3.1.tgz",
@@ -9706,100 +9738,6 @@
}
}
},
"eslint-filtered-fix": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/eslint-filtered-fix/-/eslint-filtered-fix-0.1.2.tgz",
"integrity": "sha512-WjubxjjPpqT+kQWKH59ZK17b0sdAwnDNVHXVcscCGNfKS36Big+AnlR3o9/0v3WRbeU6HSkrfE4vaFFRoywpUw==",
"requires": {
"optionator": "^0.9.1"
},
"dependencies": {
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"requires": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
}
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"requires": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.3"
}
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"requires": {
"prelude-ls": "^1.2.1"
}
}
}
},
"eslint-formatter-friendly": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/eslint-formatter-friendly/-/eslint-formatter-friendly-7.0.0.tgz",
"integrity": "sha512-WXg2D5kMHcRxIZA3ulxdevi8/BGTXu72pfOO5vXHqcAfClfIWDSlOljROjCSOCcKvilgmHz1jDWbvFCZHjMQ5w==",
"requires": {
"@babel/code-frame": "7.0.0",
"chalk": "2.4.2",
"extend": "3.0.2",
"strip-ansi": "5.2.0",
"text-table": "0.2.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"eslint-nibble": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/eslint-nibble/-/eslint-nibble-6.1.0.tgz",
"integrity": "sha512-GFgHPsoJMKkvNALHhR6C29KM0jtjBK+fS0pBUCZ6eOU38/hJyd9rNjdo+4LsoxrHhh69hEePZ0LgmFeuMsrT4g==",
"requires": {
"@ianvs/eslint-stats": "^2.0.0",
"chalk": "^2.4.2",
"eslint-filtered-fix": "^0.1.1",
"eslint-formatter-friendly": "^7.0.0",
"eslint-summary": "^1.0.0",
"inquirer": "7.0.1 - 7.2.0",
"optionator": "^0.8.3"
}
},
"eslint-plugin-jest": {
"version": "23.13.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.13.2.tgz",
@@ -9866,39 +9804,6 @@
"estraverse": "^4.1.1"
}
},
"eslint-summary": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/eslint-summary/-/eslint-summary-1.0.0.tgz",
"integrity": "sha1-uBHwBDcBayDA9vUjRHm9Y5W1eIY=",
"requires": {
"chalk": "^1.0.0",
"text-table": "^0.2.0"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
@@ -10239,16 +10144,6 @@
}
}
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
@@ -10492,14 +10387,6 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
@@ -11592,6 +11479,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -12325,117 +12213,6 @@
}
}
},
"inquirer": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz",
"integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==",
"requires": {
"ansi-escapes": "^4.2.1",
"chalk": "^3.0.0",
"cli-cursor": "^3.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.15",
"mute-stream": "0.0.8",
"run-async": "^2.4.0",
"rxjs": "^6.5.3",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0",
"through": "^2.3.6"
},
"dependencies": {
"ansi-escapes": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"requires": {
"type-fest": "^0.11.0"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.1.tgz",
"integrity": "sha512-LL0OLyN6AnfV9xqGQpDBwedT2Rt63737LxvsRxbcwpa2aIeynBApG2Sm//F3TaLHIR1aJBN52DWklc06b94o5Q==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ=="
}
}
},
"internal-ip": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
@@ -13130,9 +12907,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": "*"
@@ -13385,9 +13162,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
},
@@ -14173,9 +13950,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",
@@ -16071,7 +15848,8 @@
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mimic-response": {
"version": "2.1.0",
@@ -16198,12 +15976,6 @@
"through2": "^2.0.0"
}
},
"mitt": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
"integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==",
"dev": true
},
"mixin-deep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -16311,9 +16083,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",
@@ -16344,11 +16116,6 @@
"resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz",
"integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg=="
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
@@ -16831,45 +16598,148 @@
"dev": true
},
"office-ui-fabric-react": {
"version": "7.134.1",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.134.1.tgz",
"integrity": "sha512-yQhPdt5kQfzI/MQnqQMu9lf2mgdHSogEfVIYgfOjbvfMJoUzqA/hH3X2Z7RbncdJ/ca2H+GXVp5GvSgahCOs6g==",
"version": "7.164.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.164.2.tgz",
"integrity": "sha512-qx8WbSXDJbcfq7MdJujUSAkiaYTQIQBkz4pb03r9LncwlrcnVx+thgKn8yztb2g0FlP0z5ONHv1wufrOHYxnHQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.7.0",
"@fluentui/react-focus": "^7.15.0",
"@fluentui/react-icons": "^0.3.0",
"@fluentui/react-window-provider": "^0.3.0",
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.17.6",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.9.0",
"@uifabric/icons": "^7.5.0",
"@uifabric/merge-styles": "^7.18.0",
"@uifabric/react-hooks": "^7.11.0",
"@uifabric/set-version": "^7.0.22",
"@uifabric/styling": "^7.16.0",
"@uifabric/utilities": "^7.31.0",
"@uifabric/foundation": "^7.9.26",
"@uifabric/icons": "^7.5.23",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.13.12",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
},
"dependencies": {
"@fluentui/react-window-provider": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-0.3.3.tgz",
"integrity": "sha512-MVPf2hqOQ17LAZsuvGcr3oOHksAskUm+fCYdXFhbVoAgsCDVTIuH6i8XgHFd6YjBtzjZmI4+k/3NTQfDqBX8EQ==",
"@fluentui/date-time-utilities": {
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-7.9.1.tgz",
"integrity": "sha512-o8iU1VIY+QsqVRWARKiky29fh4KR1xaKSgMClXIi65qkt8EDDhjmlzL0KVDEoDA2GWukwb/1PpaVCWDg4v3cUQ==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/dom-utilities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-1.1.2.tgz",
"integrity": "sha512-XqPS7l3YoMwxdNlaYF6S2Mp0K3FmVIOIy2K3YkMc+eRxu9wFK6emr2Q/3rBhtG5u/On37NExRT7/5CTLnoi9gw==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/react-focus": {
"version": "7.17.6",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.17.6.tgz",
"integrity": "sha512-JkLWNDe567lhvbnIhbYv9nUWYDIVN06utc3krs0UZBI+A0YZtQmftBtY0ghXo4PSjgozZocdu9sYkkgZOgyRLg==",
"requires": {
"@fluentui/keyboard-key": "^0.2.12",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@fluentui/react-window-provider": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.2.tgz",
"integrity": "sha512-fGSgL3Vp/+6t1Ysfz21FWZmqsU+iFVxOigvHnm5uKVyyRPwtaabv/F6kQ2y5isLMI2YmJaUd2i0cDJKu8ggrvw==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/theme": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-1.7.4.tgz",
"integrity": "sha512-o4eo7lstLxxXl1g2RR9yz18Yt8yjQO/LbQuZjsiAfv/4Bf0CRnb+3j1F7gxIdBWAchKj9gzaMpIFijfI98pvYQ==",
"requires": {
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/foundation": {
"version": "7.9.26",
"resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.9.26.tgz",
"integrity": "sha512-1FLTb+jlH/Tuel2L9wT/zLl5ZW6W4Lbjrs5VUVjv81vWxzznvPnTf8+Ew0qkzaH7xDuMNMl7okswhV0IfJyheg==",
"requires": {
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/icons": {
"version": "7.5.23",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.23.tgz",
"integrity": "sha512-eIvUbH0EWgFgdfgFfINgqS2ZVZTyJ/9n5nR4bmcyAe75wsKxm4ser4WIT9IvaBF6+HFVfjUF/v6+VMD7y2LBng==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"tslib": "^1.10.0"
}
},
"@uifabric/merge-styles": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-7.19.2.tgz",
"integrity": "sha512-kTlhwglDqwVgIaJq+0yXgzi65plGhmFcPrfme/rXUGMJZoU+qlGT5jXj5d3kuI59p6VB8jWEg9DAxHozhYeu0g==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@uifabric/react-hooks": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@uifabric/react-hooks/-/react-hooks-7.14.0.tgz",
"integrity": "sha512-Ndu/DEKHF4gFXEZa2AGgSkdWaj+njVrsSyXbkWRh2UZReFWnH1LMko9p/ZCwk1i9kAd5CUmyIfURUzIEya9YCg==",
"requires": {
"@fluentui/react-window-provider": "^1.0.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/set-version": {
"version": "7.0.24",
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.24.tgz",
"integrity": "sha512-t0Pt21dRqdC707/ConVJC0WvcQ/KF7tKLU8AZY7YdjgJpMHi1c0C427DB4jfUY19I92f60LOQyhJ4efH+KpFEg==",
"requires": {
"@uifabric/set-version": "^7.0.23",
"tslib": "^1.10.0"
}
},
"@uifabric/styling": {
"version": "7.16.19",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.16.19.tgz",
"integrity": "sha512-T5fjUCx0LbzUC8Myw15YCaBjdGbSrihWSiPHtLVW77k59yWAW947XnH73QngE8xU7kyKPH3AhQrUEBMB2NjHag==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.19.0.tgz",
"integrity": "sha512-fXComDtGV7dHF4rP4cLHwI6fC+1f/nvPavpMBz4IQdySwixta9TVMKbzt0OA6i0mJztqZCVAd27F/sl9R/JmcQ==",
"requires": {
"@fluentui/theme": "^1.7.1",
"@fluentui/theme": "^1.7.4",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/merge-styles": "^7.19.1",
"@uifabric/set-version": "^7.0.23",
"@uifabric/utilities": "^7.33.2",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/utilities": {
"version": "7.33.5",
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.33.5.tgz",
"integrity": "sha512-I+Oi0deD/xltSluFY8l2EVd/J4mvOaMljxKO2knSD9/KoGDlo/o5GN4gbnVo8nIt76HWHLAk3KtlJKJm6BhbIQ==",
"requires": {
"@fluentui/dom-utilities": "^1.1.2",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
@@ -16902,6 +16772,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
@@ -16989,11 +16860,6 @@
"windows-release": "^3.1.0"
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@@ -17914,38 +17780,66 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"puppeteer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-4.0.0.tgz",
"integrity": "sha512-yNshT0M5DWfZ8DQoduZuGYpcwqXxKOZdgt5G0IF5VEKbydaDbWP/f5pQRfzQ4e+a4w0Rge3uzcogHeUPQM8nCA==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz",
"integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.854822",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0",
"mime": "^2.0.3",
"mitt": "^2.0.1",
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.1",
"pkg-dir": "^4.2.0",
"progress": "^2.0.1",
"proxy-from-env": "^1.0.0",
"proxy-from-env": "^1.1.0",
"rimraf": "^3.0.2",
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
"dev": true
},
"https-proxy-agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"agent-base": "5",
"debug": "4"
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"requires": {
"find-up": "^4.0.0"
}
},
"rimraf": {
@@ -18973,15 +18867,6 @@
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
}
},
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -19031,11 +18916,6 @@
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
},
"run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
"integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="
},
"run-parallel": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
@@ -20801,7 +20681,8 @@
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"throat": {
"version": "4.1.0",
@@ -20811,7 +20692,8 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"through2": {
"version": "2.0.5",
@@ -20859,14 +20741,6 @@
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz",
"integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA=="
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"requires": {
"os-tmpdir": "~1.0.2"
}
},
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
@@ -22894,6 +22768,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",
@@ -61,7 +62,6 @@
"dotenv": "8.2.0",
"es6-object-assign": "1.1.0",
"es6-symbol": "3.1.3",
"eslint-nibble": "6.1.0",
"eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0",
"hasher": "1.2.0",
@@ -77,9 +77,10 @@
"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",
"office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
@@ -120,15 +121,15 @@
"@types/d3": "5.9.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.3",
"@types/expect-puppeteer": "4.4.5",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.3.2",
"@types/jest-environment-puppeteer": "4.4.1",
"@types/memoize-one": "4.1.1",
"@types/node": "12.11.1",
"@types/promise.prototype.finally": "2.0.3",
"@types/prop-types": "15.5.8",
"@types/puppeteer": "3.0.1",
"@types/puppeteer": "5.4.3",
"@types/q": "1.5.1",
"@types/react": "17.0.0",
"@types/react-dom": "17.0.0",
@@ -175,7 +176,7 @@
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1",
"prettier": "2.2.1",
"puppeteer": "4.0.0",
"puppeteer": "8.0.0",
"raw-loader": "0.5.1",
"rimraf": "3.0.0",
"sinon": "3.2.1",
@@ -213,7 +214,6 @@
"format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
"lint:important": "eslint --no-ingore --no-eslintrc -c ./eslintrc.important.js \"**/*.{ts,tsx}\"",
"build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js",
@@ -238,4 +238,4 @@
"prettier": {
"printWidth": 120
}
}
}

View File

@@ -120,6 +120,7 @@ export class Features {
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
public static readonly selfServeType = "selfservetype";
public static readonly enableKOPanel = "enablekopanel";
public static readonly enableReactPane = "enablereactpane";
}
// flight names returned from the portal are always lowercase

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

@@ -1,8 +1,8 @@
import { MessageTypes } from "../Contracts/ExplorerContracts";
import Q from "q";
import * as _ from "underscore";
import * as Constants from "./Constants";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
import * as Constants from "./Constants";
export interface CachedDataPromise<T> {
deferred: Q.Deferred<T>;
@@ -61,6 +61,21 @@ export function sendMessage(data: any): void {
}
}
export function sendReadyMessage(): void {
if (canSendMessage()) {
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe",
kind: "ready",
data: "ready",
},
portalChildWindow.document.referrer
);
}
}
export function canSendMessage(): boolean {
return window.parent !== window;
}

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

@@ -1,55 +1,61 @@
export default class UrlUtility {
public static parseDocumentsPath(resourcePath: string): any {
if (typeof resourcePath !== "string") {
return {};
}
if (resourcePath.length === 0) {
return {};
}
if (resourcePath[resourcePath.length - 1] !== "/") {
resourcePath = resourcePath + "/";
}
if (resourcePath[0] !== "/") {
resourcePath = "/" + resourcePath;
}
var id: string;
var type: string;
var pathParts = resourcePath.split("/");
if (pathParts.length % 2 === 0) {
id = pathParts[pathParts.length - 2];
type = pathParts[pathParts.length - 3];
} else {
id = pathParts[pathParts.length - 3];
type = pathParts[pathParts.length - 2];
}
var result = {
type: type,
objectBody: {
id: id,
self: resourcePath,
},
};
return result;
}
public static createUri(baseUri: string, relativeUri: string): string {
if (!baseUri) {
throw new Error("baseUri is null or empty");
}
var slashAtEndOfUriRegex = /\/$/,
slashAtStartOfUriRegEx = /^\//;
var normalizedBaseUri = baseUri.replace(slashAtEndOfUriRegex, "") + "/",
normalizedRelativeUri = (relativeUri && relativeUri.replace(slashAtStartOfUriRegEx, "")) || "";
return normalizedBaseUri + normalizedRelativeUri;
}
interface Result {
type?: string;
objectBody?: {
id: string;
self: string;
};
}
export function parseDocumentsPath(resourcePath: string): Result {
if (typeof resourcePath !== "string") {
return {};
}
if (resourcePath.length === 0) {
return {};
}
if (resourcePath[resourcePath.length - 1] !== "/") {
resourcePath = resourcePath + "/";
}
if (resourcePath[0] !== "/") {
resourcePath = "/" + resourcePath;
}
let id: string;
let type: string;
const pathParts = resourcePath.split("/");
if (pathParts.length % 2 === 0) {
id = pathParts[pathParts.length - 2];
type = pathParts[pathParts.length - 3];
} else {
id = pathParts[pathParts.length - 3];
type = pathParts[pathParts.length - 2];
}
const result = {
type: type,
objectBody: {
id: id,
self: resourcePath,
},
};
return result;
}
export function createUri(baseUri: string, relativeUri: string): string {
if (!baseUri) {
throw new Error("baseUri is null or empty");
}
const slashAtEndOfUriRegex = /\/$/,
slashAtStartOfUriRegEx = /^\//;
const normalizedBaseUri = baseUri.replace(slashAtEndOfUriRegex, "") + "/",
normalizedRelativeUri = (relativeUri && relativeUri.replace(slashAtStartOfUriRegEx, "")) || "";
return normalizedBaseUri + normalizedRelativeUri;
}

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

@@ -390,10 +390,18 @@ export interface DataExplorerInputsFrame {
sharedThroughputMaximum?: number;
sharedThroughputDefault?: number;
dataExplorerVersion?: string;
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

@@ -1,5 +1,10 @@
import * as Plotly from "plotly.js-cartesian-dist-min";
import dayjs from "dayjs";
import * as Plotly from "plotly.js-cartesian-dist-min";
import { StyleConstants } from "../../Common/Constants";
import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import "./Heatmap.less";
import {
ChartSettings,
DataPayload,
@@ -11,11 +16,6 @@ import {
PartitionTimeStampToData,
PortalTheme,
} from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { StyleConstants } from "../../Common/Constants";
import "./Heatmap.less";
export class Heatmap {
public static readonly elementId: string = "heatmap";
@@ -266,4 +266,4 @@ export function handleMessage(event: MessageEvent) {
}
window.addEventListener("message", handleMessage, false);
sendMessage("ready");
sendReadyMessage();

View File

@@ -6,6 +6,7 @@ describe("CollapsibleSectionComponent", () => {
it("renders", () => {
const props: CollapsibleSectionProps = {
title: "Sample title",
isExpandedByDefault: true,
};
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);

View File

@@ -1,9 +1,10 @@
import { Icon, Label, Stack } from "office-ui-fabric-react";
import * as React from "react";
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
}
export interface CollapsibleSectionState {
@@ -14,7 +15,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
constructor(props: CollapsibleSectionProps) {
super(props);
this.state = {
isExpanded: true,
isExpanded: this.props.isExpandedByDefault,
};
}
@@ -25,8 +26,14 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
public render(): JSX.Element {
return (
<>
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
<Stack
className="collapsibleSection"
horizontal
verticalAlign="center"
tokens={accordionStackTokens}
onClick={this.toggleCollapsed}
>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
<Label>{this.props.title}</Label>
</Stack>
{this.state.isExpanded && this.props.children}

View File

@@ -11,16 +11,10 @@ exports[`CollapsibleSectionComponent renders 1`] = `
"childrenGap": 10,
}
}
verticalAlign="center"
>
<StyledIconBase
<Icon
iconName="ChevronDown"
styles={
Object {
"root": Object {
"paddingTop": 7,
},
}
}
/>
<StyledLabelBase>
Sample title

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

@@ -354,7 +354,6 @@ exports[`test render renders with filters 1`] = `
data-is-scrollable="true"
>
<div
aria-hidden="true"
className="stickyAbove-42"
style={
Object {
@@ -375,7 +374,6 @@ exports[`test render renders with filters 1`] = `
>
<div>
<div
aria-hidden={true}
style={
Object {
"pointerEvents": "none",
@@ -395,7 +393,6 @@ exports[`test render renders with filters 1`] = `
style={Object {}}
>
<div
aria-hidden={false}
style={
Object {
"backgroundColor": "",
@@ -411,6 +408,7 @@ exports[`test render renders with filters 1`] = `
>
<TextFieldBase
ariaLabel="Directory filter text box"
canRevealPassword={false}
className="directoryListFilterTextBox"
deferredValidationTime={200}
onChange={[Function]}
@@ -1123,7 +1121,7 @@ exports[`test render renders with filters 1`] = `
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1149,7 +1147,7 @@ exports[`test render renders with filters 1`] = `
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1168,7 +1166,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1247,7 +1245,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1279,8 +1277,10 @@ exports[`test render renders with filters 1`] = `
},
},
Object {
"backgroundColor": "#f3f2f1",
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1300,7 +1300,7 @@ exports[`test render renders with filters 1`] = `
"backgroundColor": "#f3f2f1",
"color": "#201f1e",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"borderColor": "Highlight",
"color": "Highlight",
},
@@ -1326,7 +1326,7 @@ exports[`test render renders with filters 1`] = `
"splitButtonContainer": Array [
Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
},
},
@@ -1344,7 +1344,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1373,19 +1373,20 @@ exports[`test render renders with filters 1`] = `
"borderBottomRightRadius": "0",
"borderTopRightRadius": "0",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"border": "1px solid WindowText",
"borderRightWidth": "0",
"color": "WindowText",
"forcedColorAdjust": "none",
},
},
},
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
@@ -1398,10 +1399,11 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
"forcedColorAdjust": "none",
},
},
},
@@ -1411,10 +1413,11 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
"forcedColorAdjust": "none",
},
},
},
@@ -1424,12 +1427,11 @@ exports[`test render renders with filters 1`] = `
"border": "none",
"outline": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
},
"@media screen and (forced-colors: active)": Object {
"forcedColorAdjust": "none",
},
},
@@ -1441,7 +1443,7 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"color": "Window",
},
@@ -1450,7 +1452,7 @@ exports[`test render renders with filters 1`] = `
".ms-Button.is-disabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1466,7 +1468,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1478,7 +1480,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1495,7 +1497,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "GrayText",
},
},
@@ -1518,7 +1520,7 @@ exports[`test render renders with filters 1`] = `
":hover": Object {
"backgroundColor": "#edebe9",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "Highlight",
},
},
@@ -1526,6 +1528,11 @@ exports[`test render renders with filters 1`] = `
},
},
Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
".ms-Button-menuIcon": Object {
"color": "WindowText",
},
},
"border": "1px solid #8a8886",
"borderBottomRightRadius": "2px",
"borderLeft": "none",
@@ -1571,7 +1578,7 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1580,7 +1587,7 @@ exports[`test render renders with filters 1`] = `
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1588,7 +1595,7 @@ exports[`test render renders with filters 1`] = `
":hover": Object {
"cursor": "default",
},
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"border": "1px solid GrayText",
"color": "GrayText",
@@ -1893,7 +1900,7 @@ exports[`test render renders with filters 1`] = `
>
<button
aria-disabled={true}
className="ms-Button ms-Button--default is-disabled directoryListButton root-54"
className="ms-Button ms-Button--default is-disabled directoryListButton root-57"
data-is-focusable={false}
disabled={true}
onClick={[Function]}
@@ -1905,7 +1912,7 @@ exports[`test render renders with filters 1`] = `
type="button"
>
<span
className="ms-Button-flexContainer flexContainer-55"
className="ms-Button-flexContainer flexContainer-58"
data-automationid="splitbuttonprimary"
>
<div
@@ -1936,7 +1943,6 @@ exports[`test render renders with filters 1`] = `
</List>
</div>
<div
aria-hidden="true"
className="stickyBelow-43"
style={
Object {

View File

@@ -7,7 +7,7 @@ import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import * as UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
@@ -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,15 @@ 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;
private static readonly smallTextLineHeight = 18;
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 +74,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 styles={{ root: { height: GalleryCardComponent.smallTextLineHeight } }}>
{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: GalleryCardComponent.smallTextLineHeight * 2 } }}>
{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

@@ -50,6 +50,13 @@ exports[`GalleryCardComponent renders 1`] = `
>
<Text
nowrap={true}
styles={
Object {
"root": Object {
"height": 18,
},
}
}
variant="small"
>
<span
@@ -100,7 +107,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="RedEye"
styles={
Object {
@@ -124,7 +131,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="Download"
styles={
Object {
@@ -148,7 +155,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="Heart"
styles={
Object {
@@ -173,7 +180,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
}
>
<Styled
<Separator
styles={
Object {
"root": Object {

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

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

View File

@@ -47,8 +47,8 @@ export interface GalleryViewerComponentProps {
}
export enum GalleryTab {
OfficialSamples,
PublicGallery,
OfficialSamples,
Favorites,
Published,
}
@@ -151,18 +151,19 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
public render(): JSX.Element {
this.traceViewGallery();
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
tabs.push(
const tabs: GalleryTabInfo[] = [
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
)
);
),
this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks),
];
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
if (this.props.container) {
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
}
const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange,
@@ -199,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();
@@ -213,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();
@@ -387,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>
@@ -442,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,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 (
@@ -708,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

@@ -13,7 +13,7 @@ exports[`InfoComponent renders 1`] = `
<div
className="infoPanelMain"
>
<StyledIconBase
<Icon
className="infoIconMain"
iconName="Help"
styles={

View File

@@ -8,90 +8,6 @@ exports[`GalleryViewerComponent renders 1`] = `
onLinkClick={[Function]}
selectedKey="OfficialSamples"
>
<PivotItem
headerText="Official samples"
itemKey="OfficialSamples"
key="OfficialSamples"
style={
Object {
"marginTop": 20,
}
}
>
<Stack
tokens={
Object {
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
Object {
"childrenGap": 20,
"padding": 10,
}
}
>
<StackItem
grow={true}
>
<StyledSearchBoxBase
onChange={[Function]}
placeholder="Search"
/>
</StackItem>
<StackItem>
<StyledLabelBase>
Sort by
</StyledLabelBase>
</StackItem>
<StackItem
styles={
Object {
"root": Object {
"minWidth": 200,
},
}
}
>
<StyledWithResponsiveMode
onChange={[Function]}
options={
Array [
Object {
"key": 0,
"text": "Most viewed",
},
Object {
"key": 1,
"text": "Most downloaded",
},
Object {
"key": 3,
"text": "Most recent",
},
Object {
"key": 2,
"text": "Most favorited",
},
]
}
selectedKey={0}
/>
</StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack>
<StackItem>
<StyledSpinnerBase
size={3}
/>
</StackItem>
</Stack>
</PivotItem>
<PivotItem
headerText="Public gallery"
itemKey="PublicGallery"
@@ -120,6 +36,7 @@ exports[`GalleryViewerComponent renders 1`] = `
"padding": 10,
}
}
wrap={true}
>
<StackItem
grow={true}
@@ -181,32 +98,89 @@ exports[`GalleryViewerComponent renders 1`] = `
</div>
</PivotItem>
<PivotItem
headerText="My favorites"
itemKey="Favorites"
key="Favorites"
headerText="Official samples"
itemKey="OfficialSamples"
key="OfficialSamples"
style={
Object {
"marginTop": 20,
}
}
>
<StyledSpinnerBase
size={3}
/>
</PivotItem>
<PivotItem
headerText="My published work"
itemKey="Published"
key="Published"
style={
Object {
"marginTop": 20,
<Stack
tokens={
Object {
"childrenGap": 10,
}
}
}
>
<StyledSpinnerBase
size={3}
/>
>
<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>
</PivotItem>
</StyledPivotBase>
</div>

View File

@@ -15,7 +15,6 @@ 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">

View File

@@ -68,14 +68,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Icon
iconName="Download"
/>
0
@@ -180,14 +180,14 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Icon
iconName="Download"
/>
0

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

@@ -1,49 +1,51 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
import * as React from "react";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import Explorer from "../../Explorer";
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps,
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import {
hasDatabaseSharedThroughput,
GeospatialConfigType,
TtlType,
ChangeFeedPolicyState,
SettingsV2TabTypes,
getTabTitle,
isDirty,
AddMongoIndexProps,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
getMongoNotification,
} from "./SettingsUtils";
import {
ConflictResolutionComponent,
ConflictResolutionComponentProps,
} from "./SettingsSubComponents/ConflictResolutionComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps,
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import {
AddMongoIndexProps,
ChangeFeedPolicyState,
GeospatialConfigType,
getMongoNotification,
getTabTitle,
hasDatabaseSharedThroughput,
isDirty,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
} from "./SettingsUtils";
interface SettingsV2TabInfo {
tab: SettingsV2TabTypes;
@@ -316,8 +318,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(),
});
@@ -327,16 +327,14 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
? this.saveCollectionSettings(startKey)
: this.saveDatabaseSettings(startKey));
} catch (error) {
this.container.isRefreshingExplorer(false);
this.props.settingsTab.isExecutionError(true);
console.error(error);
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),
@@ -409,10 +407,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(),
},
@@ -703,15 +700,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}
}
this.container.isRefreshingExplorer(false);
this.setBaseline();
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
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(),
},
@@ -810,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(),
},
@@ -823,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),
@@ -869,16 +862,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
});
}
}
this.container.isRefreshingExplorer(false);
this.setBaseline();
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
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(),
},
@@ -886,6 +876,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
);
};
public getMongoIndexTabContent = (
mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps
): JSX.Element => {
if (userContext.authType === AuthType.AAD) {
if (this.container.isEnableMongoCapabilityPresent()) {
return <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />;
}
return undefined;
}
return mongoIndexingPolicyAADError;
};
public render(): JSX.Element {
const scaleComponentProps: ScaleComponentProps = {
collection: this.collection,
@@ -1003,15 +1005,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
});
} else if (this.container.isPreferredApiMongoDB()) {
if (this.container.isEnableMongoCapabilityPresent()) {
const mongoIndexTabContext = this.getMongoIndexTabContent(mongoIndexingPolicyComponentProps);
if (mongoIndexTabContext) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />,
});
} else {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexingPolicyAADError,
content: mongoIndexTabContext,
});
}
}

View File

@@ -23,7 +23,6 @@ import {
ITextStyles,
IDetailsRowStyles,
IStackStyles,
IIconStyles,
IDetailsListStyles,
IDropdownStyles,
ISeparatorStyles,
@@ -116,8 +115,6 @@ export const addMongoIndexSubElementsTokens: IStackTokens = {
childrenGap: 20,
};
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };

View File

@@ -239,7 +239,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return (
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Current index(es)">
<CollapsibleSectionComponent title="Current index(es)" isExpandedByDefault={true}>
{
<>
<DetailsList
@@ -266,7 +266,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return (
<Stack styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Index(es) to be dropped">
<CollapsibleSectionComponent title="Index(es) to be dropped" isExpandedByDefault={true}>
{indexesToBeDropped.length > 0 && (
<DetailsList
styles={customDetailsListStyles}

View File

@@ -42,6 +42,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
}
>
<CollapsibleSectionComponent
isExpandedByDefault={true}
title="Current index(es)"
>
<StyledWithViewportComponent
@@ -114,7 +115,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Stack>
</CollapsibleSectionComponent>
</Stack>
<Styled
<Separator
styles={
Object {
"root": Array [
@@ -139,6 +140,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
}
>
<CollapsibleSectionComponent
isExpandedByDefault={true}
title="Index(es) to be dropped"
/>
</Stack>

View File

@@ -1,23 +1,24 @@
import { Label, Link, MessageBar, MessageBarType, Stack, Text, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as Constants from "../../../../Common/Constants";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { configContext, Platform } from "../../../../ConfigContext";
import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels";
import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import Explorer from "../../../Explorer";
import {
getTextFieldStyles,
subComponentStackProps,
titleAndInputStackProps,
throughputUnit,
getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage,
subComponentStackProps,
throughputUnit,
titleAndInputStackProps,
updateThroughputBeyondLimitWarningMessage,
} from "../SettingsRenderUtils";
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { Link, Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
import { configContext, Platform } from "../../../../ConfigContext";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
export interface ScaleComponentProps {
collection: ViewModels.Collection;
@@ -79,7 +80,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMaxRUs = (): number => {
if (this.props.container?.isTryCosmosDBSubscription()) {
if (userContext.isTryCosmosDBSubscription) {
return Constants.TryCosmosExperience.maxRU;
}
@@ -91,7 +92,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMinRUs = (): number => {
if (this.props.container?.isTryCosmosDBSubscription()) {
if (userContext.isTryCosmosDBSubscription) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
}
@@ -172,7 +173,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
databaseAccount={this.props.container.databaseAccount()}
databaseName={this.databaseId}
collectionName={this.collectionId}
serverId={this.props.container.serverId()}
throughput={this.props.throughput}
throughputBaseline={this.props.throughputBaseline}
onThroughputChange={this.props.onThroughputChange}

View File

@@ -1,17 +1,16 @@
import { shallow } from "enzyme";
import React from "react";
import * as DataModels from "../../../../../Contracts/DataModels";
import {
ThroughputInputAutoPilotV3Component,
ThroughputInputAutoPilotV3Props,
} from "./ThroughputInputAutoPilotV3Component";
import * as DataModels from "../../../../../Contracts/DataModels";
describe("ThroughputInputAutoPilotV3Component", () => {
const baseProps: ThroughputInputAutoPilotV3Props = {
databaseAccount: {} as DataModels.DatabaseAccount,
databaseName: "test",
collectionName: "test",
serverId: undefined,
wasAutopilotOriginallySet: false,
throughput: 100,
throughputBaseline: 100,

View File

@@ -1,55 +1,53 @@
import React from "react";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import {
getTextFieldStyles,
getToolTipContainer,
noLeftPaddingCheckBoxStyle,
titleAndInputStackProps,
checkBoxAndInputStackProps,
getChoiceGroupStyles,
messageBarStyles,
getEstimatedSpendingElement,
getAutoPilotV3SpendElement,
manualToAutoscaleDisclaimerElement,
saveThroughputWarningMessage,
ManualEstimatedSpendingDisplayProps,
AutoscaleEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown,
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import {
Text,
TextField,
ChoiceGroup,
IChoiceGroupOption,
Checkbox,
Stack,
ChoiceGroup,
FontIcon,
IChoiceGroupOption,
IColumn,
Label,
Link,
MessageBar,
FontIcon,
IColumn,
Stack,
Text,
TextField,
} from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import * as SharedConstants from "../../../../../Shared/Constants";
import * as DataModels from "../../../../../Contracts/DataModels";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { userContext } from "../../../../../UserContext";
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
import React from "react";
import { Features } from "../../../../../Common/Constants";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import * as DataModels from "../../../../../Contracts/DataModels";
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
import * as SharedConstants from "../../../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../../../UserContext";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
AutoscaleEstimatedSpendingDisplayProps,
checkBoxAndInputStackProps,
getAutoPilotV3SpendElement,
getChoiceGroupStyles,
getEstimatedSpendingElement,
getRuPriceBreakdown,
getTextFieldStyles,
getToolTipContainer,
ManualEstimatedSpendingDisplayProps,
manualToAutoscaleDisclaimerElement,
messageBarStyles,
noLeftPaddingCheckBoxStyle,
PriceBreakdown,
saveThroughputWarningMessage,
titleAndInputStackProps,
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
export interface ThroughputInputAutoPilotV3Props {
databaseAccount: DataModels.DatabaseAccount;
databaseName: string;
collectionName: string;
serverId: string;
throughput: number;
throughputBaseline: number;
onThroughputChange: (newThroughput: number) => void;
@@ -182,7 +180,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
const serverId: string = this.props.serverId;
const regions = account?.properties?.readLocations?.length || 1;
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
@@ -192,7 +189,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
estimatedSpend = this.getEstimatedManualSpendElement(
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
serverId,
userContext.portalEnv,
regions,
multimaster,
isDirty ? this.props.throughput : undefined
@@ -200,7 +197,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
} else {
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
this.props.maxAutoPilotThroughputBaseline,
serverId,
userContext.portalEnv,
regions,
multimaster,
isDirty ? this.props.maxAutoPilotThroughput : undefined
@@ -458,11 +455,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

@@ -41,7 +41,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
}
}
>
<StyledIconBase
<Icon
ariaLabel="Info"
iconName="Info"
styles={

View File

@@ -920,7 +920,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -937,7 +936,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -946,7 +944,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -962,13 +959,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],
@@ -1067,15 +1057,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,
"setNotificationConsoleData": undefined,
@@ -2136,7 +2117,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -2153,7 +2133,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -2162,7 +2141,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -2178,13 +2156,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],
@@ -2283,15 +2254,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,
"setNotificationConsoleData": undefined,
@@ -3365,7 +3327,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -3382,7 +3343,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -3391,7 +3351,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -3407,13 +3366,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],
@@ -3512,15 +3464,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,
"setNotificationConsoleData": undefined,
@@ -4581,7 +4524,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -4598,7 +4540,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -4607,7 +4548,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -4623,13 +4563,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],
@@ -4728,15 +4661,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,
"setNotificationConsoleData": undefined,

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

@@ -1,24 +1,26 @@
import * as React from "react";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import { TFunction } from "i18next";
import { Label, Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { Slider } from "office-ui-fabric-react/lib/Slider";
import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
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 * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import * as React from "react";
import {
ChoiceItem,
Description,
DescriptionType,
Info,
InputType,
InputTypeValue,
NumberUiType,
SmartUiInput,
} from "../../../SelfServe/SelfServeTypes";
import { TFunction } from "i18next";
import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
/**
* 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,28 @@ 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, labelElement: JSX.Element): 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">
<Stack>
{labelElement}
<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 +154,42 @@ 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>
</Stack>
);
}
private renderDescription(input: DescriptionDisplay): JSX.Element {
const description = input.description;
return (
<Text id={`${input.dataFieldName}-text-display`}>
{this.props.getTranslation(input.description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={input.description.link.href}>
{this.props.getTranslation(input.description.link.textTKey)}
</Link>
)}
</Text>
private renderDescription(input: DescriptionDisplay, labelId: string, labelElement: JSX.Element): JSX.Element {
const dataFieldName = input.dataFieldName;
const description = input.description || (this.props.currentValues.get(dataFieldName)?.value as Description);
if (!description) {
if (!input.isDynamicDescription) {
return this.renderError("Description is not provided.");
}
// If input is a dynamic description and description is not available, empty element is rendered
return <></>;
}
const descriptionElement = (
<Stack>
{labelElement}
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
{this.props.getTranslation(description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={description.link.href}>
{this.props.getTranslation(description.link.textTKey)}
</Link>
)}
</Text>
</Stack>
);
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 +235,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return undefined;
};
private renderNumberInput(input: NumberInput): JSX.Element {
private renderNumberInput(input: NumberInput, labelId: string, labelElement: JSX.Element): 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,
};
@@ -234,71 +248,73 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
if (input.uiType === NumberUiType.Spinner) {
return (
<Stack styles={{ root: { width: 400 } }} tokens={{ childrenGap: 2 }}>
<SpinButton
{...props}
id={`${input.dataFieldName}-spinner-input`}
value={value?.toString()}
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top}
disabled={disabled}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
}}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
)}
<Stack>
{labelElement}
<Stack styles={{ root: { width: 400 } }} tokens={{ childrenGap: 2 }}>
<SpinButton
{...props}
id={`${input.dataFieldName}-spinner-input`}
value={value?.toString()}
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
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}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>
Error: {this.state.errors.get(dataFieldName)}
</MessageBar>
)}
</Stack>
</Stack>
);
} else if (input.uiType === NumberUiType.Slider) {
return (
<div id={`${input.dataFieldName}-slider-input`}>
<Slider
{...props}
value={value}
disabled={disabled}
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
titleLabel: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
valueLabel: SmartUiComponent.labelStyle,
}}
/>
</div>
<Stack>
{labelElement}
<div id={`${input.dataFieldName}-slider-input`}>
<Slider
{...props}
value={value}
disabled={disabled}
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
valueLabel: SmartUiComponent.labelStyle,
}}
/>
</div>
</Stack>
);
} else {
return <>Unsupported number UI type {input.uiType}</>;
}
}
private renderBooleanInput(input: BooleanInput): JSX.Element {
private renderBooleanInput(input: BooleanInput, labelId: string, labelElement: JSX.Element): 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)}
checked={value || false}
onText={this.props.getTranslation(input.trueLabelTKey)}
offText={this.props.getTranslation(input.falseLabelTKey)}
disabled={disabled}
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
styles={{ root: { width: 400 } }}
/>
<Stack>
{labelElement}
<Toggle
id={`${input.dataFieldName}-toggle-input`}
aria-labelledby={labelId}
checked={value || false}
onText={this.props.getTranslation(input.trueLabelTKey)}
offText={this.props.getTranslation(input.falseLabelTKey)}
disabled={disabled}
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
styles={{ root: { width: 400 } }}
/>
</Stack>
);
}
private renderChoiceInput(input: ChoiceInput): JSX.Element {
const { labelTKey, defaultKey, dataFieldName, choices, placeholderTKey } = input;
private renderChoiceInput(input: ChoiceInput, labelId: string, labelElement: JSX.Element): 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;
@@ -306,53 +322,67 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
selectedKey = "";
}
return (
<Dropdown
id={`${input.dataFieldName}-dropdown-input`}
label={this.props.getTranslation(labelTKey)}
selectedKey={selectedKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={this.props.getTranslation(placeholderTKey)}
disabled={disabled}
options={choices.map((c) => ({
key: c.key,
text: this.props.getTranslation(c.label),
}))}
styles={{
root: { width: 400 },
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600,
},
dropdown: SmartUiComponent.labelStyle,
}}
/>
<Stack>
{labelElement}
<Dropdown
id={`${input.dataFieldName}-dropdown-input`}
aria-labelledby={labelId}
selectedKey={selectedKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={this.props.getTranslation(placeholderTKey)}
disabled={disabled}
dropdownWidth="auto"
options={choices.map((c) => ({
key: c.key,
text: this.props.getTranslation(c.label),
}))}
styles={{
root: { width: 400 },
dropdown: SmartUiComponent.labelStyle,
}}
/>
</Stack>
);
}
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 renderElement(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`;
const labelElement: JSX.Element = input.labelTKey && (
<Label id={labelId}>
<ToolTipLabelComponent
label={this.props.getTranslation(input.labelTKey)}
toolTipElement={this.renderInfo(info)}
/>
</Label>
);
return <Stack>{this.renderControl(input, labelId, labelElement)}</Stack>;
}
private renderControl(input: AnyDisplay, labelId: string, labelElement: JSX.Element): 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, labelElement);
}
return this.renderTextInput(input as StringInput);
return this.renderTextInput(input as StringInput, labelId, labelElement);
case "number":
return this.renderNumberInput(input as NumberInput);
return this.renderNumberInput(input as NumberInput, labelId, labelElement);
case "boolean":
return this.renderBooleanInput(input as BooleanInput);
return this.renderBooleanInput(input as BooleanInput, labelId, labelElement);
case "object":
return this.renderChoiceInput(input as ChoiceInput);
return this.renderChoiceInput(input as ChoiceInput, labelId, labelElement);
default:
throw new Error(`Unknown input type: ${input.type}`);
}
@@ -363,10 +393,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.renderElement(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,23 @@ 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"
>
Click here for more information.
</StyledLinkBase>
</Text>
<Stack>
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
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>
</Stack>
</StackItem>
</Stack>
</div>
@@ -67,53 +54,55 @@ 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",
<Stack>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
tokens={
Object {
"childrenGap": 2,
}
}
}
label="Throughput (input)"
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
styles={
Object {
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
}
}
/>
>
<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>
</Stack>
</StackItem>
</Stack>
@@ -130,37 +119,41 @@ 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,
},
}
}
/>
</div>
<Stack>
<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>
</Stack>
</Stack>
</StackItem>
</Stack>
</div>
@@ -197,35 +190,32 @@ 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>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<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=""
/>
</Stack>
</Stack>
</StackItem>
</Stack>
</div>
@@ -241,22 +231,33 @@ 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>
<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>
</Stack>
</StackItem>
</Stack>
</div>
@@ -272,47 +273,53 @@ 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>
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
disabled={true}
dropdownWidth="auto"
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>
</Stack>
</StackItem>
</Stack>
</div>
@@ -328,25 +335,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 +348,23 @@ 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"
>
Click here for more information.
</StyledLinkBase>
</Text>
<Stack>
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
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>
</Stack>
</StackItem>
</Stack>
</div>
@@ -386,53 +380,55 @@ 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",
<Stack>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
tokens={
Object {
"childrenGap": 2,
}
}
}
label="Throughput (input)"
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
styles={
Object {
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
}
}
/>
>
<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>
</Stack>
</StackItem>
</Stack>
@@ -449,36 +445,40 @@ 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,
},
}
}
/>
</div>
<Stack>
<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>
</Stack>
</Stack>
</StackItem>
</Stack>
</div>
@@ -515,34 +515,31 @@ 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>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<StyledTextFieldBase
aria-labelledby="containerId-label"
id="containerId-textField-input"
onChange={[Function]}
styles={
Object {
"root": Object {
"width": 400,
},
},
}
}
}
type="text"
value=""
/>
</div>
type="text"
value=""
/>
</Stack>
</Stack>
</StackItem>
</Stack>
</div>
@@ -558,21 +555,32 @@ 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>
<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>
</Stack>
</StackItem>
</Stack>
</div>
@@ -588,46 +596,52 @@ 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>
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
dropdownWidth="auto"
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>
</Stack>
</StackItem>
</Stack>
</div>

View File

@@ -0,0 +1,20 @@
@import "../../../../less/Common/Constants";
.throughputInputContainer {
.throughputInputRadioBtn {
margin: 0;
}
}
.throughputInputRadioBtnLabel {
font-size: @mediumFontSize;
padding: 0 @LargeSpace 0 @SmallSpace;
}
.throughputInputSpacing {
margin-bottom: @SmallSpace;
& > * {
margin-bottom: @SmallSpace;
}
}

View File

@@ -0,0 +1,302 @@
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "office-ui-fabric-react";
import React from "react";
import * as Constants from "../../../Common/Constants";
import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
export interface ThroughputInputProps {
isDatabase: boolean;
showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
}
export interface ThroughputInputState {
isAutoscaleSelected: boolean;
throughput: number;
isCostAcknowledged: boolean;
}
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
constructor(props: ThroughputInputProps) {
super(props);
this.state = {
isAutoscaleSelected: true,
throughput: AutoPilotUtils.minAutoPilotThroughput,
isCostAcknowledged: false,
};
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
this.props.setIsAutoscale(true);
}
render(): JSX.Element {
return (
<div className="throughputInputContainer throughputInputSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text variant="small" style={{ lineHeight: "20px" }}>
{this.getThroughputLabelText()}
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="throughputInputRadioBtn"
aria-label="Autoscale mode"
checked={this.state.isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onAutoscaleRadioBtnChange.bind(this)}
/>
<span className="throughputInputRadioBtnLabel">Autoscale</span>
<input
className="throughputInputRadioBtn"
aria-label="Manual mode"
checked={!this.state.isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onManualRadioBtnChange.bind(this)}
/>
<span className="throughputInputRadioBtnLabel">Manual</span>
</Stack>
{this.state.isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small">
Provision maximum RU/s required by this resource. Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
capacity calculator
</Link>
.
</Text>
<Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px" }}>
Max RU/s
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<TextField
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
value={this.state.throughput.toString()}
aria-label="Max request units per second"
required={true}
/>
<Text variant="small">
Your {this.props.isDatabase ? "database" : "container"} throughput will automatically scale from{" "}
<b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
{this.state.throughput} RU/s
</b>{" "}
based on usage.
</Text>
</Stack>
)}
{!this.state.isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small">
Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
capacity calculator
</Link>
.
</Text>
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
content={
this.props.showFreeTierExceedThroughputTooltip &&
this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
: undefined
}
>
<TextField
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
step={100}
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
value={this.state.throughput.toString()}
aria-label="Max request units per second"
required={true}
/>
</TooltipHost>
</Stack>
)}
<CostEstimateText requestUnits={this.state.throughput} isAutoscale={this.state.isAutoscaleSelected} />
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start">
<Checkbox
checked={this.state.isCostAcknowledged}
styles={{
checkbox: { width: 12, height: 12 },
label: { padding: 0, margin: "4px 4px 0 0" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
this.setState({ isCostAcknowledged: isChecked });
this.props.onCostAcknowledgeChange(isChecked);
}}
/>
<Text variant="small" style={{ lineHeight: "20px" }}>
{this.getCostAcknowledgeText()}
</Text>
</Stack>
)}
</div>
);
}
private getThroughputLabelText(): string {
if (this.state.isAutoscaleSelected) {
return AutoPilotUtils.getAutoPilotHeaderText();
}
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
return this.state.isAutoscaleSelected
? AutoPilotUtils.getAutoPilotHeaderText()
: `Throughput (${minRU} - ${maxRU} RU/s)`;
}
private onThroughputValueChange(newInput: string): void {
const newThroughput = parseInt(newInput);
this.setState({ throughput: newThroughput });
this.props.setThroughputValue(newThroughput);
}
private getAutoScaleTooltip(): string {
return `After the first ${AutoPilotUtils.getStorageBasedOnUserInput(
this.state.throughput
)} GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value.`;
}
private getCostAcknowledgeText(): string {
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
return "";
}
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
return PricingUtils.getEstimatedSpendAcknowledgeString(
this.state.throughput,
userContext.portalEnv,
numberOfRegions,
multimasterEnabled,
this.state.isAutoscaleSelected
);
}
private onAutoscaleRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.checked && !this.state.isAutoscaleSelected) {
this.setState({ isAutoscaleSelected: true, throughput: AutoPilotUtils.minAutoPilotThroughput });
this.props.setIsAutoscale(true);
}
}
private onManualRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.checked && this.state.isAutoscaleSelected) {
this.setState({
isAutoscaleSelected: false,
throughput: SharedConstants.CollectionCreation.DefaultCollectionRUs400,
});
this.props.setIsAutoscale(false);
this.props.setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
}
}
}
interface CostEstimateTextProps {
requestUnits: number;
isAutoscale: boolean;
}
const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props: CostEstimateTextProps) => {
const { requestUnits, isAutoscale } = props;
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
return <></>;
}
const serverId: string = userContext.portalEnv;
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
const hourlyPrice: number = PricingUtils.computeRUUsagePriceHourly({
serverId,
requestUnits,
numberOfRegions,
multimasterEnabled,
isAutoscale,
});
const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
const currency: string = PricingUtils.getPriceCurrency(serverId);
const currencySign: string = PricingUtils.getCurrencySign(serverId);
const multiplier = PricingUtils.getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
: PricingUtils.getPricePerRu(serverId) * multiplier;
if (isAutoscale) {
return (
<Text variant="small">
Estimated monthly cost ({currency}):{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
</Text>
);
}
return (
<Text variant="small">
Cost ({currency}):{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
<br />
<em>{PricingUtils.estimatedCostDisclaimer}</em>
</Text>
);
};

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);
}

View File

@@ -1,94 +1,88 @@
import React from "react";
import * as ComponentRegisterer from "./ComponentRegisterer";
import * as Constants from "../Common/Constants";
import * as DataModels from "../Contracts/DataModels";
import * as ko from "knockout";
import * as MostRecentActivity from "./MostRecentActivity/MostRecentActivity";
import { IChoiceGroupProps } from "office-ui-fabric-react";
import * as path from "path";
import * as SharedConstants from "../Shared/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import _ from "underscore";
import AddCollectionPane from "./Panes/AddCollectionPane";
import AddDatabasePane from "./Panes/AddDatabasePane";
import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane";
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import Database from "./Tree/Database";
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import GraphStylingPane from "./Panes/GraphStylingPane";
import NewVertexPane from "./Panes/NewVertexPane";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
import Q from "q";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import TerminalTab from "./Tabs/TerminalTab";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import React from "react";
import _ from "underscore";
import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { DialogProps, TextFieldProps } from "./Controls/Dialog";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import * as Constants from "../Common/Constants";
import { ExplorerMetrics } from "../Common/Constants";
import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { IGalleryItem } from "../Juno/JunoClient";
import { LoadQueryPane } from "./Panes/LoadQueryPane";
import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases";
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
import * as Logger from "../Common/Logger";
import { sendMessage, sendCachedDataMessage } from "../Common/MessageHandler";
import { sendCachedDataMessage, sendMessage } from "../Common/MessageHandler";
import { QueriesClient } from "../Common/QueriesClient";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { SubscriptionType } from "../Contracts/SubscriptionType";
import * as ViewModels from "../Contracts/ViewModels";
import { IGalleryItem } from "../Juno/JunoClient";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { appInsights } from "../Shared/appInsights";
import * as SharedConstants from "../Shared/Constants";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
import { updateUserContext, userContext } from "../UserContext";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as ComponentRegisterer from "./ComponentRegisterer";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { DialogProps, TextFieldProps } from "./Controls/Dialog";
import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
import { RouteHandler } from "../RouteHandlers/RouteHandler";
import AddCollectionPane from "./Panes/AddCollectionPane";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
import AddDatabasePane from "./Panes/AddDatabasePane";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel";
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane";
import GraphStylingPane from "./Panes/GraphStylingPane";
import { LoadQueryPane } from "./Panes/LoadQueryPane";
import NewVertexPane from "./Panes/NewVertexPane";
import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SplashScreen } from "./SplashScreen/SplashScreen";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane";
import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
import { TabsManager } from "./Tabs/TabsManager";
import { UploadFilePane } from "./Panes/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
import TabsBase from "./Tabs/TabsBase";
import { TabsManager } from "./Tabs/TabsManager";
import TerminalTab from "./Tabs/TerminalTab";
import Database from "./Tree/Database";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import TabsBase from "./Tabs/TabsBase";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { updateUserContext, userContext } from "../UserContext";
import { stringToBlob } from "../Utils/BlobUtils";
import { IChoiceGroupProps } from "office-ui-fabric-react";
import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils";
import { SubscriptionType } from "../Contracts/SubscriptionType";
import { appInsights } from "../Shared/appInsights";
import { SelfServeLoadingComponentAdapter } from "../SelfServe/SelfServeLoadingComponentAdapter";
import { SelfServeType } from "../SelfServe/SelfServeUtils";
import { SelfServeComponentAdapter } from "../SelfServe/SelfServeComponentAdapter";
import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -119,28 +113,60 @@ export default class Explorer {
public hasWriteAccess: ko.Observable<boolean>;
public collapsedResourceTreeWidth: number = ExplorerMetrics.CollapsedResourceTreeWidth;
/**
* @deprecated
* Use userContext.databaseAccount instead
* */
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
/**
* @deprecated
* Use userContext.subscriptionType instead
* */
public subscriptionType: ko.Observable<SubscriptionType>;
/**
* @deprecated
* Use userContext.apiType instead
* */
public defaultExperience: ko.Observable<string>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "SQL"
* */
public isPreferredApiDocumentDB: ko.Computed<boolean>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Cassandra"
* */
public isPreferredApiCassandra: ko.Computed<boolean>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Mongo"
* */
public isPreferredApiMongoDB: ko.Computed<boolean>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Gremlin"
* */
public isPreferredApiGraph: ko.Computed<boolean>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Tables"
* */
public isPreferredApiTable: ko.Computed<boolean>;
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
/**
* @deprecated
* Compare a string with userContext.apiType instead: userContext.apiType === "Mongo"
* */
public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
public isServerlessEnabled: ko.Computed<boolean>;
public isAccountReady: ko.Observable<boolean>;
public selfServeType: ko.Observable<SelfServeType>;
public canSaveQueries: ko.Computed<boolean>;
public features: ko.Observable<any>;
public serverId: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public queriesClient: QueriesClient;
public tableDataClient: TableDataClient;
public splitter: Splitter;
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
// Notification Console
private setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
@@ -159,18 +185,15 @@ export default class Explorer {
public selectedCollectionId: ko.Computed<string>;
public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>;
public isRefreshingExplorer: ko.Observable<boolean>;
private resourceTree: ResourceTreeAdapter;
private selfServeComponentAdapter: SelfServeComponentAdapter;
// Resource Token
public resourceTokenDatabaseId: ko.Observable<string>;
public resourceTokenCollectionId: ko.Observable<string>;
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
public resourceTokenPartitionKey: ko.Observable<string>;
public isAuthWithResourceToken: ko.Observable<boolean>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs
public isTabsContentExpanded: ko.Observable<boolean>;
@@ -245,7 +268,6 @@ export default class Explorer {
// React adapters
private commandBarComponentAdapter: CommandBarComponentAdapter;
private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter;
private static readonly MaxNbDatabasesToAutoExpand = 5;
@@ -272,24 +294,7 @@ export default class Explorer {
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
let firstInitialization = true;
this.isRefreshingExplorer = ko.observable<boolean>(true);
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
if (!isRefreshing && firstInitialization) {
// set focus on first element
firstInitialization = false;
try {
document.getElementById("createNewContainerCommandButton").parentElement.parentElement.focus();
} catch (e) {
Logger.logWarning(
"getElementById('createNewContainerCommandButton') failed to find element",
"Explorer/this.isRefreshingExplorer.subscribe"
);
}
}
});
this.isAccountReady = ko.observable<boolean>(false);
this.selfServeType = ko.observable<SelfServeType>(undefined);
this._isInitializingNotebooks = false;
this.arcadiaToken = ko.observable<string>();
this.arcadiaToken.subscribe((token: string) => {
@@ -309,7 +314,9 @@ export default class Explorer {
this.isSynapseLinkUpdating = ko.observable<boolean>(false);
this.isAccountReady.subscribe(async (isAccountReady: boolean) => {
if (isAccountReady) {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
this.arcadiaWorkspaces = ko.observableArray();
@@ -320,15 +327,13 @@ export default class Explorer {
Promise.all([this._refreshNotebooksEnabledStateForAccount(), this._refreshSparkEnabledStateForAccount()]).then(
async () => {
this.isNotebookEnabled(
!this.isAuthWithResourceToken() &&
userContext.authType !== AuthType.ResourceToken &&
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
this.isFeatureEnabled(Constants.Features.enableNotebooks))
);
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
isNotebookEnabled: this.isNotebookEnabled(),
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
});
@@ -371,15 +376,12 @@ export default class Explorer {
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.features = ko.observable();
this.serverId = ko.observable<string>();
this.queriesClient = new QueriesClient(this);
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
this.resourceTokenDatabaseId = ko.observable<string>();
this.resourceTokenCollectionId = ko.observable<string>();
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
this.resourceTokenPartitionKey = ko.observable<string>();
this.isAuthWithResourceToken = ko.observable<boolean>(false);
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -443,6 +445,7 @@ export default class Explorer {
databaseAccount
);
this.defaultExperience(defaultExperience);
// TODO. Remove this entirely
updateUserContext({
defaultExperience: DefaultExperienceUtility.mapDefaultExperienceStringToEnum(defaultExperience),
});
@@ -666,7 +669,6 @@ export default class Explorer {
});
this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this);
this.selfServeComponentAdapter = new SelfServeComponentAdapter(this);
this.loadQueryPane = new LoadQueryPane({
id: "loadquerypane",
@@ -749,99 +751,90 @@ export default class Explorer {
$(document.body).click(() => $(".commandDropdownContainer").hide());
});
// TODO move this to API customization class
this.defaultExperience.subscribe((defaultExperience) => {
const defaultExperienceNormalizedString = (
defaultExperience || Constants.DefaultAccountExperience.Default
).toLowerCase();
switch (defaultExperienceNormalizedString) {
case Constants.DefaultAccountExperience.DocumentDB.toLowerCase():
this.addCollectionText("New Container");
this.addDatabaseText("New Database");
this.collectionTitle("SQL API");
this.collectionTreeNodeAltText("Container");
this.deleteCollectionText("Delete Container");
this.deleteDatabaseText("Delete Database");
this.addCollectionPane.title("Add Container");
this.addCollectionPane.collectionIdTitle("Container id");
this.addCollectionPane.collectionWithThroughputInSharedTitle(
"Provision dedicated throughput for this container"
);
this.deleteCollectionConfirmationPane.title("Delete Container");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
this.refreshTreeTitle("Refresh containers");
break;
case Constants.DefaultAccountExperience.MongoDB.toLowerCase():
case Constants.DefaultAccountExperience.ApiForMongoDB.toLowerCase():
this.addCollectionText("New Collection");
this.addDatabaseText("New Database");
this.collectionTitle("Collections");
this.collectionTreeNodeAltText("Collection");
this.deleteCollectionText("Delete Collection");
this.deleteDatabaseText("Delete Database");
this.addCollectionPane.title("Add Collection");
this.addCollectionPane.collectionIdTitle("Collection id");
this.addCollectionPane.collectionWithThroughputInSharedTitle(
"Provision dedicated throughput for this collection"
);
this.refreshTreeTitle("Refresh collections");
break;
case Constants.DefaultAccountExperience.Graph.toLowerCase():
this.addCollectionText("New Graph");
this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Graph");
this.deleteDatabaseText("Delete Database");
this.collectionTitle("Gremlin API");
this.collectionTreeNodeAltText("Graph");
this.addCollectionPane.title("Add Graph");
this.addCollectionPane.collectionIdTitle("Graph id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
this.deleteCollectionConfirmationPane.title("Delete Graph");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
this.refreshTreeTitle("Refresh graphs");
break;
case Constants.DefaultAccountExperience.Table.toLowerCase():
this.addCollectionText("New Table");
this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Table");
this.deleteDatabaseText("Delete Database");
this.collectionTitle("Azure Table API");
this.collectionTreeNodeAltText("Table");
this.addCollectionPane.title("Add Table");
this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables");
this.addTableEntityPane.title("Add Table Entity");
this.editTableEntityPane.title("Edit Table Entity");
this.deleteCollectionConfirmationPane.title("Delete Table");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.tableDataClient = new TablesAPIDataClient();
break;
case Constants.DefaultAccountExperience.Cassandra.toLowerCase():
this.addCollectionText("New Table");
this.addDatabaseText("New Keyspace");
this.deleteCollectionText("Delete Table");
this.deleteDatabaseText("Delete Keyspace");
this.collectionTitle("Cassandra API");
this.collectionTreeNodeAltText("Table");
this.addCollectionPane.title("Add Table");
this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables");
this.addTableEntityPane.title("Add Table Row");
this.editTableEntityPane.title("Edit Table Row");
this.deleteCollectionConfirmationPane.title("Delete Table");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.deleteDatabaseConfirmationPane.title("Delete Keyspace");
this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id");
this.tableDataClient = new CassandraAPIDataClient();
break;
}
});
switch (userContext.apiType) {
case "SQL":
this.addCollectionText("New Container");
this.addDatabaseText("New Database");
this.collectionTitle("SQL API");
this.collectionTreeNodeAltText("Container");
this.deleteCollectionText("Delete Container");
this.deleteDatabaseText("Delete Database");
this.addCollectionPane.title("Add Container");
this.addCollectionPane.collectionIdTitle("Container id");
this.addCollectionPane.collectionWithThroughputInSharedTitle(
"Provision dedicated throughput for this container"
);
this.deleteCollectionConfirmationPane.title("Delete Container");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
this.refreshTreeTitle("Refresh containers");
break;
case "Mongo":
this.addCollectionText("New Collection");
this.addDatabaseText("New Database");
this.collectionTitle("Collections");
this.collectionTreeNodeAltText("Collection");
this.deleteCollectionText("Delete Collection");
this.deleteDatabaseText("Delete Database");
this.addCollectionPane.title("Add Collection");
this.addCollectionPane.collectionIdTitle("Collection id");
this.addCollectionPane.collectionWithThroughputInSharedTitle(
"Provision dedicated throughput for this collection"
);
this.refreshTreeTitle("Refresh collections");
break;
case "Gremlin":
this.addCollectionText("New Graph");
this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Graph");
this.deleteDatabaseText("Delete Database");
this.collectionTitle("Gremlin API");
this.collectionTreeNodeAltText("Graph");
this.addCollectionPane.title("Add Graph");
this.addCollectionPane.collectionIdTitle("Graph id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
this.deleteCollectionConfirmationPane.title("Delete Graph");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
this.refreshTreeTitle("Refresh graphs");
break;
case "Tables":
this.addCollectionText("New Table");
this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Table");
this.deleteDatabaseText("Delete Database");
this.collectionTitle("Azure Table API");
this.collectionTreeNodeAltText("Table");
this.addCollectionPane.title("Add Table");
this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables");
this.addTableEntityPane.title("Add Table Entity");
this.editTableEntityPane.title("Edit Table Entity");
this.deleteCollectionConfirmationPane.title("Delete Table");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.tableDataClient = new TablesAPIDataClient();
break;
case "Cassandra":
this.addCollectionText("New Table");
this.addDatabaseText("New Keyspace");
this.deleteCollectionText("Delete Table");
this.deleteDatabaseText("Delete Keyspace");
this.collectionTitle("Cassandra API");
this.collectionTreeNodeAltText("Table");
this.addCollectionPane.title("Add Table");
this.addCollectionPane.collectionIdTitle("Table id");
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
this.refreshTreeTitle("Refresh tables");
this.addTableEntityPane.title("Add Table Row");
this.editTableEntityPane.title("Edit Table Row");
this.deleteCollectionConfirmationPane.title("Delete Table");
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.deleteDatabaseConfirmationPane.title("Delete Keyspace");
this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id");
this.tableDataClient = new CassandraAPIDataClient();
break;
}
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);
this.selfServeLoadingComponentAdapter = new SelfServeLoadingComponentAdapter();
this._initSettings();
@@ -926,8 +919,6 @@ export default class Explorer {
featureSubcription.dispose();
});
this.mostRecentActivity = new MostRecentActivity.MostRecentActivity(this);
}
public openEnableSynapseLinkDialog(): void {
@@ -970,7 +961,7 @@ export default class Explorer {
ConsoleDataType.Info,
"Enabled Azure Synapse Link for this account"
);
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, startTime);
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime);
this.databaseAccount(databaseAccount);
} catch (error) {
NotificationConsoleUtils.clearInProgressMessageWithId(logId);
@@ -978,7 +969,7 @@ export default class Explorer {
ConsoleDataType.Error,
`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`
);
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, startTime);
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime);
} finally {
this.isSynapseLinkUpdating(false);
}
@@ -1071,17 +1062,12 @@ export default class Explorer {
}
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
this.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
let resourceTreeStartKey: number = null;
if (isInitialLoad) {
resourceTreeStartKey = TelemetryProcessor.traceStart(Action.LoadResourceTree, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
}
@@ -1095,8 +1081,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess(
Action.LoadDatabases,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
},
startKey
@@ -1107,29 +1091,24 @@ export default class Explorer {
this.deleteDatabasesFromList(deltaDatabases.toDelete);
this.selectedNode(currentlySelectedNode);
this._setLoadingStatusText("Fetching containers...");
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
.then(
() => {
this._setLoadingStatusText("Successfully fetched containers.");
deferred.resolve();
},
(reason) => {
this._setLoadingStatusText("Failed to fetch containers.");
deferred.reject(reason);
}
)
.finally(() => this.isRefreshingExplorer(false));
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
() => {
this._setLoadingStatusText("Successfully fetched containers.");
deferred.resolve();
},
(reason) => {
this._setLoadingStatusText("Failed to fetch containers.");
deferred.reject(reason);
}
);
},
(error) => {
this._setLoadingStatusText("Failed to fetch databases.");
this.isRefreshingExplorer(false);
deferred.reject(error);
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.LoadDatabases,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: errorMessage,
errorStack: getErrorStack(error),
@@ -1149,8 +1128,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess(
Action.LoadResourceTree,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
},
resourceTreeStartKey
@@ -1162,8 +1139,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure(
Action.LoadResourceTree,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
@@ -1186,12 +1161,11 @@ export default class Explorer {
public onRefreshResourcesClick = (source: any, event: MouseEvent): void => {
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
description: "Refresh button clicked",
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
this.isRefreshingExplorer(true);
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases();
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases();
this.refreshNotebookList();
};
@@ -1427,20 +1401,6 @@ export default class Explorer {
return false;
}
public setSelfServeType(inputs: ViewModels.DataExplorerInputsFrame): void {
const selfServeFeature = inputs.features[Constants.Features.selfServeType];
if (selfServeFeature) {
// self serve type received from query string
const selfServeType = SelfServeType[selfServeFeature?.toLowerCase() as keyof typeof SelfServeType];
this.selfServeType(selfServeType ? selfServeType : SelfServeType.invalid);
} else if (inputs.selfServeType) {
// self serve type received from portal
this.selfServeType(inputs.selfServeType);
} else {
this.selfServeType(SelfServeType.none);
}
}
public configure(inputs: ViewModels.DataExplorerInputsFrame): void {
if (inputs != null) {
// In development mode, save the iframe message from the portal in session storage.
@@ -1449,45 +1409,22 @@ export default class Explorer {
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
}
const authorizationToken = inputs.authorizationToken || "";
const masterKey = inputs.masterKey || "";
const databaseAccount = inputs.databaseAccount || null;
if (inputs.defaultCollectionThroughput) {
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
}
this.features(inputs.features);
this.serverId(inputs.serverId ?? Constants.ServerIds.productionPortal);
this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType);
this.hasWriteAccess(inputs.hasWriteAccess ?? true);
if (inputs.addCollectionDefaultFlight) {
this.flight(inputs.addCollectionDefaultFlight);
}
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription ?? false);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken ?? false);
this.setFeatureFlagsFromFlights(inputs.flights);
this.setSelfServeType(inputs);
updateConfigContext({
BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT,
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
});
updateUserContext({
authorizationToken,
masterKey,
databaseAccount,
resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId,
subscriptionType: inputs.subscriptionType,
quotaId: inputs.quotaId,
});
TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount,
{
resourceId: this.databaseAccount && this.databaseAccount().id,
dataExplorerArea: Constants.Areas.ResourceTree,
databaseAccount: this.databaseAccount && this.databaseAccount(),
},
inputs.loadDatabaseAccountTimestamp
);
@@ -1563,9 +1500,9 @@ export default class Explorer {
public isRunningOnNationalCloud(): boolean {
return (
this.serverId() === Constants.ServerIds.blackforest ||
this.serverId() === Constants.ServerIds.fairfax ||
this.serverId() === Constants.ServerIds.mooncake
userContext.portalEnv === "blackforest" ||
userContext.portalEnv === "fairfax" ||
userContext.portalEnv === "mooncake"
);
}
@@ -1618,8 +1555,6 @@ export default class Explorer {
: this.databases().filter((db) => db.isDatabaseExpanded());
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
});
databasesToLoad.forEach(async (database: ViewModels.Database) => {
@@ -1645,8 +1580,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure(
Action.LoadCollections,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
@@ -2207,8 +2140,6 @@ export default class Explorer {
);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
});
@@ -2219,8 +2150,6 @@ export default class Explorer {
TelemetryProcessor.traceSuccess(
Action.CreateNewNotebook,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
},
startKey
@@ -2234,8 +2163,6 @@ export default class Explorer {
TelemetryProcessor.traceFailure(
Action.CreateNewNotebook,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
error: errorMessage,
errorStack: getErrorStack(error),
@@ -2359,7 +2286,7 @@ export default class Explorer {
account: userContext.databaseAccount,
container: this,
junoClient: this.notebookManager?.junoClient,
selectedTab: selectedTab || GalleryTab.OfficialSamples,
selectedTab: selectedTab || GalleryTab.PublicGallery,
notebookUrl,
galleryItem,
isFavorite,
@@ -2440,10 +2367,12 @@ export default class Explorer {
public onNewCollectionClicked(): void {
if (this.isPreferredApiCassandra()) {
this.cassandraAddCollectionPane.open();
} else if (this.isFeatureEnabled(Constants.Features.enableReactPane)) {
this.openAddCollectionPanel();
} else {
this.addCollectionPane.open(this.selectedDatabaseId());
document.getElementById("linkAddCollection").focus();
}
document.getElementById("linkAddCollection").focus();
}
private refreshCommandBarButtons(): void {
@@ -2583,4 +2512,16 @@ export default class Explorer {
/>
);
}
public async openAddCollectionPanel(): Promise<void> {
await this.loadDatabaseOffers();
this.openSidePanel(
"New Collection",
<AddCollectionPanel
explorer={this}
closePanel={() => this.closeSidePanel()}
openNotificationConsole={() => this.expandConsole()}
/>
);
}
}

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

@@ -1,8 +1,10 @@
import * as ko from "knockout";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { AuthType } from "../../../AuthType";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager";
import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
@@ -13,7 +15,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -53,7 +54,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -118,7 +118,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
@@ -199,7 +198,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
@@ -281,7 +279,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -340,12 +337,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
updateUserContext({
authType: AuthType.ResourceToken,
});
});
it("should only show New SQL Query and Open Query buttons", () => {

View File

@@ -1,37 +1,38 @@
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 * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.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 AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import GitHubIcon from "../../../../images/github.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.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 OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { Areas } from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext";
import Explorer from "../../Explorer";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as React from "react";
import Explorer from "../../Explorer";
import { OpenFullScreen } from "../../OpenFullScreen";
let counter = 0;
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
if (container.isAuthWithResourceToken()) {
if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container);
}
@@ -548,8 +549,6 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
onCommandClick: () => {
if (!connectedToGitHub) {
TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, {
databaseAccountName: container.databaseAccount() && container.databaseAccount().name,
defaultExperience: container.defaultExperience && container.defaultExperience(),
dataExplorerArea: Areas.Notebook,
});
}

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;
}

View File

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

View File

@@ -29,7 +29,7 @@ import "@nteract/styles/global-variables.css";
import "react-table/react-table.css";
import * as CdbActions from "./actions";
import NteractUtil from "../NTeractUtil";
import * as NteractUtil from "../NTeractUtil";
export interface NotebookComponentBootstrapperOptions {
notebookClient: NotebookClientV2;

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { AppState, ContentRef, selectors } from "@nteract/core";
import { connect } from "react-redux";
import NteractUtil from "../NTeractUtil";
import * as NteractUtil from "../NTeractUtil";
interface VirtualCommandBarComponentProps {
kernelSpecName: string;

View File

@@ -1,4 +1,4 @@
import { StringUtils } from "../../../../../Utils/StringUtils";
import * as StringUtils from "../../../../../Utils/StringUtils";
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
import * as React from "react";

View File

@@ -1,4 +1,4 @@
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer, from } from "rxjs";
import { webSocket } from "rxjs/webSocket";
import { StateObservable } from "redux-observable";
import { ofType } from "redux-observable";
@@ -54,8 +54,6 @@ interface NotebookServiceConfig extends JupyterServerConfig {
const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
databaseAccountName: state.cdb.databaseAccountName,
defaultExperience: state.cdb.defaultExperience,
dataExplorerArea: Constants.Areas.Notebook,
title,
error,
@@ -946,6 +944,39 @@ const traceNotebookKernelEpic = (
);
};
const resetCellStatusOnExecuteCanceledEpic = (
action$: Observable<actions.ExecuteCanceled>,
state$: StateObservable<AppState>
): Observable<actions.UpdateCellStatus> => {
return action$.pipe(
ofType(actions.EXECUTE_CANCELED),
mergeMap((action) => {
const contentRef = action.payload.contentRef;
const model = state$.value.core.entities.contents.byRef.get(contentRef).model;
let busyCellIds: string[] = [];
if (model.type === "notebook") {
const cellMap = model.transient.get("cellMap");
if (cellMap) {
for (const entry of cellMap.toArray()) {
const cellId = entry[0];
const status = model.transient.getIn(["cellMap", cellId, "status"]);
if (status === "busy") {
busyCellIds.push(cellId);
}
}
}
}
return from(busyCellIds).pipe(
map((busyCellId) => {
return actions.updateCellStatus({ id: busyCellId, contentRef, status: undefined });
})
);
})
);
};
export const allEpics = [
addInitialCodeCellEpic,
focusInitialCodeCellEpic,
@@ -962,4 +993,5 @@ export const allEpics = [
traceNotebookTelemetryEpic,
traceNotebookInfoEpic,
traceNotebookKernelEpic,
resetCellStatusOnExecuteCanceledEpic,
];

View File

@@ -1,6 +1,6 @@
import * as DataModels from "../../Contracts/DataModels";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
import { StringUtils } from "../../Utils/StringUtils";
import * as StringUtils from "../../Utils/StringUtils";
import { FileSystemUtil } from "./FileSystemUtil";
import { NotebookUtil } from "./NotebookUtil";

View File

@@ -160,9 +160,6 @@ export default class NotebookManager {
primaryButtonLabel || "Commit",
() => {
TelemetryProcessor.trace(Action.NotebooksGitHubCommit, ActionModifiers.Mark, {
databaseAccountName:
this.params.container.databaseAccount() && this.params.container.databaseAccount().name,
defaultExperience: this.params.container.defaultExperience && this.params.container.defaultExperience(),
dataExplorerArea: Areas.Notebook,
});
resolve(commitMsg);

View File

@@ -1,7 +1,7 @@
import path from "path";
import { ImmutableNotebook, ImmutableCodeCell } from "@nteract/commutable";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
import { StringUtils } from "../../Utils/StringUtils";
import * as StringUtils from "../../Utils/StringUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils";
// Must match rx-jupyter' FileType

View File

@@ -214,7 +214,6 @@
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFreeTierAccount(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}"
>
@@ -435,7 +434,6 @@
maxAutoPilotThroughputSet: autoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFixedStorageSelected(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}"
>

View File

@@ -1,22 +1,22 @@
import * as _ from "underscore";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import editable from "../../Common/EditableUtility";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import { createCollection } from "../../Common/dataAccess/createCollection";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed<boolean>;
@@ -49,7 +49,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
public throughputDatabase: ViewModels.Editable<number>;
public isPreferredApiTable: ko.Computed<boolean>;
public partitionKeyPlaceholder: ko.Computed<string>;
public isTryCosmosDBSubscription: ko.Computed<boolean>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public maxThroughputRU: ko.Observable<number>;
public minThroughputRU: ko.Observable<number>;
public throughputRangeText: ko.Computed<string>;
@@ -186,7 +186,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId: string = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -200,23 +199,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (!this.isSharedAutoPilotSelected()) {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
} else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
@@ -240,7 +244,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId: string = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -254,28 +257,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (!this.isAutoPilotSelected()) {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.throughputMultiPartition(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.throughputMultiPartition(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
} else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.autoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.autoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
@@ -285,9 +288,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return estimatedSpend;
});
this.isTryCosmosDBSubscription = ko.pureComputed<boolean>(() => {
return (this.container && this.container.isTryCosmosDBSubscription()) || false;
});
this.isTryCosmosDBSubscription = ko.observable<boolean>(userContext.isTryCosmosDBSubscription || false);
this.isTryCosmosDBSubscription.subscribe((isTryCosmosDB: boolean) => {
if (!!isTryCosmosDB) {
@@ -298,7 +299,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.canRequestSupport = ko.pureComputed(() => {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
!userContext.isTryCosmosDBSubscription &&
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this._getThroughput();
@@ -489,7 +490,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(
this.container.serverId(),
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
this.container.defaultExperience(),
@@ -693,8 +694,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.databaseId(databaseId);
const addCollectionPaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({
id: this.collectionId(),
storage: this.storage(),
@@ -751,12 +750,16 @@ export default class AddCollectionPane extends ContextualPaneBase {
return undefined;
}
if (this.isAutoPilotSelected()) {
return undefined;
}
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) {
return undefined;
// return undefined if autopilot is selected for the new database/collection
if (this.databaseCreateNew()) {
// database is shared and autopilot is sleected for the database
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) {
return undefined;
}
// database is not shared and autopilot is selected for the collection
if (!this.databaseCreateNewShared() && this.isAutoPilotSelected()) {
return undefined;
}
}
return this._getThroughput();
@@ -788,8 +791,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
const autoPilot: DataModels.AutoPilotCreationSettings = this._getAutoPilot();
const addCollectionPaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
new: this.databaseCreateNew(),
@@ -863,8 +864,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.close();
this.container.refreshAllDatabases();
const addCollectionPaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
new: this.databaseCreateNew(),
@@ -897,8 +896,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage);
const addCollectionPaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
new: this.databaseCreateNew(),

File diff suppressed because it is too large Load Diff

View File

@@ -149,7 +149,6 @@
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFreeTierAccount(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}"
>

View File

@@ -1,19 +1,19 @@
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import editable from "../../Common/EditableUtility";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ContextualPaneBase } from "./ContextualPaneBase";
import * as Constants from "../../Common/Constants";
import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { configContext, Platform } from "../../ConfigContext";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>;
@@ -122,7 +122,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -134,10 +133,15 @@ export default class AddDatabasePane extends ContextualPaneBase {
let estimatedSpendAcknowledge: string;
let estimatedSpend: string;
if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -145,13 +149,13 @@ export default class AddDatabasePane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.maxAutoPilotThroughputSet(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.maxAutoPilotThroughputSet(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -165,7 +169,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.canRequestSupport = ko.pureComputed(() => {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
!userContext.isTryCosmosDBSubscription &&
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this.throughput();
@@ -239,7 +243,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(
this.container.serverId(),
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
this.container.defaultExperience(),
@@ -272,8 +276,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
super.open();
this.resetData();
const addDatabasePaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
subscriptionType: SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
@@ -295,8 +297,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
const offerThroughput: number = this._computeOfferThroughput();
const addDatabasePaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
shared: this.databaseCreateNewShared(),
@@ -359,8 +359,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.close();
this.container.refreshAllDatabases();
const addDatabasePaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
shared: this.databaseCreateNewShared(),
@@ -383,8 +381,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage);
const addDatabasePaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
database: ko.toJS({
id: this.databaseId(),
shared: this.databaseCreateNewShared(),

View File

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

View File

@@ -166,7 +166,6 @@
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
costsVisible: costsVisible,
showAutoPilot: !isFreeTierAccount()
}"
>
</throughput-input-autopilot-v3>

View File

@@ -1,21 +1,21 @@
import * as _ from "underscore";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable<string>;
@@ -127,7 +127,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -139,10 +138,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string;
let estimatedDedicatedSpendAcknowledge: string;
if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -150,13 +154,13 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.selectedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.selectedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -172,7 +176,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -183,10 +186,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string;
let estimatedSharedSpendAcknowledge: string;
if (!this.isSharedAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.keyspaceThroughput(),
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.keyspaceThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
@@ -194,13 +202,13 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
@@ -215,7 +223,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
});
this.canRequestSupport = ko.pureComputed(() => {
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
if (configContext.platform !== Platform.Emulator && !userContext.isTryCosmosDBSubscription) {
const offerThroughput: number = this.throughput();
return offerThroughput <= 100000;
}
@@ -293,8 +301,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
}
const addCollectionPaneOpenMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -345,8 +351,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}
const addCollectionPaneStartMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -391,8 +395,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.isExecuting(false);
this.close();
const addCollectionPaneSuccessMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
@@ -421,8 +423,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.formErrors(errorMessage);
this.isExecuting(false);
const addCollectionPaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
collection: {
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,

View File

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

View File

@@ -133,7 +133,7 @@ describe("Delete Collection Confirmation Pane", () => {
.simulate("change", { target: { value: selectedCollectionId } });
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
wrapper.unmount();
@@ -154,7 +154,7 @@ describe("Delete Collection Confirmation Pane", () => {
.simulate("change", { target: { value: feedbackText } });
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
const deleteFeedback = new DeleteFeedback(

View File

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

View File

@@ -1,20 +1,19 @@
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as React from "react";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { PanelFooterComponent } from "./PanelFooterComponent";
import { Collection } from "../../Contracts/ViewModels";
import { Text, TextField } from "office-ui-fabric-react";
import { userContext } from "../../UserContext";
import * as React from "react";
import { Areas } from "../../Common/Constants";
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { PanelErrorComponent, PanelErrorProps } from "./PanelErrorComponent";
import DeleteFeedback from "../../Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { Collection } from "../../Contracts/ViewModels";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
import { PanelFooterComponent } from "./PanelFooterComponent";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
import { PanelLoadingScreen } from "./PanelLoadingScreen";
export interface DeleteCollectionConfirmationPanelProps {
explorer: Explorer;
closePanel: () => void;
@@ -44,8 +43,8 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
render(): JSX.Element {
return (
<div className="panelContentContainer">
<PanelErrorComponent {...this.getPanelErrorProps()} />
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
<PanelInfoErrorComponent {...this.getPanelErrorProps()} />
<div className="panelMainContent">
<div className="confirmDeleteInput">
<span className="mandatoryStar">* </span>
@@ -79,18 +78,16 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
</div>
)}
</div>
<PanelFooterComponent buttonLabel="OK" onOKButtonClicked={() => this.submit()} />
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" hidden={!this.state.isExecuting}>
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
</div>
<PanelFooterComponent buttonLabel="OK" />
{this.state.isExecuting && <PanelLoadingScreen />}
</form>
);
}
private getPanelErrorProps(): PanelErrorProps {
private getPanelErrorProps(): PanelInfoErrorProps {
if (this.state.formError) {
return {
isWarning: false,
messageType: "error",
message: this.state.formError,
showErrorDetails: true,
openNotificationConsole: this.props.openNotificationConsole,
@@ -98,7 +95,7 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
}
return {
isWarning: true,
messageType: "warning",
showErrorDetails: false,
message:
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
@@ -109,9 +106,10 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
return this.props.explorer.isLastCollection() && !this.props.explorer.isSelectedDatabaseShared();
}
public async submit(): Promise<void> {
const collection = this.props.explorer.findSelectedCollection();
public async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault();
const collection = this.props.explorer.findSelectedCollection();
if (!collection || this.inputCollectionName !== collection.id()) {
const errorMessage = "Input collection name does not match the selected collection";
this.setState({ formError: errorMessage });
@@ -122,8 +120,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
this.setState({ formError: "", isExecuting: true });
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, {
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection",
@@ -142,8 +138,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
TelemetryProcessor.traceSuccess(
Action.DeleteCollection,
{
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection",
@@ -171,8 +165,6 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
TelemetryProcessor.traceFailure(
Action.DeleteCollection,
{
databaseAccountName: userContext.databaseAccount?.name,
defaultExperience: userContext.defaultExperience,
collectionId: collection.id(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: "Delete Collection",

View File

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

View File

@@ -6,7 +6,7 @@ import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import { JunoUtils } from "../../Utils/JunoUtils";
import * as JunoUtils from "../../Utils/JunoUtils";
import { AuthorizeAccessComponent } from "../Controls/GitHub/AuthorizeAccessComponent";
import { GitHubReposComponent, GitHubReposComponentProps, RepoListItem } from "../Controls/GitHub/GitHubReposComponent";
import { GitHubReposComponentAdapter } from "../Controls/GitHub/GitHubReposComponentAdapter";
@@ -157,8 +157,6 @@ export class GitHubReposPane extends ContextualPaneBase {
this.isExecuting(false);
this.title(GitHubReposComponent.ManageGitHubRepoTitle); // Used for telemetry
TelemetryProcessor.trace(Action.NotebooksGitHubManageRepo, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook,
});
this.triggerRender();
@@ -339,8 +337,6 @@ export class GitHubReposPane extends ContextualPaneBase {
private connectToGitHub(scope: string): void {
this.isExecuting(true);
TelemetryProcessor.trace(Action.NotebooksGitHubAuthorize, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook,
scopesSelected: scope,
});

View File

@@ -1,12 +1,58 @@
@import "../../../less/Common/Constants";
.panelContentContainer {
.panelFormWrapper {
display: flex;
flex-direction: column;
height: 100%;
.panelMainContent {
flex-grow: 1;
padding: 0 34px;
margin: 20px 0;
overflow: auto;
& > * {
margin-bottom: @DefaultSpace;
& > * {
margin-bottom: @SmallSpace;
}
}
.panelInfoIcon {
font-size: @mediumFontSize;
width: @mediumFontSize;
margin: auto 0 auto @SmallSpace;
color: @InfoIconColor;
cursor: default;
vertical-align: middle;
}
.panelTextBold {
font-weight: 600;
line-height: 20px;
}
.panelTextField {
font-size: @mediumFontSize;
border: 1px solid #605e5c;
color: #000;
padding: 4px 10px;
width: @newCollectionPaneInputWidth;
}
.panelRadioBtn {
margin: 0;
}
.panelRadioBtnLabel {
font-size: @mediumFontSize;
padding: 0 @LargeSpace 0 @SmallSpace;
}
.collapsibleSection {
margin-bottom: 0;
}
}
}
@@ -16,26 +62,30 @@
font-weight: 400;
}
.panelWarningErrorContainer {
.panelInfoErrorContainer {
background-color: @BaseLow;
padding: @DefaultSpace;
display: inline-flex;
margin-bottom: 24px;
margin: 20px 34px 0 34px;
.panelWarningIcon {
i {
font-size: @WarningErrorIconSize;
width: @WarningErrorIconSize;
margin: auto 0 auto @SmallSpace;
margin-left: @SmallSpace;
}
.panelWarningIcon {
color: @WarningIconColor;
}
.panelErrorIcon {
font-size: @WarningErrorIconSize;
width: @WarningErrorIconSize;
margin: auto 0 auto @SmallSpace;
color: @ErrorIconColor;
}
.panelLargeInfoIcon {
color: @InfoIconColor;
}
.panelWarningErrorDetailsLinkContainer {
display: flex;
flex-direction: column;
@@ -48,10 +98,19 @@
}
}
.panelFooter button {
height: 30px;
.panelFooter {
padding: 20px 34px;
border-top: solid 1px #bbbbbb;
& button {
height: 30px;
}
}
.deleteCollectionFeedback {
margin-top: 12px;
}
.panelGroupSpacing > * {
margin-bottom: @SmallSpace;
}

View File

@@ -9,10 +9,30 @@ export interface PanelContainerProps {
closePanel: () => void;
}
export class PanelContainerComponent extends React.Component<PanelContainerProps> {
export interface PanelContainerState {
height: string;
}
export class PanelContainerComponent extends React.Component<PanelContainerProps, PanelContainerState> {
private static readonly consoleHeaderHeight = 32;
private static readonly consoleContentHeight = 220;
constructor(props: PanelContainerProps) {
super(props);
this.state = {
height: this.getPanelHeight(),
};
}
componentDidMount(): void {
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
}
componentWillUnmount(): void {
window.removeEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
}
render(): JSX.Element {
if (!this.props.panelContent) {
return <></>;
@@ -30,8 +50,10 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
headerClassName="panelHeader"
styles={{
navigation: { borderBottom: "1px solid #cccccc" },
content: { padding: "24px 34px 20px 34px", height: "100%" },
content: { padding: 0, height: "100%" },
scrollableContent: { height: "100%" },
header: { padding: "0 0 8px 34px" },
commands: { marginTop: 8 },
}}
style={{ height: this.getPanelHeight() }}
>

View File

@@ -1,29 +0,0 @@
import React from "react";
import { Icon, Text } from "office-ui-fabric-react";
export interface PanelErrorProps {
message: string;
isWarning: boolean;
showErrorDetails: boolean;
openNotificationConsole?: () => void;
}
export const PanelErrorComponent: React.FunctionComponent<PanelErrorProps> = (props: PanelErrorProps): JSX.Element => (
<div className="panelWarningErrorContainer">
{props.isWarning ? (
<Icon iconName="WarningSolid" className="panelWarningIcon" />
) : (
<Icon iconName="StatusErrorFull" className="panelErrorIcon" />
)}
<span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small">
{props.message}
</Text>
{props.showErrorDetails && (
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
More details
</a>
)}
</span>
</div>
);

View File

@@ -3,13 +3,12 @@ import { PrimaryButton } from "office-ui-fabric-react";
export interface PanelFooterProps {
buttonLabel: string;
onOKButtonClicked: () => void;
}
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
props: PanelFooterProps
): JSX.Element => (
<div className="panelFooter">
<PrimaryButton id="sidePanelOkButton" text={props.buttonLabel} onClick={() => props.onOKButtonClicked()} />
<PrimaryButton type="submit" id="sidePanelOkButton" text={props.buttonLabel} />
</div>
);

View File

@@ -0,0 +1,45 @@
import React from "react";
import { Icon, Link, Stack, Text } from "office-ui-fabric-react";
export interface PanelInfoErrorProps {
message: string;
messageType: string;
showErrorDetails: boolean;
link?: string;
linkText?: string;
openNotificationConsole?: () => void;
}
export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProps> = (
props: PanelInfoErrorProps
): JSX.Element => {
let icon: JSX.Element;
if (props.messageType === "error") {
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" />;
} else if (props.messageType === "warning") {
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" />;
} else if (props.messageType === "info") {
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" />;
}
return (
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="start">
{icon}
<span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small">
{props.message}{" "}
{props.link && props.linkText && (
<Link target="_blank" href={props.link}>
{props.linkText}
</Link>
)}
</Text>
{props.showErrorDetails && (
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
More details
</a>
)}
</span>
</Stack>
);
};

View File

@@ -0,0 +1,8 @@
import React from "react";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
export const PanelLoadingScreen: React.FunctionComponent = () => (
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
);

View File

@@ -152,10 +152,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
}
try {
startKey = traceStart(Action.NotebooksGalleryPublish, {
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
});
startKey = traceStart(Action.NotebooksGalleryPublish, {});
const response = await this.junoClient.publishNotebook(
this.name,
@@ -183,8 +180,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
traceSuccess(
Action.NotebooksGalleryPublish,
{
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
notebookId: data.id,
isPublishPending,
},
@@ -195,8 +190,6 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
traceFailure(
Action.NotebooksGalleryPublish,
{
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},

View File

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

View File

@@ -5,7 +5,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import { ContextualPaneBase } from "./ContextualPaneBase";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { StringUtility } from "../../Shared/StringUtility";
import * as StringUtility from "../../Shared/StringUtility";
import { configContext } from "../../ConfigContext";
export class SettingsPane extends ContextualPaneBase {

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