Compare commits

..

6 Commits

Author SHA1 Message Date
Balaji Sridharan
5b0a98dce7 Removing TODO comments 2021-02-17 10:43:23 -08:00
Balaji Sridharan
67ebce444f Stylecop changes 2021-02-16 00:21:44 -08:00
Balaji Sridharan
a09bcc7197 Merge branch 'users/fnbalaji/PortalChangesForDGW' of https://github.com/Azure/cosmos-explorer into users/fnbalaji/PortalChangesForDGW 2021-02-15 23:49:54 -08:00
Balaji Sridharan
b2390e23e7 Portal changes for DedicatedGateway. CR feedback 2021-02-15 23:49:14 -08:00
fnbalaji
f922103e5c Merge branch 'master' into users/fnbalaji/PortalChangesForDGW 2021-02-15 23:39:50 -08:00
Balaji Sridharan
faa98de9e9 Portal changes for DedicatedGateway
Changes to support creation and deletion of DedicatedGateway resource.

Tested locally with various scenarios.
2021-02-08 05:16:10 -08:00
259 changed files with 6403 additions and 7505 deletions

View File

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

View File

@@ -11,9 +11,15 @@ 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
@@ -24,6 +30,8 @@ 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
@@ -50,6 +58,8 @@ 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
@@ -85,6 +95,8 @@ 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
@@ -98,6 +110,7 @@ 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
@@ -149,7 +162,7 @@ src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
src/Explorer/Panes/UploadFilePane.ts
src/Explorer/Panes/UploadItemsPane.ts
src/Explorer/SplashScreen/SplashScreen.test.ts
src/Explorer/SplashScreen/SplashScreenComponentAdapter.test.ts
src/Explorer/Tables/Constants.ts
src/Explorer/Tables/DataTable/CacheBase.ts
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
@@ -157,6 +170,7 @@ 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
@@ -165,6 +179,8 @@ 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
@@ -184,6 +200,7 @@ src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/SparkMasterTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts
@@ -247,6 +264,8 @@ 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
@@ -255,14 +274,25 @@ 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
@@ -309,7 +339,15 @@ 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
@@ -339,7 +377,8 @@ src/Explorer/Notebook/temp/inputs/editor.tsx
src/Explorer/Notebook/temp/markdown-cell.tsx
src/Explorer/Notebook/temp/source.tsx
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
src/Explorer/SplashScreen/SplashScreen.tsx
src/Explorer/SplashScreen/SplashScreenComponent.tsx
src/Explorer/SplashScreen/SplashScreenComponentApdapter.tsx
src/Explorer/Tabs/GalleryTab.tsx
src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx

View File

@@ -15,10 +15,10 @@ jobs:
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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"
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, unittest]
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 12.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
@@ -143,72 +143,48 @@ jobs:
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendhosted:
name: "End to End Tests"
needs: [cleanupaccounts]
name: "End to End Hosted Tests"
needs: [lint, format, compile, unittest]
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 14.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: npm start &
- run: node utils/cleanupDBs.js
- run: npm run wait-for-server
- name: ${{ matrix['test-file'] }}
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }}
node-version: 12.x
- name: End to End Hosted Tests
run: |
npm ci
npm start &
node utils/cleanupDBs.js
npm run wait-for-server
npm run test:e2e
shell: bash
env:
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: [build]
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -224,7 +200,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 -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
@@ -232,7 +208,7 @@ jobs:
nugetmpac:
name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [build]
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -249,7 +225,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 -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:

43
.vscode/settings.json vendored
View File

@@ -1,26 +1,21 @@
// 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",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
}
}
"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"
}

View File

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

View File

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

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.
### Architecture
### Architechture
[![](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

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

View File

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

View File

@@ -21,13 +21,17 @@ module.exports = {
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],
// collectCoverageFrom: [
// "src/Common/Headers*"
// ],
// 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"],
@@ -35,10 +39,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 25,
functions: 25,
lines: 30,
statements: 30,
branches: 22,
functions: 28,
lines: 33,
statements: 31,
},
},

