Compare commits

...

76 Commits

Author SHA1 Message Date
hardiknai-techm
9acf9485cb host fix 2021-05-11 20:52:54 +05:30
hardiknai-techm
9751f0dd81 resolve merge conflict 2021-05-11 20:37:53 +05:30
victor-meng
4ed8fe9e7d Remove old add collection pane (#767) 2021-05-10 20:10:48 -05:00
Srinath Narayanan
4c506da7b9 Added metrics blade link and fixed SelfServe bugs (#764)
* Added metrics blade link

* fixed lint error
2021-05-10 17:36:50 -07:00
Hardikkumar Nai
a81b1a40a3 Use @fluentui/react DocumentCard (#715)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-10 14:17:37 -05:00
Hardikkumar Nai
9d5c9d6296 Migrate Add Database Panel to React (#597)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-10 14:02:14 -05:00
hardiknai-techm
e0c8f32a7e Merge branch 'move_add_Database_Panel_to_react' of https://github.com/Azure/cosmos-explorer into move_add_Database_Panel_to_react 2021-05-10 23:41:51 +05:30
hardiknai-techm
46eda44fd7 snapshort update 2021-05-10 23:41:13 +05:30
Steve Faulkner
ab0dd728b9 Update snapshot 2021-05-10 13:09:13 -05:00
hardiknai-techm
c50543c341 address comment 2021-05-10 21:56:23 +05:30
hardiknai-techm
a675124bdc Merge branch 'master' of https://github.com/Azure/cosmos-explorer into move_add_Database_Panel_to_react 2021-05-10 17:56:04 +05:30
hardiknai-techm
05eae478d0 resolve master merge conflict 2021-05-10 17:55:26 +05:30
Steve Faulkner
7efa8ca58f Remove unused Switch Directory Pane (#766) 2021-05-09 22:37:18 -05:00
Hardikkumar Nai
487fbf2072 Remove genericRightPaneComponent and create RightPaneWrapper with form (#679)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-09 19:22:44 -05:00
Sunil Kumar Yadav
aa308b3e4d Enable TypeScript noImplicitThis (#761) 2021-05-07 10:25:19 -05:00
Steve Faulkner
db227084be Remove IE11 from Coding Guidelines 2021-05-07 10:04:47 -05:00
hardiknai-techm
6b0e972564 resolve merge conflict 2021-05-07 12:53:48 +05:30
hardiknai-techm
8075072f76 correct base on some suggestion 2021-05-07 12:51:53 +05:30
victor-meng
d62baf327b Change create wildcard index default value to false for mongo 3.2 (#759)
* Change create wildcard index default value to false for mongo 3.2

* Update snapshots
2021-05-06 21:27:47 -05:00
Jordi Bunster
78eafe1aec Remove NotebookViewerTab (#749)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/749)
2021-05-07 00:20:25 +00:00
Sunil Kumar Yadav
a91ea6c1e4 Remove old Add/Edit Table Entity code (#755) 2021-05-06 18:51:45 -05:00
victor-meng
5606ef3266 Fix table edit entity bug and add collection panel bug for connection string users (#757)
* Fix table edit entity bug and add collection panel bug for connection string users

* Remove parseInt for int64
2021-05-06 18:51:22 -05:00
Steve Faulkner
503f044a70 Update strict mode files (#753) 2021-05-06 12:35:24 -05:00
Steve Faulkner
0e0e6a2257 more 2021-05-05 19:31:07 -05:00
Steve Faulkner
4299b86c55 Revert "Pane cleanup"
This reverts commit b169fba45b.
2021-05-05 19:29:56 -05:00
Steve Faulkner
b169fba45b Pane cleanup 2021-05-05 19:26:23 -05:00
Steve Faulkner
2b576d6b8a Remove core-js 2021-05-05 19:22:53 -05:00
Steve Faulkner
0c009caea6 Fix test 2021-05-05 19:15:26 -05:00
Steve Faulkner
33048b2c98 Fixes 2021-05-05 19:00:23 -05:00
Steve Faulkner
3d4a24b2fd rename 2021-05-05 18:42:30 -05:00
Steve Faulkner
2ad0236c73 Fix 2021-05-05 18:41:37 -05:00
Steve Faulkner
c180d6a0cd Merge with mater 2021-05-05 18:31:54 -05:00
Hardikkumar Nai
23223cfb23 Upgrade Fluent UI v8 (#688)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 18:26:03 -05:00
Steve Faulkner
bd47e5ed49 Remove unused Explorer methods (#750) 2021-05-05 17:35:35 -05:00
Hardikkumar Nai
8c05ac740c Remove Explorer.databaseAccount (#717)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 16:54:50 -05:00
Steve Faulkner
dd747ee1ee Resolve conflict 2021-05-05 15:23:37 -05:00
Hardikkumar Nai
fdd12b41c4 Remove Explorer.defaultExperience (#680)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 13:00:01 -05:00
Tanuj Mittal
d1d28885d0 Use CellOutputViewer for SchemaAnalyzer (#747) 2021-05-05 11:12:27 +05:30
Hardikkumar Nai
aab624e241 Create End to End Graph Test (#745) 2021-05-04 23:01:13 -05:00
Tanuj Mittal
181b53c858 Disable HTML in markdown cell for NotebookReadOnlyRenderer (#746)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/746)

We have a custom implementation of `Markdown` cell that we use in `NotebookRenderer`. This custom implementation disables HTML rendering in markdown cells. This change uses the same for `NotebookReadOnlyRenderer` which we use for Gallery.
2021-05-04 21:43:39 +00:00
Srinath Narayanan
1fdb339fbf Enable the "Enable notebooks" button (#734)
* enable notebooks initial commit

* use only first write location

* addressed PR comments

* Minor edits
2021-05-04 13:06:27 -07:00
Jordi Bunster
b7579d5c8b eslint switch/case exhaustiveness check rule (#739) 2021-05-04 09:12:54 -07:00
hardiknai-techm
5485a0d3c6 resolve merge conflict 2021-05-04 13:03:01 +05:30
vaidankarswapnil
038f3ee684 Move GitHub repos panel to react (#638)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-03 19:56:47 -05:00
victor-meng
4efacace16 add collection panel improvements (#630)
Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-04-30 10:23:34 -07:00
hardiknai-techm
7b6975761f resolve format and compile issue 2021-04-29 08:41:14 +05:30
hardiknai-techm
ed44c8640a Merge branch 'master' of https://github.com/Azure/cosmos-explorer into move_add_Database_Panel_to_react 2021-04-29 08:36:37 +05:30
hardiknai-techm
ade9049d80 update snapshrot test case 2021-04-28 20:36:04 +05:30
hardiknai-techm
6a3f50651e marge master conflict 2021-04-28 19:58:09 +05:30
hardiknai-techm
8de3edd0f8 remove div wrapper class 2021-04-24 15:49:10 +05:30
hardiknai-techm
9f0d5658c7 Merge branch 'genericRightPaneComponent' of https://github.com/Azure/cosmos-explorer into move_add_Database_Panel_to_react 2021-04-24 13:41:03 +05:30
hardiknai-techm
28bbba5522 addDatabasePane provide backward compatibility 2021-04-24 13:39:13 +05:30
hardiknai-techm
42552bad07 addDatabasePane provide backward compatibility 2021-04-24 13:32:00 +05:30
hardiknai-techm
0aeb16fb96 resolve master branch conflict 2021-04-24 11:10:38 +05:30
hardiknai-techm
3e011f939d resolve conflict master branch 2021-04-23 19:13:13 +05:30
hardiknai-techm
43aafde25f revert knockout component 2021-04-19 18:43:10 +05:30
hardiknai-techm
9aafadf346 resolve master merge conflict 2021-04-19 17:52:28 +05:30
hardiknai-techm
ba25eea41e update sanpshort test case 2021-04-16 15:59:48 +05:30
hardiknai-techm
e17fe25292 Some panel use GenenricRightPanel and Some panel use RightPanelForm 2021-04-16 15:53:44 +05:30
hardiknai-techm
9494c9cd55 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into genericRightPaneComponent 2021-04-16 14:55:40 +05:30
hardiknai-techm
5c9ab15b3a remove genericRightPaneComponent and create RightPaneWrapper with form 2021-04-15 11:47:05 +05:30
hardiknai-techm
57d8df6608 npm install perfomr due to error 2021-04-07 10:30:51 +05:30
hardiknai-techm
2dc33c0333 remove AddDatabasePane knowckout.js code 2021-04-07 05:39:29 +05:30
hardiknai-techm
dfa97936ed resolve master merge conflict 2021-04-07 05:33:29 +05:30
hardiknai-techm
6a12ba27d9 functional component convert and use fluent ui 2021-04-07 05:21:09 +05:30
hardiknai-techm
f9ffff3c11 functional component convert and use fluent ui 2021-04-07 05:17:31 +05:30
hardiknai-techm
538b2700ef added snapshort test 2021-04-05 15:44:22 +05:30
hardiknai-techm
f468a0c36e default auto scale selected 2021-04-05 11:28:21 +05:30
hardiknai-techm
fb7526a0d0 api call with string autoPilotMaxThroughput 2021-04-05 09:09:38 +05:30
hardiknai-techm
63afb5dd6c format and compile 2021-03-31 18:36:12 +05:30
hardiknai-techm
6a1f8ce822 format and compile 2021-03-31 18:24:25 +05:30
hardiknai-techm
3b09a19ce5 resolve merge master conflict 2021-03-31 17:08:50 +05:30
hardiknai-techm
299c765e7b resolve autoscale save option 2021-03-31 17:02:03 +05:30
hardiknai-techm
8d206c39b1 remove knockout component 2021-03-30 20:16:55 +05:30
hardiknai-techm
b68e601e02 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into move_add_Database_Panel_to_react 2021-03-29 16:21:23 +05:30
Your Name
201310eea4 migrate AddDatabase component 2021-03-24 12:40:20 +05:30
245 changed files with 17606 additions and 43170 deletions

View File

@@ -1,4 +1,5 @@
**/node_modules/
src/**/__mocks__/**/*
dist/
Contracts/
src/Api/Apis.ts
@@ -53,7 +54,6 @@ src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
src/Explorer/Controls/Toolbar/IToolbarAction.ts
@@ -108,24 +108,18 @@ src/Explorer/Notebook/NotebookUtil.ts
src/Explorer/OpenActions.test.ts
src/Explorer/OpenActions.ts
src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddCollectionPane.test.ts
src/Explorer/Panes/AddCollectionPane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/EditTableEntityPane.ts
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
src/Explorer/Panes/Tables/TableEntityPane.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts

View File

@@ -11,6 +11,7 @@ module.exports = {
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: ["./tsconfig.json", "./tsconfig.test.json"],
ecmaFeatures: {
jsx: true,
},
@@ -35,6 +36,7 @@ module.exports = {
rules: {
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
curly: "error",
"@typescript-eslint/switch-exhaustiveness-check": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",

View File

@@ -138,6 +138,7 @@ jobs:
matrix:
test-file:
- ./test/cassandra/container.spec.ts
- ./test/graph/container.spec.ts
- ./test/sql/container.spec.ts
- ./test/mongo/container.spec.ts
- ./test/selfServe/selfServeExample.spec.ts

View File

@@ -188,7 +188,3 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
✅ 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

@@ -69,7 +69,8 @@ module.exports = {
moduleNameMapper: {
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
"@fluentui/react/lib/(.*)$": "@fluentui/react/lib-commonjs/$1", // https://github.com/microsoft/fluentui/wiki/Version-8-release-notes
"monaco-editor/(.*)$": "<rootDir>/__mocks__/monaco-editor",
"^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",

View File

@@ -1757,7 +1757,7 @@ input::-webkit-calendar-picker-indicator {
cursor: pointer;
}
.contextual-pane .paneMainContent {
.paneMainContent {
flex: 1;
padding-left: 34px;
padding-right: 34px;

31512
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.14.3",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1",
@@ -42,8 +43,6 @@
"@testing-library/jest-dom": "5.11.9",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
@@ -76,7 +75,6 @@
"monaco-editor": "0.18.1",
"ms": "2.1.3",
"msal": "1.4.4",
"office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42",
@@ -128,8 +126,8 @@
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"buffer": "5.1.0",
@@ -191,7 +189,7 @@
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
"copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase",

View File

@@ -0,0 +1,10 @@
.schema-analyzer-cell-outputs {
padding: 10px;
}
.schema-analyzer-cell-output {
margin-bottom: 20px;
padding: 10px;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 13%) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 11%) 0px 0.3px 0.9px 0px;
}

View File

@@ -11,13 +11,14 @@ import * as ReactDOM from "react-dom";
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
import "../Explorer/Notebook/NotebookRenderer/base.css";
import "../Explorer/Notebook/NotebookRenderer/default.css";
import "./CellOutputViewer.less";
import { TransformMedia } from "./TransformMedia";
export interface CellOutputViewerProps {
id: string;
contentRef: ContentRef;
hidden: boolean;
expanded: boolean;
outputsContainerClassName: string;
outputClassName: string;
outputs: OnDiskOutput[];
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void;
}
@@ -34,27 +35,26 @@ const onInit = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as CellOutputViewerProps;
const outputs = (
<div
data-iframe-height
className={`nteract-cell-outputs ${props.hidden ? "hidden" : ""} ${props.expanded ? "expanded" : ""}`}
>
<div data-iframe-height className={props.outputsContainerClassName}>
{props.outputs?.map((output, index) => (
<Output output={createImmutableOutput(output)} key={index}>
<TransformMedia
output_type={"display_data"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<TransformMedia
output_type={"execute_result"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<KernelOutputError />
<StreamText />
</Output>
<div className={props.outputClassName} key={index}>
<Output output={createImmutableOutput(output)} key={index}>
<TransformMedia
output_type={"display_data"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<TransformMedia
output_type={"execute_result"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<KernelOutputError />
<StreamText />
</Output>
</div>
))}
</div>
);

View File

@@ -65,28 +65,18 @@ export class ClientDefaults {
public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000;
}
export class AccountKind {
public static DocumentDB: string = "DocumentDB";
public static MongoDB: string = "MongoDB";
public static Parse: string = "Parse";
public static GlobalDocumentDB: string = "GlobalDocumentDB";
public static Default: string = AccountKind.DocumentDB;
export enum AccountKind {
DocumentDB = "DocumentDB",
MongoDB = "MongoDB",
Parse = "Parse",
GlobalDocumentDB = "GlobalDocumentDB",
Default = "DocumentDB",
}
export class CorrelationBackend {
public static Url: string = "https://aka.ms/cosmosdbanalytics";
}
export class DefaultAccountExperience {
public static DocumentDB: string = "DocumentDB";
public static Graph: string = "Graph";
public static MongoDB: string = "MongoDB";
public static ApiForMongoDB: string = "Azure Cosmos DB for MongoDB API";
public static Table: string = "Table";
public static Cassandra: string = "Cassandra";
public static Default: string = DefaultAccountExperience.DocumentDB;
}
export class CapabilityNames {
public static EnableTable: string = "EnableTable";
public static EnableGremlin: string = "EnableGremlin";

View File

@@ -1,5 +1,5 @@
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { configContext, Platform, updateConfigContext, resetConfigContext } from "../ConfigContext";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
@@ -91,7 +91,6 @@ describe("endpoint", () => {
location: "foo",
type: "foo",
kind: "foo",
tags: [],
properties: {
documentEndpoint: "bar",
gremlinEndpoint: "foo",

View File

@@ -43,12 +43,7 @@ export const endpoint = () => {
const location = _global.parent ? _global.parent.location : _global.location;
return configContext.EMULATOR_ENDPOINT || location.origin;
}
return (
userContext.endpoint ||
(userContext.databaseAccount &&
userContext.databaseAccount.properties &&
userContext.databaseAccount.properties.documentEndpoint)
);
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
};
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {

View File

@@ -1,4 +1,4 @@
import { DatePicker, TextField } from "office-ui-fabric-react";
import { DatePicker, TextField } from "@fluentui/react";
import React, { FunctionComponent } from "react";
export interface TableEntityProps {

View File

@@ -61,7 +61,7 @@ export function queryDocuments(
query: string,
continuationToken?: string
): Promise<QueryResponse> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -121,7 +121,7 @@ export function readDocument(
collection: Collection,
documentId: DocumentId
): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/");
@@ -167,7 +167,7 @@ export function createDocument(
partitionKeyProperty: string,
documentContent: unknown
): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -206,7 +206,7 @@ export function updateDocument(
documentId: DocumentId,
documentContent: string
): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -247,7 +247,7 @@ export function updateDocument(
}
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -289,7 +289,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams
): Promise<DataModels.Collection> {
const databaseAccount = userContext.databaseAccount;
const { databaseAccount } = userContext;
const shardKey: string = params.partitionKey?.paths[0];
const mongoParams: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,

View File

@@ -1,8 +1,8 @@
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
const notificationsPath = () => {
switch (configContext.platform) {
@@ -20,9 +20,7 @@ export const fetchPortalNotifications = async (): Promise<DataModels.Notificatio
return [];
}
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;

View File

@@ -182,11 +182,8 @@ export class QueriesClient {
}
public getResourceId(): string {
const databaseAccount = userContext.databaseAccount;
const databaseAccountName = (databaseAccount && databaseAccount.name) || "";
const subscriptionId = userContext.subscriptionId || "";
const resourceGroup = userContext.resourceGroup || "";
const { databaseAccount, subscriptionId = "", resourceGroup = "" } = userContext;
const databaseAccountName = databaseAccount?.name || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
}

View File

@@ -8,7 +8,7 @@ import {
Stack,
TextField,
TooltipHost,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg";
import EditIcon from "../../images/Edit_entity.svg";

View File

@@ -0,0 +1,16 @@
import { Icon, TooltipHost } from "@fluentui/react";
import * as React from "react";
export interface TooltipProps {
children: string;
}
export const InfoTooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
return (
<span>
<TooltipHost content={children}>
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
</TooltipHost>
</span>
);
};

View File

@@ -1,24 +0,0 @@
import { useId } from "@uifabric/react-hooks";
import { ITooltipHostStyles, TooltipHost } from "office-ui-fabric-react/lib/Tooltip";
import * as React from "react";
import InfoBubble from "../../../images/info-bubble.svg";
const calloutProps = { gapSpace: 0 };
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: "inline-block" } };
export interface TooltipProps {
children: string;
}
export const Tooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
const tooltipId = useId("tooltip");
return children ? (
<span>
<TooltipHost content={children} id={tooltipId} calloutProps={calloutProps} styles={hostStyles}>
<img className="infoImg" src={InfoBubble} alt="More information" />
</TooltipHost>
</span>
) : (
<></>
);
};

View File

@@ -1,8 +1,8 @@
import { Image, Stack, TextField } from "office-ui-fabric-react";
import { Image, Stack, TextField } from "@fluentui/react";
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
import FolderIcon from "../../../images/folder_16x16.svg";
import * as Constants from "../Constants";
import { Tooltip } from "../Tooltip/Tooltip";
import { InfoTooltip } from "../Tooltip/InfoTooltip";
interface UploadProps {
label: string;
@@ -51,7 +51,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
return (
<div>
<span className="renewUploadItemsHeader">{label}</span>
<Tooltip>{tooltip}</Tooltip>
{tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
<Stack horizontal>
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
<input

View File

@@ -62,8 +62,8 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
};
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const defaultExperience = userContext.apiType;
switch (defaultExperience) {
const { apiType } = userContext;
switch (apiType) {
case "SQL":
return createSqlContainer(params);
case "Mongo":
@@ -75,7 +75,7 @@ const createCollectionWithARM = async (params: DataModels.CreateCollectionParams
case "Tables":
return createTable(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
};

View File

@@ -48,8 +48,9 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
}
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const defaultExperience = userContext.apiType;
switch (defaultExperience) {
const { apiType } = userContext;
switch (apiType) {
case "SQL":
return createSqlDatabase(params);
case "Mongo":
@@ -59,7 +60,7 @@ async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): P
case "Gremlin":
return createGremlineDatabase(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
}

View File

@@ -27,12 +27,10 @@ export async function deleteCollection(databaseId: string, collectionId: string)
}
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (defaultExperience) {
switch (apiType) {
case "SQL":
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case "Mongo":
@@ -44,6 +42,6 @@ function deleteCollectionWithARM(databaseId: string, collectionId: string): Prom
case "Tables":
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
}

View File

@@ -30,12 +30,10 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
}
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (defaultExperience) {
switch (apiType) {
case "SQL":
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case "Mongo":
@@ -45,6 +43,6 @@ function deleteDatabaseWithARM(databaseId: string): Promise<void> {
case "Gremlin":
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
}

View File

@@ -1,8 +1,8 @@
import { AuthType } from "../../AuthType";
import { armRequest } from "../../Utils/arm/request";
import { configContext } from "../../ConfigContext";
import { handleError } from "../ErrorHandlingUtils";
import { userContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { handleError } from "../ErrorHandlingUtils";
interface TimeSeriesData {
data: {
@@ -45,9 +45,9 @@ export const getCollectionUsageSizeInKB = async (databaseName: string, container
return undefined;
}
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`;
const metricNames = "DataUsage,IndexUsage";
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`;

View File

@@ -28,14 +28,12 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
};
const readCollectionOfferWithARM = async (databaseId: string, collectionId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
let rpResponse;
try {
switch (defaultExperience) {
switch (apiType) {
case "SQL":
rpResponse = await getSqlContainerThroughput(
subscriptionId,
@@ -76,7 +74,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
} catch (error) {
if (error.code !== "NotFound") {

View File

@@ -29,12 +29,11 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
switch (defaultExperience) {
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (apiType) {
case "SQL":
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break;
@@ -51,7 +50,7 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection);

View File

@@ -27,14 +27,12 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
};
const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
let rpResponse;
try {
switch (defaultExperience) {
switch (apiType) {
case "SQL":
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
@@ -48,7 +46,7 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
} catch (error) {
if (error.code !== "NotFound") {

View File

@@ -29,12 +29,10 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (defaultExperience) {
switch (apiType) {
case "SQL":
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break;
@@ -48,7 +46,7 @@ async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database);

View File

@@ -1,9 +1,9 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType";
export async function readMongoDBCollectionThroughRP(
databaseId: string,
@@ -13,9 +13,9 @@ export async function readMongoDBCollectionThroughRP(
return undefined;
}
let collection: MongoDBCollectionResource;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {

View File

@@ -11,12 +11,13 @@ export async function readUserDefinedFunctions(
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId
);

View File

@@ -63,12 +63,10 @@ async function updateCollectionWithARM(
collectionId: string,
newCollection: Partial<Collection>
): Promise<Collection> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.apiType;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
switch (defaultExperience) {
switch (apiType) {
case "SQL":
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case "Cassandra":
@@ -87,7 +85,7 @@ async function updateCollectionWithARM(
newCollection
);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
throw new Error(`Unsupported default experience type: ${apiType}`);
}
}

View File

@@ -144,9 +144,8 @@ const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Of
};
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale(
@@ -178,9 +177,8 @@ const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale(
@@ -212,9 +210,8 @@ const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<vo
};
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale(
@@ -246,9 +243,8 @@ const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<voi
};
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale(
@@ -280,9 +276,8 @@ const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
@@ -295,9 +290,8 @@ const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
};
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -310,9 +304,8 @@ const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void>
};
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -325,9 +318,8 @@ const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void
};
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -340,9 +332,8 @@ const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<
};
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);

View File

@@ -20,11 +20,13 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
@@ -38,9 +40,9 @@ export async function updateStoredProcedure(
},
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,

View File

@@ -16,12 +16,13 @@ export async function updateTrigger(
trigger: TriggerDefinition
): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
trigger.id
@@ -35,9 +36,9 @@ export async function updateTrigger(
},
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
trigger.id,

View File

@@ -19,12 +19,13 @@ export async function updateUserDefinedFunction(
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
@@ -38,9 +39,9 @@ export async function updateUserDefinedFunction(
},
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
subscriptionId,
resourceGroup,
databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,

View File

@@ -4,7 +4,6 @@ export interface DatabaseAccount {
location: string;
type: string;
kind: string;
tags: any;
properties: DatabaseAccountExtendedProperties;
}

View File

@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
});

View File

@@ -19,10 +19,7 @@ ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
// Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -29,11 +29,11 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] {
public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(),
onClick: () => container.onNewCollectionClicked(databaseId),
label: container.addCollectionText(),
},
];

View File

@@ -1,9 +1,8 @@
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
import * as React from "react";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
import * as Logger from "../../../Common/Logger";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
export interface ArcadiaMenuPickerProps {
selectText?: string;

View File

@@ -1,10 +1,11 @@
import { Icon, Label, Stack } from "office-ui-fabric-react";
import { Icon, Label, Stack } from "@fluentui/react";
import * as React from "react";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
onExpand?: () => void;
}
export interface CollapsibleSectionState {
@@ -23,6 +24,12 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded });
};
public componentDidUpdate(): void {
if (this.state.isExpanded && this.props.onExpand) {
this.props.onExpand();
}
}
public render(): JSX.Element {
return (
<>

View File

@@ -1,14 +1,20 @@
import {
ChoiceGroup,
DefaultButton,
Dialog as FluentDialog,
DialogFooter,
DialogType,
FontIcon,
IButtonProps,
IChoiceGroupProps,
IDialogProps,
IProgressIndicatorProps,
ITextFieldProps,
Link,
PrimaryButton,
ProgressIndicator,
} from "office-ui-fabric-react";
import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react/lib/Button";
import { Dialog as FluentDialog, DialogFooter, DialogType, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { Link } from "office-ui-fabric-react/lib/Link";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
TextField,
} from "@fluentui/react";
import React, { FunctionComponent } from "react";
export interface TextFieldProps extends ITextFieldProps {

View File

@@ -2,9 +2,9 @@
* React component for Switch Directory
*/
import _ from "underscore";
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps {

View File

@@ -1,11 +1,15 @@
import _ from "underscore";
import {
DefaultButton,
IButtonProps,
ITextFieldProps,
List,
ScrollablePane,
Sticky,
StickyPositionType,
TextField,
} from "@fluentui/react";
import * as React from "react";
import { DefaultButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
import { List } from "office-ui-fabric-react/lib/List";
import { ScrollablePane } from "office-ui-fabric-react/lib/ScrollablePane";
import { Sticky, StickyPositionType } from "office-ui-fabric-react/lib/Sticky";
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DirectoryListProps {

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test render renders with directories and default 1`] = `
<StyledWithResponsiveMode
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="asdfghjklzxcvbnm9876543210"
label="Set your default directory"
@@ -26,7 +26,7 @@ exports[`test render renders with directories and default 1`] = `
`;
exports[`test render renders with directories and last visit default 1`] = `
<StyledWithResponsiveMode
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
@@ -51,7 +51,7 @@ exports[`test render renders with directories and last visit default 1`] = `
`;
exports[`test render renders with directories but no default 1`] = `
<StyledWithResponsiveMode
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
@@ -76,7 +76,7 @@ exports[`test render renders with directories but no default 1`] = `
`;
exports[`test render renders with no directories 1`] = `
<StyledWithResponsiveMode
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"

View File

@@ -350,11 +350,11 @@ exports[`test render renders with filters 1`] = `
}
>
<div
className="ms-ScrollablePane root-40"
className="ms-ScrollablePane root-53"
data-is-scrollable="true"
>
<div
className="stickyAbove-42"
className="stickyAbove-55"
style={
Object {
"height": 0,
@@ -365,7 +365,7 @@ exports[`test render renders with filters 1`] = `
}
/>
<div
className="ms-ScrollablePane--contentContainer contentContainer-41"
className="ms-ScrollablePane--contentContainer contentContainer-54"
data-is-scrollable={true}
>
<Sticky
@@ -408,7 +408,6 @@ exports[`test render renders with filters 1`] = `
>
<TextFieldBase
ariaLabel="Directory filter text box"
canRevealPassword={false}
className="directoryListFilterTextBox"
deferredValidationTime={200}
onChange={[Function]}
@@ -691,18 +690,18 @@ exports[`test render renders with filters 1`] = `
validateOnLoad={true}
>
<div
className="ms-TextField directoryListFilterTextBox root-46"
className="ms-TextField directoryListFilterTextBox root-59"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-47"
className="ms-TextField-fieldGroup fieldGroup-60"
>
<input
aria-invalid={false}
aria-label="Directory filter text box"
className="ms-TextField-field field-48"
className="ms-TextField-field field-61"
id="TextField0"
onBlur={[Function]}
onChange={[Function]}
@@ -1266,7 +1265,6 @@ exports[`test render renders with filters 1`] = `
"borderColor": "#f3f2f1",
"color": "#a19f9d",
"cursor": "default",
"pointerEvents": "none",
"selectors": Object {
":focus": Object {
"outline": 0,
@@ -1611,6 +1609,35 @@ exports[`test render renders with filters 1`] = `
},
},
},
"splitButtonMenuFocused": Object {
"outline": "transparent",
"position": "relative",
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
"right": -2,
"top": -2,
},
},
"top": 3,
"zIndex": 1,
},
"::-moz-focus-inner": Object {
"border": "0",
},
},
},
"splitButtonMenuIcon": Object {
"color": "#323130",
},
@@ -1900,7 +1927,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-70"
data-is-focusable={false}
disabled={true}
onClick={[Function]}
@@ -1912,7 +1939,7 @@ exports[`test render renders with filters 1`] = `
type="button"
>
<span
className="ms-Button-flexContainer flexContainer-58"
className="ms-Button-flexContainer flexContainer-71"
data-automationid="splitbuttonprimary"
>
<div
@@ -1932,7 +1959,7 @@ exports[`test render renders with filters 1`] = `
</div>
</span>
</button>
<Component />
<FocusRects />
</BaseButton>
</DefaultButton>
</CustomizedDefaultButton>
@@ -1943,7 +1970,7 @@ exports[`test render renders with filters 1`] = `
</List>
</div>
<div
className="stickyBelow-43"
className="stickyBelow-56"
style={
Object {
"bottom": "0px",
@@ -1954,7 +1981,7 @@ exports[`test render renders with filters 1`] = `
}
>
<div
className="stickyBelowItems-44"
className="stickyBelowItems-57"
/>
</div>
</div>

View File

@@ -1,9 +1,14 @@
import {
Checkbox,
DefaultButton,
Dropdown,
IDropdownOption,
IDropdownStyles,
ITextFieldStyles,
Stack,
TextField,
} from "@fluentui/react";
import * as React from "react";
import { Stack } from "office-ui-fabric-react/lib/Stack";
import { Dropdown, IDropdownOption, IDropdownStyles } from "office-ui-fabric-react/lib/Dropdown";
import { Checkbox } from "office-ui-fabric-react/lib/Checkbox";
import { TextField, ITextFieldStyles } from "office-ui-fabric-react/lib/TextField";
import { DefaultButton } from "office-ui-fabric-react";
import "./FeaturePanelComponent.less";
export const FeaturePanelComponent: React.FunctionComponent = () => {

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import { FeaturePanelComponent } from "./FeaturePanelComponent";
import { getTheme, mergeStyleSets, FontWeights, Modal, IconButton, IIconProps } from "office-ui-fabric-react";
import { getTheme, mergeStyleSets, FontWeights, Modal, IconButton, IIconProps } from "@fluentui/react";
import "./FeaturePanelLauncher.less";
// Modal wrapper

View File

@@ -57,7 +57,7 @@ exports[`Feature panel renders all flags 1`] = `
}
}
>
<StyledWithResponsiveMode
<Dropdown
label="Base Url"
onChange={[Function]}
options={
@@ -85,7 +85,7 @@ exports[`Feature panel renders all flags 1`] = `
}
}
/>
<StyledWithResponsiveMode
<Dropdown
label="Platform"
onChange={[Function]}
options={

View File

@@ -1,4 +1,4 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
import * as React from "react";
import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";

View File

@@ -1,10 +1,4 @@
import {
ChoiceGroup,
IButtonProps,
IChoiceGroupProps,
PrimaryButton,
IChoiceGroupOption,
} from "office-ui-fabric-react";
import { ChoiceGroup, IButtonProps, IChoiceGroupProps, PrimaryButton, IChoiceGroupOption } from "@fluentui/react";
import * as React from "react";
import { ChildrenMargin } from "./GitHubStyleConstants";

View File

@@ -1,4 +1,4 @@
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react";
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "@fluentui/react";
import * as React from "react";
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
@@ -23,8 +23,6 @@ export interface RepoListItem {
}
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
@@ -32,14 +30,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element {
const header: JSX.Element = (
<p>
{this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle}
</p>
);
const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : (
@@ -66,9 +56,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return (
<>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && (
<>

View File

@@ -1,19 +1,21 @@
import {
IStyleFunctionOrObject,
ICheckboxStyleProps,
ICheckboxStyles,
IDropdownStyles,
IDropdownStyleProps,
} from "office-ui-fabric-react";
IDropdownStyles,
IStyleFunctionOrObject,
} from "@fluentui/react";
export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14,
paddingTop: 14,
height: "auto",
borderTop: "2px solid lightGray",
};
export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px",
paddingTop: "10px",
height: "auto",
borderTop: "2px solid lightGray",
};
export const ChildrenMargin = 10;
@@ -53,6 +55,11 @@ export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8,
};
export const ContentMainStyle: React.CSSProperties = {
display: "flex",
flexDirection: "column",
};
export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200;

View File

@@ -16,7 +16,7 @@ import {
ResponsiveMode,
SelectionMode,
Text,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import * as React from "react";
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils";

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "office-ui-fabric-react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "@fluentui/react";
export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure";

View File

@@ -13,7 +13,6 @@ const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};
@@ -30,7 +29,6 @@ const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};
@@ -48,7 +46,6 @@ const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
tableEndpoint: null,
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
},
tags: "testTags",
type: "testType",
};
};
@@ -65,7 +62,6 @@ const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};

View File

@@ -1,21 +1,23 @@
import { Card } from "@uifabric/react-cards";
import {
BaseButton,
Button,
FontWeights,
DocumentCard,
DocumentCardActivity,
DocumentCardDetails,
DocumentCardPreview,
DocumentCardTitle,
Icon,
IconButton,
Image,
IDocumentCardPreviewProps,
IDocumentCardStyles,
ImageFit,
Link,
LinkBase,
Persona,
Separator,
Spinner,
SpinnerSize,
Text,
TooltipHost,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import React, { FunctionComponent, useState } from "react";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { IGalleryItem } from "../../../../Juno/JunoClient";
@@ -49,7 +51,6 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
const CARD_WIDTH = 256;
const cardImageHeight = 144;
const cardDescriptionMaxChars = 80;
const cardItemGapBig = 10;
const cardItemGapSmall = 8;
const cardDeleteSpinnerHeight = 360;
const smallTextLineHeight = 18;
@@ -65,9 +66,9 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
const dateString = new Date(data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
const renderTruncatedDescription = (): string => {
let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
if (data.description.length > cardDescriptionMaxChars) {
const renderTruncated = (text: string, totalLength: number): string => {
let truncatedDescription = text.substr(0, totalLength);
if (text.length > totalLength) {
truncatedDescription = `${truncatedDescription} ...`;
}
return truncatedDescription;
@@ -110,7 +111,7 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
const handlerOnClick = (
event:
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | MouseEvent>
| React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
@@ -121,42 +122,35 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
event.preventDefault();
activate();
};
const DocumentCardActivityPeople = [{ name: data.author, profileImageSrc: data.isSample && CosmosDBLogo }];
const previewProps: IDocumentCardPreviewProps = {
previewImages: [
{
previewImageSrc: data.thumbnailUrl,
imageFit: ImageFit.cover,
width: CARD_WIDTH,
height: cardImageHeight,
},
],
};
const cardStyles: IDocumentCardStyles = {
root: { display: "inline-block", marginRight: 20, width: CARD_WIDTH },
};
return (
<Card
style={{ background: "white" }}
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => handlerOnClick(event, onClick)}
>
<DocumentCard aria-label={cardTitle} styles={cardStyles} onClick={onClick}>
{isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: cardDeleteSpinnerHeight } }}
/>
</Card.Item>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: cardDeleteSpinnerHeight } }}
/>
)}
{!isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Persona imageUrl={data.isSample && CosmosDBLogo} text={data.author} secondaryText={dateString} />
</Card.Item>
<Card.Item>
<Image
src={data.thumbnailUrl}
width={CARD_WIDTH}
height={cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: cardItemGapBig } }}>
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight } }}>
<DocumentCardActivity activity={dateString} people={DocumentCardActivityPeople} />
<DocumentCardPreview {...previewProps} />
<DocumentCardDetails>
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight, padding: "2px 16px" } }}>
{data.tags ? (
data.tags.map((tag, index, array) => (
<span key={tag}>
@@ -168,43 +162,22 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
<br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: cardItemGapSmall,
paddingBottom: cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: smallTextLineHeight * 2 } }}>
{renderTruncatedDescription()}
</Text>
<span>
<DocumentCardTitle title={renderTruncated(cardTitle, 20)} shouldTruncate />
<DocumentCardTitle
title={renderTruncated(data.description, cardDescriptionMaxChars)}
showAsSecondaryTitle
/>
<span style={{ padding: "8px 16px" }}>
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
</span>
</Card.Section>
</DocumentCardDetails>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: cardItemGapBig,
marginRight: cardItemGapBig,
},
}}
>
<DocumentCardDetails>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
<span style={{ padding: "0px 16px" }}>
{isFavorite !== undefined &&
generateIconButtonWithTooltip(
isFavorite ? "HeartFill" : "Heart",
@@ -223,10 +196,10 @@ export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps>
)
)}
</span>
</Card.Section>
</DocumentCardDetails>
)}
</>
)}
</Card>
</DocumentCard>
);
};

View File

@@ -1,59 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GalleryCardComponent renders 1`] = `
<Card
<StyledDocumentCardBase
aria-label="name"
data-is-focusable="true"
onClick={[Function]}
style={
styles={
Object {
"background": "white",
}
}
tokens={
Object {
"childrenGap": 0,
"width": 256,
"root": Object {
"display": "inline-block",
"marginRight": 20,
"width": 256,
},
}
}
>
<CardItem
tokens={
Object {
"padding": 10,
}
}
>
<StyledPersonaBase
imageUrl={false}
secondaryText="Invalid Date"
text="author"
/>
</CardItem>
<CardItem>
<StyledImageBase
alt="name cover image"
height={144}
imageFit={2}
src="thumbnailUrl"
width={256}
/>
</CardItem>
<CardSection
styles={
Object {
"root": Object {
"padding": 10,
<StyledDocumentCardActivityBase
activity="Invalid Date"
people={
Array [
Object {
"name": "author",
"profileImageSrc": false,
},
}
]
}
>
/>
<StyledDocumentCardPreviewBase
previewImages={
Array [
Object {
"height": 144,
"imageFit": 2,
"previewImageSrc": "thumbnailUrl",
"width": 256,
},
]
}
/>
<StyledDocumentCardDetailsBase>
<Text
nowrap={true}
styles={
Object {
"root": Object {
"height": 18,
"padding": "2px 16px",
},
}
}
@@ -69,33 +59,21 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledLinkBase>
</span>
</Text>
<Text
nowrap={true}
styles={
<StyledDocumentCardTitleBase
shouldTruncate={true}
title="name"
/>
<StyledDocumentCardTitleBase
showAsSecondaryTitle={true}
title="description"
/>
<span
style={
Object {
"root": Object {
"fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
},
"padding": "8px 16px",
}
}
>
name
</Text>
<Text
styles={
Object {
"root": Object {
"height": 36,
},
}
}
variant="small"
>
description
</Text>
<span>
<Text
styles={
Object {
@@ -169,17 +147,8 @@ exports[`GalleryCardComponent renders 1`] = `
0
</Text>
</span>
</CardSection>
<CardSection
styles={
Object {
"root": Object {
"marginLeft": 10,
"marginRight": 10,
},
}
}
>
</StyledDocumentCardDetailsBase>
<StyledDocumentCardDetailsBase>
<Separator
styles={
Object {
@@ -190,7 +159,13 @@ exports[`GalleryCardComponent renders 1`] = `
}
}
/>
<span>
<span
style={
Object {
"padding": "0px 16px",
}
}
>
<StyledTooltipHostBase
calloutProps={
Object {
@@ -276,6 +251,6 @@ exports[`GalleryCardComponent renders 1`] = `
/>
</StyledTooltipHostBase>
</span>
</CardSection>
</Card>
</StyledDocumentCardDetailsBase>
</StyledDocumentCardBase>
`;

View File

@@ -0,0 +1,123 @@
import * as React from "react";
import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
import { Stack, Text, Checkbox, PrimaryButton, Link } from "@fluentui/react";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
interface CodeOfConductComponentState {
readCodeOfConduct: boolean;
}
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
private viewCodeOfConductTraced: boolean;
private descriptionPara1: string;
private descriptionPara2: string;
private descriptionPara3: string;
private link1: { label: string; url: string };
constructor(props: CodeOfConductComponentProps) {
super(props);
this.state = {
readCodeOfConduct: false,
};
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
}
private async acceptCodeOfConduct(): Promise<void> {
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
try {
const response = await this.props.junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
this.props.onAcceptCodeOfConduct(response.data);
} catch (error) {
traceFailure(
Action.NotebooksGalleryAcceptCodeOfConduct,
{
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
}
}
private onChangeCheckbox = (): void => {
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
};
public render(): JSX.Element {
if (!this.viewCodeOfConductTraced) {
this.viewCodeOfConductTraced = true;
trace(Action.NotebooksGalleryViewCodeOfConduct);
}
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{this.descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{this.descriptionPara3}
<Link href={this.link1.url} target="_blank">
{this.link1.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: 12,
},
}}
label="I have read and accept the code of conduct."
onChange={this.onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await this.acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!this.state.readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
}
}

View File

@@ -9,7 +9,7 @@ describe("CodeOfConductComponent", () => {
let codeOfConductProps: CodeOfConductComponentProps;
beforeEach(() => {
const junoClient = new JunoClient(undefined);
const junoClient = new JunoClient();
junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK,
data: true,

View File

@@ -1,4 +1,4 @@
import { Checkbox, Link, PrimaryButton, Stack, Text } from "office-ui-fabric-react";
import { Checkbox, Link, PrimaryButton, Stack, Text } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react";
import { CodeOfConductEndpoints, HttpStatusCodes } from "../../../../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../../../../Common/ErrorHandlingUtils";

View File

@@ -19,7 +19,7 @@ import {
SpinnerSize,
Stack,
Text,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import * as React from "react";
import { HttpStatusCodes } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";
@@ -34,7 +34,6 @@ import { CodeOfConductComponent } from "./CodeOfConductComponent";
import "./GalleryViewerComponent.less";
import { InfoComponent } from "./InfoComponent/InfoComponent";
const CARD_WIDTH = 256;
export interface GalleryViewerComponentProps {
container?: Explorer;
junoClient: JunoClient;
@@ -87,7 +86,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
public static readonly PublishedTitle = "My published work";
private static readonly rowsPerPage = 5;
private static readonly CARD_WIDTH = 256;
private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most favorited";
@@ -644,7 +643,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
if (itemIndex === 0) {
this.columnCount = Math.floor(visibleRect.width / CARD_WIDTH) || this.columnCount;
this.columnCount = Math.floor(visibleRect.width / GalleryViewerComponent.CARD_WIDTH) || this.columnCount;
this.rowCount = GalleryViewerComponent.rowsPerPage;
}
@@ -672,7 +671,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
};
return (
<div style={{ float: "left", padding: 10 }}>
<div style={{ float: "left", padding: 5 }}>
<GalleryCardComponent {...props} />
</div>
);

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "office-ui-fabric-react";
import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "@fluentui/react";
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
import "./InfoComponent.less";

View File

@@ -4,7 +4,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<div
className="galleryContainer"
>
<StyledPivotBase
<StyledPivot
onLinkClick={[Function]}
selectedKey="OfficialSamples"
>
@@ -41,7 +41,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<StackItem
grow={true}
>
<StyledSearchBoxBase
<StyledSearchBox
onChange={[Function]}
placeholder="Search"
/>
@@ -60,7 +60,7 @@ exports[`GalleryViewerComponent renders 1`] = `
}
}
>
<StyledWithResponsiveMode
<Dropdown
onChange={[Function]}
options={
Array [
@@ -127,7 +127,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<StackItem
grow={true}
>
<StyledSearchBoxBase
<StyledSearchBox
onChange={[Function]}
placeholder="Search"
/>
@@ -146,7 +146,7 @@ exports[`GalleryViewerComponent renders 1`] = `
}
}
>
<StyledWithResponsiveMode
<Dropdown
onChange={[Function]}
options={
Array [
@@ -182,6 +182,6 @@ exports[`GalleryViewerComponent renders 1`] = `
</StackItem>
</Stack>
</PivotItem>
</StyledPivotBase>
</StyledPivot>
</div>
`;

View File

@@ -1,17 +1,7 @@
/**
* Wrapper around Notebook metadata
*/
import {
FontWeights,
Icon,
IconButton,
Link,
Persona,
PersonaSize,
PrimaryButton,
Stack,
Text,
} from "office-ui-fabric-react";
import { FontWeights, Icon, IconButton, Link, Persona, PersonaSize, PrimaryButton, Stack, Text } from "@fluentui/react";
import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";

View File

@@ -3,7 +3,7 @@
*/
import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core";
import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "office-ui-fabric-react";
import { IChoiceGroupProps, Icon, IProgressIndicatorProps, Link, ProgressIndicator } from "@fluentui/react";
import * as React from "react";
import { contents } from "rx-jupyter";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";

View File

@@ -1,22 +1,24 @@
import { IButtonProps, IconButton } from "office-ui-fabric-react/lib/Button";
import { ContextualMenu, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import {
ContextualMenu,
DetailsList,
DetailsListLayoutMode,
DetailsRow,
FocusZone,
IButtonProps,
IColumn,
IconButton,
IContextualMenuProps,
IDetailsListProps,
IDetailsRowProps,
} from "office-ui-fabric-react/lib/DetailsList";
import { FocusZone } from "office-ui-fabric-react/lib/FocusZone";
import { ITextField, ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import {
IObjectWithKey,
ISelectionZoneProps,
ITextField,
ITextFieldProps,
Selection,
SelectionMode,
SelectionZone,
} from "office-ui-fabric-react/lib/utilities/selection/index";
TextField,
} from "@fluentui/react";
import * as React from "react";
import * as _ from "underscore";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";

View File

@@ -2,10 +2,10 @@
* Horizontal switch component
*/
import { Icon } from "@fluentui/react";
import * as React from "react";
import "./RadioSwitchComponent.less";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { NormalizedEventKey } from "../../../Common/Constants";
import "./RadioSwitchComponent.less";
export interface Choice {
key: string;

View File

@@ -154,19 +154,20 @@ describe("SettingsComponent", () => {
expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined);
const newContainer = new Explorer();
newContainer.databaseAccount = ko.observable({
id: undefined,
name: undefined,
location: undefined,
type: undefined,
kind: undefined,
tags: undefined,
properties: {
documentEndpoint: undefined,
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
enableMultipleWriteLocations: true,
updateUserContext({
databaseAccount: {
id: undefined,
name: undefined,
location: undefined,
type: undefined,
kind: undefined,
properties: {
documentEndpoint: undefined,
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
enableMultipleWriteLocations: true,
},
},
});
const newCollection = { ...collection };

View File

@@ -1,4 +1,4 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
@@ -233,11 +233,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}
public loadMongoIndexes = async (): Promise<void> => {
if (
userContext.apiType === "Mongo" &&
this.container.isEnableMongoCapabilityPresent() &&
this.container.databaseAccount()
) {
if (userContext.apiType === "Mongo" && userContext?.databaseAccount) {
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
this.collection.databaseId,
this.collection.id()
@@ -300,8 +296,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
public hasConflictResolution = (): boolean =>
this.container?.databaseAccount &&
this.container.databaseAccount()?.properties?.enableMultipleWriteLocations &&
userContext?.databaseAccount?.properties?.enableMultipleWriteLocations &&
this.collection.conflictResolutionPolicy &&
!!this.collection.conflictResolutionPolicy();
@@ -876,7 +871,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps
): JSX.Element => {
if (userContext.authType === AuthType.AAD) {
if (this.container.isEnableMongoCapabilityPresent()) {
if (userContext.apiType === "Mongo") {
return <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />;
}
return undefined;

View File

@@ -1,6 +1,6 @@
import { shallow } from "enzyme";
import React from "react";
import { IColumn, Text } from "office-ui-fabric-react";
import { IColumn, Text } from "@fluentui/react";
import {
getAutoPilotV3SpendElement,
getEstimatedSpendingElement,

View File

@@ -38,7 +38,7 @@ import {
IDetailsRowProps,
DetailsRow,
IDetailsColumnStyles,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import { isDirtyTypes, isDirty } from "./SettingsUtils";
export interface EstimatedSpendingDisplayProps {

View File

@@ -9,7 +9,7 @@ import {
subComponentStackProps,
getChoiceGroupStyles,
} from "../SettingsRenderUtils";
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "office-ui-fabric-react";
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
import { isDirty } from "../SettingsUtils";

View File

@@ -1,7 +1,8 @@
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
import * as monaco from "monaco-editor";
import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels";
import { loadMonaco, monaco } from "../../../LazyMonaco";
import { loadMonaco } from "../../../LazyMonaco";
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
import { MessageBar, MessageBarType } from "@fluentui/react";
import {
mongoIndexTransformationRefreshingMessage,
renderMongoIndexTransformationRefreshMessage,

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IndexingPolicyRefreshComponent renders 1`] = `
<StyledMessageBarBase
<StyledMessageBar
messageBarType={5}
>
<Text
@@ -20,5 +20,5 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
Refresh to check the progress.
</StyledLinkBase>
</Text>
</StyledMessageBarBase>
</StyledMessageBar>
`;

View File

@@ -8,7 +8,7 @@ import {
Dropdown,
IDropdownOption,
ITextField,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import {
addMongoIndexSubElementsTokens,
mongoErrorMessageStyles,

View File

@@ -12,7 +12,7 @@ import {
Spinner,
SpinnerSize,
Separator,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import {
addMongoIndexStackProps,
customDetailsListStyles,

View File

@@ -30,7 +30,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
}
value="sample_key"
/>
<StyledWithResponsiveMode
<Dropdown
ariaLabel="Index Type 1"
onChange={[Function]}
options={
@@ -67,7 +67,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
onClick={[Function]}
/>
</Stack>
<StyledMessageBarBase
<StyledMessageBar
messageBarType={1}
styles={
Object {
@@ -78,6 +78,6 @@ exports[`AddMongoIndexComponent renders 1`] = `
}
>
sample error
</StyledMessageBarBase>
</StyledMessageBar>
</Stack>
`;

View File

@@ -1,14 +1,15 @@
import { shallow } from "enzyme";
import ko from "knockout";
import React from "react";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { container, collection } from "../TestUtils";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import Explorer from "../../../Explorer";
import * as Constants from "../../../../Common/Constants";
import * as DataModels from "../../../../Contracts/DataModels";
import { throughputUnit } from "../SettingsRenderUtils";
import * as SharedConstants from "../../../../Shared/Constants";
import ko from "knockout";
import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import { throughputUnit } from "../SettingsRenderUtils";
import { collection, container } from "../TestUtils";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer();
@@ -80,25 +81,25 @@ describe("ScaleComponent", () => {
it("autoScale enabled", () => {
const newContainer = new Explorer();
newContainer.databaseAccount({
id: undefined,
name: undefined,
location: undefined,
type: undefined,
kind: "documentdb",
tags: undefined,
properties: {
documentEndpoint: undefined,
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
capabilities: [
{
name: Constants.CapabilityNames.EnableAutoScale.toLowerCase(),
description: undefined,
},
],
updateUserContext({
databaseAccount: {
id: undefined,
name: undefined,
location: undefined,
type: undefined,
kind: "documentdb",
properties: {
documentEndpoint: undefined,
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
capabilities: [
{
name: Constants.CapabilityNames.EnableAutoScale.toLowerCase(),
description: undefined,
},
],
},
},
});
const props = { ...baseProps, container: newContainer };

View File

@@ -1,4 +1,4 @@
import { Label, Link, MessageBar, MessageBarType, Stack, Text, TextField } from "office-ui-fabric-react";
import { Label, Link, MessageBar, MessageBarType, Stack, Text, TextField } from "@fluentui/react";
import * as React from "react";
import * as Constants from "../../../../Common/Constants";
import { configContext, Platform } from "../../../../ConfigContext";
@@ -54,8 +54,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}
public isAutoScaleEnabled = (): boolean => {
const accountCapabilities: DataModels.Capability[] = this.props.container?.databaseAccount()?.properties
?.capabilities;
const accountCapabilities: DataModels.Capability[] = userContext?.databaseAccount?.properties?.capabilities;
const enableAutoScaleCapability =
accountCapabilities &&
accountCapabilities.find((capability: DataModels.Capability) => {
@@ -170,7 +169,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
private getThroughputInputComponent = (): JSX.Element => (
<ThroughputInputAutoPilotV3Component
databaseAccount={this.props.container.databaseAccount()}
databaseAccount={userContext?.databaseAccount}
databaseName={this.databaseId}
collectionName={this.collectionId}
throughput={this.props.throughput}
@@ -199,8 +198,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
);
private isFreeTierAccount(): boolean {
const databaseAccount = this.props.container?.databaseAccount();
return databaseAccount?.properties?.enableFreeTier;
return userContext?.databaseAccount?.properties?.enableFreeTier;
}
private getFreeTierInfoMessage(): JSX.Element {

View File

@@ -1,13 +1,4 @@
import {
ChoiceGroup,
IChoiceGroupOption,
Label,
Link,
MessageBar,
Stack,
Text,
TextField,
} from "office-ui-fabric-react";
import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text, TextField } from "@fluentui/react";
import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { userContext } from "../../../../UserContext";

View File

@@ -10,7 +10,7 @@ import {
Stack,
Text,
TextField,
} from "office-ui-fabric-react";
} from "@fluentui/react";
import React from "react";
import * as DataModels from "../../../../../Contracts/DataModels";
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";

View File

@@ -8,7 +8,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
}
}
>
<StyledMessageBarBase
<StyledMessageBar
messageBarIconProps={
Object {
"className": "messageBarWarningIcon",
@@ -27,7 +27,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
>
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes
</Text>
</StyledMessageBarBase>
</StyledMessageBar>
<Stack>
<StyledLabelBase
id="settingsV2RadioButtonLabelId"
@@ -49,7 +49,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
}
/>
</StyledLabelBase>
<StyledMessageBarBase
<StyledMessageBar
messageBarIconProps={
Object {
"className": "messageBarInfoIcon",
@@ -86,8 +86,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
Learn more
</a>
</Text>
</StyledMessageBarBase>
<StyledChoiceGroupBase
</StyledMessageBar>
<StyledChoiceGroup
ariaLabelledBy="settingsV2RadioButtonLabelId"
onChange={[Function]}
options={
@@ -196,7 +196,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
ariaLabelledBy="settingsV2RadioButtonLabelId"
onChange={[Function]}
options={
@@ -252,7 +252,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
>
capacity calculator
<Component
<FontIcon
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
@@ -415,7 +415,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>
@@ -470,7 +470,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
ariaLabelledBy="settingsV2RadioButtonLabelId"
onChange={[Function]}
options={
@@ -526,7 +526,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
>
capacity calculator
<Component
<FontIcon
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
@@ -689,7 +689,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Stack, Text, IIconStyles, Icon, TooltipHost, DirectionalHint } from "office-ui-fabric-react";
import { Stack, Text, IIconStyles, Icon, TooltipHost, DirectionalHint } from "@fluentui/react";
import { toolTipLabelStackTokens } from "../SettingsRenderUtils";
export interface ToolTipLabelComponentProps {

View File

@@ -8,7 +8,7 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
label="Mode"
onChange={[Function]}
options={
@@ -80,7 +80,7 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
label="Mode"
onChange={[Function]}
options={

View File

@@ -8,7 +8,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
}
}
>
<StyledMessageBarBase
<StyledMessageBar
messageBarType={5}
>
<Text
@@ -26,7 +26,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
Database: test, Container: test
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
</Text>
</StyledMessageBarBase>
</StyledMessageBar>
<Stack
tokens={
Object {

View File

@@ -15,7 +15,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="timeToLive"
label="Time to Live"
onChange={[Function]}
@@ -85,7 +85,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
value="1000"
/>
</Stack>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="geoSpatialConfig"
label="Geospatial Configuration"
onChange={[Function]}
@@ -146,7 +146,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
aria-labelledby="settingsV2ChangeFeedLabelId"
id="changeFeedPolicy"
onChange={[Function]}
@@ -238,7 +238,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="timeToLive"
label="Time to Live"
onChange={[Function]}
@@ -308,7 +308,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
value="1000"
/>
</Stack>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="geoSpatialConfig"
label="Geospatial Configuration"
onChange={[Function]}
@@ -355,7 +355,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="analyticalStorageTimeToLive"
label="Analytical Storage Time to Live"
onChange={[Function]}
@@ -422,7 +422,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
aria-labelledby="settingsV2ChangeFeedLabelId"
id="changeFeedPolicy"
onChange={[Function]}
@@ -514,7 +514,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="timeToLive"
label="Time to Live"
onChange={[Function]}
@@ -584,7 +584,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
value="1000"
/>
</Stack>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="geoSpatialConfig"
label="Geospatial Configuration"
onChange={[Function]}
@@ -631,7 +631,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="analyticalStorageTimeToLive"
label="Analytical Storage Time to Live"
onChange={[Function]}
@@ -753,7 +753,7 @@ exports[`SubSettingsComponent renders 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="timeToLive"
label="Time to Live"
onChange={[Function]}
@@ -823,7 +823,7 @@ exports[`SubSettingsComponent renders 1`] = `
value="1000"
/>
</Stack>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="geoSpatialConfig"
label="Geospatial Configuration"
onChange={[Function]}
@@ -870,7 +870,7 @@ exports[`SubSettingsComponent renders 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="analyticalStorageTimeToLive"
label="Analytical Storage Time to Live"
onChange={[Function]}
@@ -962,7 +962,7 @@ exports[`SubSettingsComponent renders 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
aria-labelledby="settingsV2ChangeFeedLabelId"
id="changeFeedPolicy"
onChange={[Function]}
@@ -1054,7 +1054,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="timeToLive"
label="Time to Live"
onChange={[Function]}
@@ -1099,7 +1099,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
}
/>
</Stack>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="geoSpatialConfig"
label="Geospatial Configuration"
onChange={[Function]}
@@ -1146,7 +1146,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
}
}
>
<StyledChoiceGroupBase
<StyledChoiceGroup
id="analyticalStorageTimeToLive"
label="Analytical Storage Time to Live"
onChange={[Function]}
@@ -1238,7 +1238,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
}
/>
</StyledLabelBase>
<StyledChoiceGroupBase
<StyledChoiceGroup
aria-labelledby="settingsV2ChangeFeedLabelId"
id="changeFeedPolicy"
onChange={[Function]}

View File

@@ -150,7 +150,7 @@ exports[`SettingsUtils functions render 1`] = `
</Text>
<Text>
<em>
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
</em>
</Text>
</Stack>
@@ -338,7 +338,7 @@ exports[`SettingsUtils functions render 1`] = `
</StyledLinkBase>
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
</Text>
<StyledMessageBarBase
<StyledMessageBar
messageBarType={1}
>
<Text>
@@ -350,7 +350,7 @@ exports[`SettingsUtils functions render 1`] = `
azure portal.
</StyledLinkBase>
</Text>
</StyledMessageBarBase>
</StyledMessageBar>
<Stack
horizontal={true}
tokens={

View File

@@ -1,12 +1,20 @@
import {
Dropdown,
IDropdownOption,
IStackTokens,
Label,
Link,
MessageBar,
MessageBarType,
Position,
Slider,
SpinButton,
Stack,
Text,
TextField,
Toggle,
} from "@fluentui/react";
import { TFunction } from "i18next";
import { Label, Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { Slider } from "office-ui-fabric-react/lib/Slider";
import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
import { Text } from "office-ui-fabric-react/lib/Text";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import * as React from "react";
import {
ChoiceItem,

View File

@@ -77,22 +77,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
}
>
<CustomizedSpinButton
<StyledSpinButton
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}
@@ -169,12 +158,12 @@ exports[`SmartUiComponent disable all inputs 1`] = `
}
>
<StackItem>
<StyledMessageBarBase
<StyledMessageBar
messageBarType={1}
>
Error:
label, truelabel and falselabel are required for boolean input 'throughput3'
</StyledMessageBarBase>
</StyledMessageBar>
</StackItem>
</Stack>
</div>
@@ -282,7 +271,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
<Dropdown
aria-labelledby="database-label"
disabled={true}
id="database-dropdown-input"
@@ -402,22 +391,10 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
}
>
<CustomizedSpinButton
<StyledSpinButton
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}
@@ -493,12 +470,12 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
}
>
<StackItem>
<StyledMessageBarBase
<StyledMessageBar
messageBarType={1}
>
Error:
label, truelabel and falselabel are required for boolean input 'throughput3'
</StyledMessageBarBase>
</StyledMessageBar>
</StackItem>
</Stack>
</div>
@@ -604,7 +581,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
<Dropdown
aria-labelledby="database-label"
id="database-dropdown-input"
onChange={[Function]}

View File

@@ -0,0 +1,13 @@
import { shallow } from "enzyme";
import React from "react";
import { CostEstimateText } from "./CostEstimateText";
const props = {
requestUnits: 5,
isAutoscale: false,
};
describe("CostEstimateText Pane", () => {
it("should render Default properly", () => {
const wrapper = shallow(<CostEstimateText {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,77 @@
import { Text } from "@fluentui/react";
import React, { FunctionComponent } from "react";
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext";
import {
calculateEstimateNumber,
computeRUUsagePriceHourly,
getAutoscalePricePerRu,
getCurrencySign,
getMultimasterMultiplier,
getPriceCurrency,
getPricePerRu,
} from "../../../../Utils/PricingUtils";
interface CostEstimateTextProps {
requestUnits: number;
isAutoscale: boolean;
}
export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
requestUnits,
isAutoscale,
}: CostEstimateTextProps) => {
const { databaseAccount } = userContext;
if (!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 = computeRUUsagePriceHourly({
serverId,
requestUnits,
numberOfRegions,
multimasterEnabled,
isAutoscale,
});
const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
const currency: string = getPriceCurrency(serverId);
const currencySign: string = getCurrencySign(serverId);
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
: getPricePerRu(serverId) * multiplier;
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
if (isAutoscale) {
return (
<Text variant="small">
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
</Text>
);
}
return (
<Text variant="small">
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
</Text>
);
};

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CostEstimateText Pane should render Default properly 1`] = `<Fragment />`;

View File

@@ -11,10 +11,6 @@
padding: 0 @LargeSpace 0 @SmallSpace;
}
.throughputInputSpacing {
margin-bottom: @SmallSpace;
& > * {
margin-bottom: @SmallSpace;
}
.throughputInputSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace;
}

View File

@@ -0,0 +1,36 @@
import { mount, ReactWrapper } from "enzyme";
import React from "react";
import { ThroughputInput } from "./ThroughputInput";
const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: false,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
onCostAcknowledgeChange: () => jest.fn(),
};
describe("ThroughputInput Pane", () => {
let wrapper: ReactWrapper;
beforeEach(() => {
wrapper = mount(<ThroughputInput {...props} />);
});
it("should render Default properly", () => {
expect(wrapper).toMatchSnapshot();
});
it("test Autoscale Mode select", () => {
wrapper.setProps({ isAutoscaleSelected: true });
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toBe(
"Estimate your required RU/s with capacity calculator."
);
expect(wrapper.find('[aria-label="maxRUDescription"]').at(0).text()).toContain("Max RU/s");
});
it("test Manual Mode select", () => {
wrapper.setProps({ isAutoscaleSelected: false });
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toContain("Estimate your required RU/s with");
expect(wrapper.find('[aria-label="capacityLink"]').at(0).text()).toContain("capacity calculator");
});
});

View File

@@ -1,209 +1,68 @@
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "office-ui-fabric-react";
import React from "react";
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
import React, { FunctionComponent, useState } from "react";
import * as Constants from "../../../Common/Constants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
import "./ThroughputInput.less";
export interface ThroughputInputProps {
isDatabase: boolean;
isSharded: boolean;
showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
isAutoscaleSelected?: boolean;
throughput?: number;
}
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();
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
isDatabase,
showFreeTierExceedThroughputTooltip,
setThroughputValue,
setIsAutoscale,
isSharded,
isAutoscaleSelected = true,
throughput = AutoPilotUtils.minAutoPilotThroughput,
onCostAcknowledgeChange,
}: ThroughputInputProps) => {
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
const [throughputError, setThroughputError] = useState<string>("");
const getThroughputLabelText = (): string => {
let throughputHeaderText: string;
if (isAutoscaleSelected) {
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
} else {
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
}
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
};
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 onThroughputValueChange = (newInput: string): void => {
const newThroughput = parseInt(newInput);
this.setState({ throughput: newThroughput });
this.props.setThroughputValue(newThroughput);
}
setThroughputValue(newThroughput);
if (!isSharded && newThroughput > 10000) {
setThroughputError("Unsharded collections support up to 10,000 RUs");
} else {
setThroughputError("");
}
};
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.`;
}
const getAutoScaleTooltip = (): string => {
const collectionName = getCollectionName().toLocaleLowerCase();
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
};
private getCostAcknowledgeText(): string {
const getCostAcknowledgeText = (): string => {
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
return "";
@@ -213,90 +72,159 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
return PricingUtils.getEstimatedSpendAcknowledgeString(
this.state.throughput,
throughput,
userContext.portalEnv,
numberOfRegions,
multimasterEnabled,
this.state.isAutoscaleSelected
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);
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
if (mode === "Autoscale") {
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
setIsAutoscale(true);
} else {
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
setIsAutoscale(false);
}
}
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>
<div className="throughputInputContainer throughputInputSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
{getThroughputLabelText()}
</Text>
<InfoTooltip>{PricingUtils.getRuToolTipText()}</InfoTooltip>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="throughputInputRadioBtn"
aria-label="Autoscale mode"
checked={isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
/>
<span className="throughputInputRadioBtnLabel">Autoscale</span>
<input
className="throughputInputRadioBtn"
aria-label="Manual mode"
checked={!isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeMode(e, "Manual")}
/>
<span className="throughputInputRadioBtnLabel">Manual</span>
</Stack>
{isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with{" "}
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
capacity calculator
</Link>
.
</Text>
<Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
{isDatabase ? "Database" : getCollectionName()} Max RU/s
</Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack>
<TextField
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
value={throughput.toString()}
aria-label="Max request units per second"
required={true}
errorMessage={throughputError}
/>
<Text variant="small">
Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale
from{" "}
<b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s
</b>{" "}
based on usage.
</Text>
</Stack>
)}
{!isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="capacityLink">
capacity calculator
</Link>
.
</Text>
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
content={
showFreeTierExceedThroughputTooltip &&
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) => onThroughputValueChange(newInput)}
step={100}
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
value={throughput.toString()}
aria-label="Max request units per second"
required={true}
errorMessage={throughputError}
/>
</TooltipHost>
</Stack>
)}
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
{throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start">
<Checkbox
checked={isCostAcknowledged}
styles={{
checkbox: { width: 12, height: 12 },
label: { padding: 0, margin: "4px 4px 0 0" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
setIsCostAcknowledged(isChecked);
onCostAcknowledgeChange(isChecked);
}}
/>
<Text variant="small" style={{ lineHeight: "20px" }}>
{getCostAcknowledgeText()}
</Text>
</Stack>
)}
</div>
);
};

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