395
package-lock.json generated
View File

@@ -218,11 +218,6 @@
}
}
},
"@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",
@@ -251,16 +246,6 @@
"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",
@@ -713,20 +698,12 @@
}
},
"@babel/plugin-syntax-class-properties": {
"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==",
"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==",
"dev": true,
"requires": {
"@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/helper-plugin-utils": "^7.10.4"
}
},
"@babel/plugin-syntax-decorators": {
@@ -1552,6 +1529,17 @@
}
}
},
"@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",
@@ -1684,9 +1672,9 @@
}
},
"@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
"dev": true
},
"@jest/console": {
@@ -1826,9 +1814,9 @@
}
},
"@types/yargs": {
"version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -4417,9 +4405,9 @@
}
},
"@types/expect-puppeteer": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz",
"integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.3.tgz",
"integrity": "sha512-jWZOO9d8ST2vutV5yxZ1OYxxtYD0lOufIgOUlDjyTNBGo8um67shJs2NQDLVDG06wWrabpPPOUQlI8GSvsdKVQ==",
"dev": true,
"requires": {
"@types/jest": "*",
@@ -4443,9 +4431,9 @@
}
},
"@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
"integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -4626,14 +4614,16 @@
}
},
"@types/jest-environment-puppeteer": {
"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==",
"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==",
"dev": true,
"requires": {
"@jest/types": ">=24 <=26",
"@jest/environment": "^24",
"@jest/fake-timers": "^24",
"@jest/types": "^24",
"@types/puppeteer": "*",
"jest-environment-node": ">=24 <=26"
"jest-mock": "^24"
}
},
"@types/json-schema": {
@@ -4725,9 +4715,9 @@
"dev": true
},
"@types/puppeteer": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz",
"integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
"integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -5651,38 +5641,6 @@
"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",
@@ -6101,6 +6059,7 @@
"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"
}
@@ -7329,11 +7288,6 @@
"tiny-emitter": "^2.0.0"
}
},
"clipboard-copy": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-4.0.1.tgz",
"integrity": "sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng=="
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -8566,11 +8520,6 @@
"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",
@@ -8587,13 +8536,6 @@
"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": {
@@ -8891,12 +8833,6 @@
"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",
@@ -12907,9 +12843,9 @@
}
},
"@types/yargs": {
"version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -13162,9 +13098,9 @@
}
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
"dev": true,
"optional": true
},
@@ -13950,9 +13886,9 @@
}
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
@@ -15976,6 +15912,12 @@
"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",
@@ -16083,9 +16025,9 @@
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"msal": {
"version": "1.4.4",
@@ -16598,148 +16540,45 @@
"dev": true
},
"office-ui-fabric-react": {
"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==",
"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==",
"requires": {
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.17.6",
"@fluentui/react-window-provider": "^1.0.2",
"@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",
"@microsoft/load-themed-styles": "^1.10.26",
"@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",
"@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",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
},
"dependencies": {
"@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==",
"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==",
"requires": {
"@uifabric/set-version": "^7.0.23",
"tslib": "^1.10.0"
}
},
"@uifabric/styling": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.19.0.tgz",
"integrity": "sha512-fXComDtGV7dHF4rP4cLHwI6fC+1f/nvPavpMBz4IQdySwixta9TVMKbzt0OA6i0mJztqZCVAd27F/sl9R/JmcQ==",
"version": "7.16.19",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.16.19.tgz",
"integrity": "sha512-T5fjUCx0LbzUC8Myw15YCaBjdGbSrihWSiPHtLVW77k59yWAW947XnH73QngE8xU7kyKPH3AhQrUEBMB2NjHag==",
"requires": {
"@fluentui/theme": "^1.7.4",
"@fluentui/theme": "^1.7.1",
"@microsoft/load-themed-styles": "^1.10.26",
"@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",
"@uifabric/merge-styles": "^7.19.1",
"@uifabric/set-version": "^7.0.23",
"@uifabric/utilities": "^7.33.2",
"tslib": "^1.10.0"
}
}
@@ -17780,66 +17619,38 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"puppeteer": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz",
"integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-4.0.0.tgz",
"integrity": "sha512-yNshT0M5DWfZ8DQoduZuGYpcwqXxKOZdgt5G0IF5VEKbydaDbWP/f5pQRfzQ4e+a4w0Rge3uzcogHeUPQM8nCA==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.854822",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.1",
"pkg-dir": "^4.2.0",
"https-proxy-agent": "^4.0.0",
"mime": "^2.0.3",
"mitt": "^2.0.1",
"progress": "^2.0.1",
"proxy-from-env": "^1.1.0",
"proxy-from-env": "^1.0.0",
"rimraf": "^3.0.2",
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"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": {
"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==",
"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
},
"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==",
"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==",
"dev": true,
"requires": {
"find-up": "^4.0.0"
"agent-base": "5",
"debug": "4"
}
},
"rimraf": {
@@ -22768,16 +22579,6 @@
"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,7 +8,6 @@
"@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",
@@ -50,7 +49,6 @@
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2",
"css-element-queries": "1.1.1",
@@ -77,10 +75,9 @@
"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.164.2",
"office-ui-fabric-react": "7.134.1",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
@@ -121,15 +118,15 @@
"@types/d3": "5.9.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.5",
"@types/expect-puppeteer": "4.4.3",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.4.1",
"@types/jest-environment-puppeteer": "4.3.2",
"@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": "5.4.3",
"@types/puppeteer": "3.0.1",
"@types/q": "1.5.1",
"@types/react": "17.0.0",
"@types/react-dom": "17.0.0",
@@ -176,7 +173,7 @@
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1",
"prettier": "2.2.1",
"puppeteer": "8.0.0",
"puppeteer": "4.0.0",
"raw-loader": "0.5.1",
"rimraf": "3.0.0",
"sinon": "3.2.1",
@@ -238,4 +235,4 @@
"prettier": {
"printWidth": 120
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
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";
@@ -45,7 +44,7 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
const replaceKnownError = (errorMessage: string): string => {
if (
userContext.subscriptionType === SubscriptionType.Internal &&
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";

View File

@@ -1,5 +1,28 @@
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,8 +1,6 @@
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;
@@ -18,7 +16,6 @@ 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 { MessageTypes } from "../Contracts/ExplorerContracts";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
import * as Constants from "./Constants";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
export interface CachedDataPromise<T> {
deferred: Q.Deferred<T>;
@@ -61,21 +61,6 @@ 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

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

View File

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

View File

@@ -2,16 +2,18 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
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";
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";
}
}
}

View File

@@ -1,61 +1,55 @@
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;
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,5 @@
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 dayjs from "dayjs";
import {
ChartSettings,
DataPayload,
@@ -16,6 +11,11 @@ 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);
sendReadyMessage();
sendMessage("ready");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import * as StringUtils from "../../../Utils/StringUtils";
import { StringUtils } from "../../../Utils/StringUtils";
import { KeyCodes } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 * as UrlUtility from "../../../Common/UrlUtility";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
@@ -74,6 +74,8 @@ 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;
@@ -103,6 +105,8 @@ 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
@@ -117,6 +121,8 @@ 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 * as StringUtils from "../../../Utils/StringUtils";
import { StringUtils } from "../../../Utils/StringUtils";
import { userContext } from "../../../UserContext";
import { TerminalQueryParams } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";

View File

@@ -13,8 +13,6 @@ import {
LinkBase,
Separator,
TooltipHost,
Spinner,
SpinnerSize,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient";
@@ -31,14 +29,10 @@ export interface GalleryCardComponentProps {
onFavoriteClick: () => void;
onUnfavoriteClick: () => void;
onDownloadClick: () => void;
onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
onDeleteClick: () => void;
}
interface GalleryCardComponentState {
isDeletingPublishedNotebook: boolean;
}
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> {
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
@@ -46,15 +40,6 @@ 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;
@@ -74,110 +59,91 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)}
>
{this.state.isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!this.state.isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<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 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>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<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 })
)
)}
<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>
</Card.Section>
))
) : (
<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
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
</span>
</Card.Section>
)}
</Card>
);

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,10 +11,11 @@ import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { Dialog, DialogProps, TextFieldProps } from "../Dialog";
import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent";
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";
@@ -102,7 +103,7 @@ export class NotebookViewerComponent
);
const notebook: Notebook = await response.json();
GalleryUtils.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
this.removeNotebookViewerLink(notebook, this.props.galleryItem?.newCellId);
this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook, showProgressBar: false });
@@ -132,6 +133,17 @@ 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">
@@ -167,7 +179,7 @@ export class NotebookViewerComponent
hidePrompts: this.props.hidePrompts,
})}
{this.state.dialogProps && <Dialog {...this.state.dialogProps} />}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
</div>
);
}

View File

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

View File

@@ -221,6 +221,8 @@ 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(),
});
@@ -229,6 +231,8 @@ 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(),
},
@@ -238,6 +242,8 @@ 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,51 +1,50 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
import { AuthType } from "../../../AuthType";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
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 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 { 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 { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
import {
ConflictResolutionComponent,
ConflictResolutionComponentProps,
} from "./SettingsSubComponents/ConflictResolutionComponent";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
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,
GeospatialConfigType,
TtlType,
ChangeFeedPolicyState,
SettingsV2TabTypes,
getTabTitle,
isDirty,
AddMongoIndexProps,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
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 { isEmpty } from "underscore";
interface SettingsV2TabInfo {
tab: SettingsV2TabTypes;
@@ -318,6 +317,8 @@ 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,14 +328,16 @@ 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),
@@ -407,9 +410,10 @@ 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(),
},
@@ -700,13 +704,15 @@ 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(),
},
@@ -805,9 +811,10 @@ 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(),
},
@@ -817,9 +824,10 @@ 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),
@@ -862,13 +870,16 @@ 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(),
},
@@ -876,18 +887,6 @@ 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,
@@ -1005,11 +1004,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
});
} else if (this.container.isPreferredApiMongoDB()) {
const mongoIndexTabContext = this.getMongoIndexTabContent(mongoIndexingPolicyComponentProps);
if (mongoIndexTabContext) {
if (isEmpty(this.container.features())) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexTabContext,
content: mongoIndexingPolicyAADError,
});
} else if (this.container.isEnableMongoCapabilityPresent()) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />,
});
}
}

View File

@@ -23,6 +23,7 @@ import {
ITextStyles,
IDetailsRowStyles,
IStackStyles,
IIconStyles,
IDetailsListStyles,
IDropdownStyles,
ISeparatorStyles,
@@ -115,6 +116,8 @@ 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)" isExpandedByDefault={true}>
<CollapsibleSectionComponent title="Current index(es)">
{
<>
<DetailsList
@@ -266,7 +266,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return (
<Stack styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Index(es) to be dropped" isExpandedByDefault={true}>
<CollapsibleSectionComponent title="Index(es) to be dropped">
{indexesToBeDropped.length > 0 && (
<DetailsList
styles={customDetailsListStyles}

View File

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

View File

@@ -1,24 +1,23 @@
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 { configContext, Platform } from "../../../../ConfigContext";
import * as DataModels from "../../../../Contracts/DataModels";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import * as ViewModels from "../../../../Contracts/ViewModels";
import * as DataModels from "../../../../Contracts/DataModels";
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 { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { Link, Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
import { configContext, Platform } from "../../../../ConfigContext";
export interface ScaleComponentProps {
collection: ViewModels.Collection;
@@ -80,7 +79,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMaxRUs = (): number => {
if (userContext.isTryCosmosDBSubscription) {
if (this.props.container?.isTryCosmosDBSubscription()) {
return Constants.TryCosmosExperience.maxRU;
}
@@ -92,7 +91,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMinRUs = (): number => {
if (userContext.isTryCosmosDBSubscription) {
if (this.props.container?.isTryCosmosDBSubscription()) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
}
@@ -173,6 +172,7 @@ 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,16 +1,17 @@
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,53 +1,55 @@
import React from "react";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import {
Checkbox,
getTextFieldStyles,
getToolTipContainer,
noLeftPaddingCheckBoxStyle,
titleAndInputStackProps,
checkBoxAndInputStackProps,
getChoiceGroupStyles,
messageBarStyles,
getEstimatedSpendingElement,
getAutoPilotV3SpendElement,
manualToAutoscaleDisclaimerElement,
saveThroughputWarningMessage,
ManualEstimatedSpendingDisplayProps,
AutoscaleEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown,
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import {
Text,
TextField,
ChoiceGroup,
FontIcon,
IChoiceGroupOption,
IColumn,
Checkbox,
Stack,
Label,
Link,
MessageBar,
Stack,
Text,
TextField,
FontIcon,
IColumn,
} from "office-ui-fabric-react";
import React from "react";
import { Features } from "../../../../../Common/Constants";
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";
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 { Features } from "../../../../../Common/Constants";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
export interface ThroughputInputAutoPilotV3Props {
databaseAccount: DataModels.DatabaseAccount;
databaseName: string;
collectionName: string;
serverId: string;
throughput: number;
throughputBaseline: number;
onThroughputChange: (newThroughput: number) => void;
@@ -180,6 +182,7 @@ 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;
@@ -189,7 +192,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,
userContext.portalEnv,
serverId,
regions,
multimaster,
isDirty ? this.props.throughput : undefined
@@ -197,7 +200,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
} else {
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
this.props.maxAutoPilotThroughputBaseline,
userContext.portalEnv,
serverId,
regions,
multimaster,
isDirty ? this.props.maxAutoPilotThroughput : undefined
@@ -455,8 +458,11 @@ 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`] = `
}
}
>
<Icon
<StyledIconBase
ariaLabel="Info"
iconName="Info"
styles={

View File

@@ -28,11 +28,16 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function],
"container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -435,6 +440,22 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -674,6 +695,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -780,7 +804,6 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -836,6 +859,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -920,13 +946,16 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -936,6 +965,9 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -944,6 +976,7 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -959,6 +992,13 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -981,7 +1021,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"onToggleKeyDown": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -1011,6 +1051,24 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -1057,6 +1115,15 @@ 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,
@@ -1097,8 +1164,22 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -1156,6 +1237,9 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -1225,11 +1309,16 @@ exports[`SettingsComponent renders 1`] = `
}
container={
Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -1632,6 +1721,22 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -1871,6 +1976,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -1977,7 +2085,6 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -2033,6 +2140,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -2117,13 +2227,16 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -2133,6 +2246,9 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -2141,6 +2257,7 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -2156,6 +2273,13 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -2178,7 +2302,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"onToggleKeyDown": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -2208,6 +2332,24 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -2254,6 +2396,15 @@ 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,
@@ -2294,8 +2445,22 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -2353,6 +2518,9 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -2435,11 +2603,16 @@ exports[`SettingsComponent renders 1`] = `
"changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function],
"container": Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -2842,6 +3015,22 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -3081,6 +3270,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -3187,7 +3379,6 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -3243,6 +3434,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -3327,13 +3521,16 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -3343,6 +3540,9 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -3351,6 +3551,7 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -3366,6 +3567,13 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -3388,7 +3596,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"onToggleKeyDown": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -3418,6 +3626,24 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -3464,6 +3690,15 @@ 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,
@@ -3504,8 +3739,22 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -3563,6 +3812,9 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],
@@ -3632,11 +3884,16 @@ exports[`SettingsComponent renders 1`] = `
}
container={
Explorer {
"_addSynapseLinkDialogProps": [Function],
"_closeModalDialog": [Function],
"_closeSynapseLinkModalDialog": [Function],
"_dialogProps": [Function],
"_importExplorerConfigComplete": false,
"_isAfecFeatureRegistered": [Function],
"_isInitializingNotebooks": false,
"_isInitializingSparkConnectionInfo": false,
"_isSystemDatabasePredicate": [Function],
"_openShareDialog": [Function],
"_panes": Array [
AddDatabasePane {
"autoPilotUsageCost": [Function],
@@ -4039,6 +4296,22 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
UploadItemsPane {
"container": [Circular],
"fileUploadSummaryText": [Function],
@@ -4278,6 +4551,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"addDatabaseText": [Function],
"addSynapseLinkDialog": DialogComponentAdapter {
"parameters": [Function],
},
"addTableEntityPane": AddTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -4384,7 +4660,6 @@ exports[`SettingsComponent renders 1`] = `
},
"clickHostedAccountSwitch": [Function],
"clickHostedDirectorySwitch": [Function],
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"collectionCreationDefaults": Object {
@@ -4440,6 +4715,9 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"deleteDatabaseText": [Function],
"dialogComponentAdapter": DialogComponentAdapter {
"parameters": [Function],
},
"editTableEntityPane": EditTableEntityPane {
"addButtonLabel": "Add Property",
"attributeNameLabel": "Property Name",
@@ -4524,13 +4802,16 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function],
"isGitHubPaneEnabled": [Function],
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
@@ -4540,6 +4821,9 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isReadToggled": [Function],
"isReadWriteToggled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -4548,6 +4832,7 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -4563,6 +4848,13 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function],
},
"memoryUsageInfo": [Function],
"mostRecentActivity": MostRecentActivity {
"container": [Circular],
"storedData": Object {
"itemsMap": Object {},
"schemaVersion": "1",
},
},
"newVertexPane": NewVertexPane {
"buildString": [Function],
"container": [Circular],
@@ -4585,7 +4877,7 @@ exports[`SettingsComponent renders 1`] = `
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
"openDialog": undefined,
"onToggleKeyDown": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
@@ -4615,6 +4907,24 @@ exports[`SettingsComponent renders 1`] = `
"refreshDatabaseAccount": [Function],
"refreshNotebookList": [Function],
"refreshTreeTitle": [Function],
"renewAdHocAccessPane": RenewAdHocAccessPane {
"_renewShareAccess": [Function],
"accessKey": [Function],
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "renewadhocaccesspane",
"isExecuting": [Function],
"isHelperImageVisible": [Function],
"isTemplateReady": [Function],
"onShowHelperImageClick": [Function],
"onShowHelperImageKeyPress": [Function],
"title": [Function],
"visible": [Function],
},
"renewToken": [Function],
"renewTokenError": [Function],
"resourceTokenCollection": [Function],
"resourceTokenCollectionId": [Function],
"resourceTokenDatabaseId": [Function],
@@ -4661,6 +4971,15 @@ 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,
@@ -4701,8 +5020,22 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function],
"visible": [Function],
},
"shareAccessData": [Function],
"shareAccessToggleState": [Function],
"shareAccessUrl": [Function],
"shareTokenCopyHelperText": [Function],
"shareUrlCopyHelperText": [Function],
"shouldShowContextSwitchPrompt": [Function],
"shouldShowDataAccessExpiryDialog": [Function],
"shouldShowShareDialogContents": [Function],
"signInAad": [Function],
"sparkClusterConnectionInfo": [Function],
"splashScreenAdapter": SplashScreenComponentAdapter {
"clearMostRecent": [Function],
"container": [Circular],
"forceRender": [Function],
"parameters": [Function],
},
"splitter": Splitter {
"bounds": Object {
"max": 400,
@@ -4760,6 +5093,9 @@ exports[`SettingsComponent renders 1`] = `
"openedTabs": [Function],
},
"toggleLeftPaneExpandedKeyPress": [Function],
"toggleRead": [Function],
"toggleReadWrite": [Function],
"tokenForRenewal": [Function],
"uploadFilePane": UploadFilePane {
"container": [Circular],
"extensions": [Function],

View File

@@ -1,7 +1,7 @@
import React from "react";
import { shallow } from "enzyme";
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
import { NumberUiType, SmartUiInput, DescriptionType } from "../../../SelfServe/SelfServeTypes";
import { NumberUiType, SmartUiInput } from "../../../SelfServe/SelfServeTypes";
describe("SmartUiComponent", () => {
const exampleData: SmartUiDescriptor = {
@@ -18,12 +18,10 @@ 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,26 +1,24 @@
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 * as React from "react";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import { Slider } from "office-ui-fabric-react/lib/Slider";
import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
import { Text } from "office-ui-fabric-react/lib/Text";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
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 { 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 {
ChoiceItem,
Description,
DescriptionType,
Info,
InputType,
InputTypeValue,
NumberUiType,
SmartUiInput,
} from "../../../SelfServe/SelfServeTypes";
import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
import { TFunction } from "i18next";
/**
* Generic UX renderer
@@ -31,14 +29,15 @@ import "./SmartUiComponent.less";
*/
interface BaseDisplay {
labelTKey: string;
dataFieldName: string;
errorMessage?: string;
type: InputTypeValue;
}
interface BaseInput extends BaseDisplay {
labelTKey: string;
placeholderTKey?: string;
errorMessage?: string;
}
/**
@@ -68,8 +67,7 @@ interface ChoiceInput extends BaseInput {
}
interface DescriptionDisplay extends BaseDisplay {
description?: Description;
isDynamicDescription?: boolean;
description: Description;
}
type AnyDisplay = NumberInput | BooleanInput | StringInput | ChoiceInput | DescriptionDisplay;
@@ -125,28 +123,25 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private renderInfo(info: Info): JSX.Element {
return (
info && (
<Text>
{this.props.getTranslation(info.messageTKey)}{" "}
{info.link && (
<Link href={info.link.href} target="_blank">
{this.props.getTranslation(info.link.textTKey)}
</Link>
)}
</Text>
)
<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>
);
}
private renderTextInput(input: StringInput, labelId: string, labelElement: JSX.Element): JSX.Element {
private renderTextInput(input: StringInput): 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 (
<Stack>
{labelElement}
<div className="stringInputContainer">
<TextField
id={`${input.dataFieldName}-textField-input`}
aria-labelledby={labelId}
label={this.props.getTranslation(input.labelTKey)}
type="text"
value={value || ""}
placeholder={this.props.getTranslation(input.placeholderTKey)}
@@ -154,42 +149,32 @@ 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,
},
},
},
}}
/>
</Stack>
</div>
);
}
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>
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>
);
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 {
@@ -235,12 +220,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return undefined;
};
private renderNumberInput(input: NumberInput, labelId: string, labelElement: JSX.Element): JSX.Element {
private renderNumberInput(input: NumberInput): JSX.Element {
const { labelTKey, min, max, dataFieldName, step } = input;
const props = {
label: this.props.getTranslation(labelTKey),
min: min,
max: max,
ariaLabel: this.props.getTranslation(labelTKey),
ariaLabel: labelTKey,
step: step,
};
@@ -248,73 +234,71 @@ 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>
{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 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>
);
} else if (input.uiType === NumberUiType.Slider) {
return (
<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>
<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>
);
} else {
return <>Unsupported number UI type {input.uiType}</>;
}
}
private renderBooleanInput(input: BooleanInput, labelId: string, labelElement: JSX.Element): JSX.Element {
private renderBooleanInput(input: BooleanInput): 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 (
<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>
<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 } }}
/>
);
}
private renderChoiceInput(input: ChoiceInput, labelId: string, labelElement: JSX.Element): JSX.Element {
const { defaultKey, dataFieldName, choices, placeholderTKey } = input;
private renderChoiceInput(input: ChoiceInput): JSX.Element {
const { labelTKey, 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;
@@ -322,67 +306,53 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
selectedKey = "";
}
return (
<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>
<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,
}}
/>
);
}
private renderError(errorMessage: string): JSX.Element {
return <MessageBar messageBarType={MessageBarType.error}>Error: {errorMessage}</MessageBar>;
private renderError(input: AnyDisplay): JSX.Element {
return <MessageBar messageBarType={MessageBarType.error}>Error: {input.errorMessage}</MessageBar>;
}
private renderElement(input: AnyDisplay, info: Info): JSX.Element {
private renderDisplay(input: AnyDisplay): JSX.Element {
if (input.errorMessage) {
return this.renderError(input.errorMessage);
return this.renderError(input);
}
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 || "isDynamicDescription" in input) {
return this.renderDescription(input as DescriptionDisplay, labelId, labelElement);
if ("description" in input) {
return this.renderDescription(input as DescriptionDisplay);
}
return this.renderTextInput(input as StringInput, labelId, labelElement);
return this.renderTextInput(input as StringInput);
case "number":
return this.renderNumberInput(input as NumberInput, labelId, labelElement);
return this.renderNumberInput(input as NumberInput);
case "boolean":
return this.renderBooleanInput(input as BooleanInput, labelId, labelElement);
return this.renderBooleanInput(input as BooleanInput);
case "object":
return this.renderChoiceInput(input as ChoiceInput, labelId, labelElement);
return this.renderChoiceInput(input as ChoiceInput);
default:
throw new Error(`Unknown input type: ${input.type}`);
}
@@ -393,7 +363,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
<Stack.Item>{node.input && this.renderElement(node.input, node.info as Info)}</Stack.Item>
<Stack.Item>
{node.info && this.renderInfo(node.info as Info)}
{node.input && this.renderDisplay(node.input)}
</Stack.Item>
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack>
);

View File

@@ -9,7 +9,25 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
}
>
<StackItem />
<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>
<div
key="description"
>
@@ -22,23 +40,18 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<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>
<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>
</StackItem>
</Stack>
</div>
@@ -54,55 +67,53 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<Stack>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
tokens={
Object {
"childrenGap": 2,
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
>
<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>
}
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,
},
}
}
/>
</Stack>
</StackItem>
</Stack>
@@ -119,41 +130,37 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<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>
<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>
</StackItem>
</Stack>
</div>
@@ -190,32 +197,35 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<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,
<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,
},
},
}
},
}
type="text"
value=""
/>
</Stack>
</Stack>
}
type="text"
value=""
/>
</div>
</StackItem>
</Stack>
</div>
@@ -231,33 +241,22 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<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>
<StyledToggleBase
checked={false}
disabled={true}
id="analyticalStore-toggle-input"
label="Analytical Store"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
/>
</StackItem>
</Stack>
</div>
@@ -273,53 +272,47 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<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>
<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,
},
}
}
/>
</StackItem>
</Stack>
</div>
@@ -335,7 +328,25 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
}
>
<StackItem />
<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>
<div
key="description"
>
@@ -348,23 +359,18 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<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>
<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>
</StackItem>
</Stack>
</div>
@@ -380,55 +386,53 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<Stack>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
tokens={
Object {
"childrenGap": 2,
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
>
<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>
}
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,
},
}
}
/>
</Stack>
</StackItem>
</Stack>
@@ -445,40 +449,36 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<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>
<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>
</StackItem>
</Stack>
</div>
@@ -515,31 +515,34 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<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,
<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,
},
},
}
},
}
type="text"
value=""
/>
</Stack>
</Stack>
}
type="text"
value=""
/>
</div>
</StackItem>
</Stack>
</div>
@@ -555,32 +558,21 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<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>
<StyledToggleBase
checked={false}
id="analyticalStore-toggle-input"
label="Analytical Store"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
/>
</StackItem>
</Stack>
</div>
@@ -596,52 +588,46 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<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>
<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,
},
}
}
/>
</StackItem>
</Stack>
</div>

View File

@@ -1,20 +0,0 @@
@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

@@ -1,302 +0,0 @@
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>
);
};

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